makcar/app/src/main/java/com/aros/apron/tools/DualCaptureHelper.java

270 lines
9.0 KiB
Java
Raw Normal View History

2026-04-08 13:43:50 +08:00
package com.aros.apron.tools;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfInt;
import org.opencv.core.Size;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import android.os.Handler;
import android.os.Looper;
import com.aros.apron.constant.AMSConfig;
import com.aros.apron.entity.ArucoMarker;
import com.aros.apron.entity.Movement;
import com.aros.apron.manager.AlternateLandingManager;
import org.opencv.calib3d.Calib3d;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfInt;
import org.opencv.core.MatOfPoint2f;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.ArucoDetector;
import org.opencv.objdetect.DetectorParameters;
import org.opencv.objdetect.Dictionary;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* 双图保存工具 - 单例版
* 持续接收帧按钮触发保存
* 替换为极致容错图像增强逻辑保留增强后图像的保存
*/
public class DualCaptureHelper {
private static final String TAG = "DualCapture";
private static final String TAG_LOG = "EnhanceError"; // 增强失败日志TAG
private static DualCaptureHelper instance;
private int count = 0;
private volatile boolean shouldCapture = false; // 按钮触发标志
private Handler mainHandler = new Handler(Looper.getMainLooper());
// 帧数据缓存(最新一帧)
private int lastHeight = 0;
private int lastWidth = 0;
private byte[] lastFrameData = null;
private DualCaptureHelper() {}
public static synchronized DualCaptureHelper getInstance() {
if (instance == null) {
instance = new DualCaptureHelper();
}
return instance;
}
/**
* 持续接收帧在帧回调里调用每次都有
* 参数别改和你 detectArucoTags 一致
*/
public void onFrame(int height, int width, byte[] yuvData) {
// 缓存最新帧
this.lastHeight = height;
this.lastWidth = width;
this.lastFrameData = yuvData;
// 如果按钮触发了,保存这一帧
if (shouldCapture && lastFrameData != null) {
shouldCapture = false; // 重置,只保存一次
final int num = ++count;
final String time = new SimpleDateFormat("HHmmss", Locale.CHINA).format(new Date());
// 子线程保存
new Thread(() -> {
saveRaw(lastHeight, lastWidth, lastFrameData, time, num);
saveEnhanced(lastHeight, lastWidth, lastFrameData, time, num);
mainHandler.post(() -> {
LogUtil.log(TAG, "【完成】第" + num + "");
});
}).start();
}
}
/**
* 按钮调用触发下一帧保存
* 绑定到你的按钮点击
*/
public void captureNextFrame() {
shouldCapture = true;
LogUtil.log(TAG, "【等待】下一帧保存...");
}
/**
* 原始图YUV直接转BGR无任何处理
*/
private void saveRaw(int height, int width, byte[] yuvData, String time, int num) {
try {
Mat yuv = new Mat(height + height / 2, width, CvType.CV_8UC1);
yuv.put(0, 0, yuvData);
Mat bgr = new Mat();
Imgproc.cvtColor(yuv, bgr, Imgproc.COLOR_YUV2BGR_I420);
yuv.release();
File dir = getSaveDir();
String name = String.format("raw_%s_%03d.jpg", time, num);
String path = new File(dir, name).getAbsolutePath();
Imgcodecs.imwrite(path, bgr, new MatOfInt(Imgcodecs.IMWRITE_JPEG_QUALITY, 95));
bgr.release();
LogUtil.log(TAG, "【原始】" + path);
} catch (Exception e) {
LogUtil.log(TAG, "【原始失败】" + e.getMessage());
}
}
/**
* 增强图应用极致容错图像增强流程
*/
private void saveEnhanced(int height, int width, byte[] yuvData, String time, int num) {
try {
Mat yuv = new Mat(height + height / 2, width, CvType.CV_8UC1);
yuv.put(0, 0, yuvData);
Mat gray = new Mat();
Imgproc.cvtColor(yuv, gray, Imgproc.COLOR_YUV2GRAY_I420);
yuv.release();
// 替换为极致容错的增强逻辑
Mat enhanced = createEnhancedImage(gray);
gray.release();
Mat bgr = new Mat();
Imgproc.cvtColor(enhanced, bgr, Imgproc.COLOR_GRAY2BGR);
enhanced.release();
File dir = getSaveDir();
String name = String.format("enhanced_%s_%03d.jpg", time, num);
String path = new File(dir, name).getAbsolutePath();
Imgcodecs.imwrite(path, bgr, new MatOfInt(Imgcodecs.IMWRITE_JPEG_QUALITY, 95));
bgr.release();
LogUtil.log(TAG, "【增强】" + path);
} catch (Exception e) {
LogUtil.log(TAG, "【增强失败】" + e.getMessage());
}
}
// ========== 【极致容错】图像增强:全高度段通用 ==========
private Mat createEnhancedImage(Mat src) {
Mat result = new Mat();
try {
// 1. 多尺度CLAHE适应不同亮度
Mat claheMat = new Mat();
Imgproc.createCLAHE(2.0, new Size(8, 8)).apply(src, claheMat);
// 2. 中值滤波去噪(比双边快,保边足够)
Mat filtered = new Mat();
Imgproc.medianBlur(claheMat, filtered, 5);
claheMat.release();
// 3. 自适应阈值(块大小动态,适应全高度)
Mat binary = new Mat();
Imgproc.adaptiveThreshold(filtered, binary, 255,
Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C,
Imgproc.THRESH_BINARY, 41, 3); // 块41更大常数3更敏感
filtered.release();
// 4. 形态学操作(连接断裂边框)
Mat morph = new Mat();
Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(5, 5));
Imgproc.morphologyEx(binary, morph, Imgproc.MORPH_CLOSE, kernel);
kernel.release();
binary.release();
// 5. 轻度锐化(突出边缘,不过度)
Mat sharpened = new Mat();
Mat blurred = new Mat();
Imgproc.GaussianBlur(morph, blurred, new Size(0, 0), 3.0);
Core.addWeighted(morph, 1.3, blurred, -0.3, 0, sharpened);
morph.release();
blurred.release();
return sharpened;
} catch (Exception e) {
LogUtil.log(TAG_LOG, "增强失败: " + e.getMessage());
src.copyTo(result);
return result;
}
}
// ========== 【超高容错】ArUco检测参数配置保留方法按需调用 ==========
private DetectorParameters createUltraTolerantParams() {
DetectorParameters params = new DetectorParameters();
// 全高度段4dm=375px, 9dm=190px, 50dm=19px
params.set_minMarkerPerimeterRate(0.003f); // 降到0.00319像素也能检
// 畸变/反光/模糊宽容
params.set_polygonalApproxAccuracyRate(0.12f); // 更松
params.set_cornerRefinementMethod(1); // SUBPIX
params.set_cornerRefinementWinSize(3); // 降到3更快
params.set_cornerRefinementMaxIterations(30);
params.set_cornerRefinementMinAccuracy(0.12f); // 放宽收敛
// 阈值范围更大,适应全光照
params.set_adaptiveThreshWinSizeMin(3);
params.set_adaptiveThreshWinSizeMax(63); // 更大
params.set_adaptiveThreshWinSizeStep(10);
params.set_adaptiveThreshConstant(3); // 配合预处理
params.set_minCornerDistanceRate(0.02f);
params.set_minMarkerLengthRatioOriginalImg(0.03f); // 更宽容
params.set_minDistanceToBorder(1); // 边缘也检
params.set_perspectiveRemovePixelPerCell(2); // 降到2小像素精细
params.set_perspectiveRemoveIgnoredMarginPerCell(0.2f);
params.set_maxErroneousBitsInBorderRate(0.6f); // 60%容错
params.set_detectInvertedMarker(false);
return params;
}
private File getSaveDir() {
File dir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM),
"DJIDemo/DualCapture");
if (!dir.exists()) dir.mkdirs();
return dir;
}
public void reset() {
count = 0;
LogUtil.log(TAG, "【重置】");
}
public int getCount() {
return count;
}
}