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.003,19像素也能检 // 畸变/反光/模糊宽容 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; } }