270 lines
9.0 KiB
Java
270 lines
9.0 KiB
Java
|
|
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;
|
|||
|
|
}
|
|||
|
|
}
|