2026-01-30 11:47:32 +08:00
|
|
|
|
package com.aros.apron.tools;
|
|
|
|
|
|
|
|
|
|
|
|
import android.os.Handler;
|
|
|
|
|
|
import android.os.Looper;
|
2026-02-27 10:18:56 +08:00
|
|
|
|
|
2026-01-30 11:47:32 +08:00
|
|
|
|
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;
|
2026-02-01 14:34:23 +08:00
|
|
|
|
import org.opencv.core.Size;
|
2026-01-30 11:47:32 +08:00
|
|
|
|
import org.opencv.imgproc.Imgproc;
|
2026-02-01 14:34:23 +08:00
|
|
|
|
import org.opencv.objdetect.ArucoDetector;
|
|
|
|
|
|
import org.opencv.objdetect.DetectorParameters;
|
|
|
|
|
|
import org.opencv.objdetect.Dictionary;
|
2026-01-30 11:47:32 +08:00
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
2026-02-27 10:18:56 +08:00
|
|
|
|
import dji.sdk.keyvalue.key.FlightControllerKey;
|
|
|
|
|
|
import dji.sdk.keyvalue.key.KeyTools;
|
|
|
|
|
|
import dji.sdk.keyvalue.value.common.EmptyMsg;
|
|
|
|
|
|
import dji.v5.common.callback.CommonCallbacks;
|
|
|
|
|
|
import dji.v5.common.error.IDJIError;
|
|
|
|
|
|
import dji.v5.manager.KeyManager;
|
|
|
|
|
|
|
2026-03-12 14:57:34 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 2帧一处理:0处理-1跳过-2处理-3跳过
|
|
|
|
|
|
* 跳过的帧直接return,啥也不干,让飞机自己飘
|
|
|
|
|
|
*/
|
|
|
|
|
|
public class ApronArucoDetect {
|
2026-01-30 11:47:32 +08:00
|
|
|
|
|
2026-03-12 14:57:34 +08:00
|
|
|
|
private static final String TAG = "ApronArucoDetect";
|
2026-01-30 11:47:32 +08:00
|
|
|
|
|
2026-03-12 14:57:34 +08:00
|
|
|
|
// ========== 原有参数 ==========
|
|
|
|
|
|
private static double LENS_OFFSET_X = 0;
|
|
|
|
|
|
private static double LENS_OFFSET_Y = 0;
|
|
|
|
|
|
private static final int CENTER_ERR_MAX = 30;
|
|
|
|
|
|
private static final float SLOW_LAND_SPEED = -0.2f;
|
|
|
|
|
|
private static final float SLOW_SUPER_SPEED = -0.1f;
|
|
|
|
|
|
private static final int PIXEL_TRIGGER = 360;
|
|
|
|
|
|
private static final int TRIGGER_FRAME_THRESHOLD = 2;
|
2026-01-30 11:47:32 +08:00
|
|
|
|
|
2026-03-12 14:57:34 +08:00
|
|
|
|
// ========== 原有状态 ==========
|
|
|
|
|
|
private boolean isTriggerSuccess;
|
|
|
|
|
|
private boolean arucoNotFoundTag = false;
|
2026-02-01 14:34:23 +08:00
|
|
|
|
private boolean isStartAruco = false;
|
2026-01-30 11:47:32 +08:00
|
|
|
|
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
|
|
|
|
|
|
Future<?> lastFuture = null;
|
2026-03-12 14:57:34 +08:00
|
|
|
|
private String TAG_LOG = getClass().getSimpleName();
|
2026-01-30 11:47:32 +08:00
|
|
|
|
Double resultYaw = 0.0;
|
|
|
|
|
|
private boolean triggerToAlternateLandingPoint;
|
|
|
|
|
|
long startTime;
|
|
|
|
|
|
long endTime;
|
|
|
|
|
|
private int sigleMarkerDetectFailsTimes;
|
|
|
|
|
|
private boolean dropTimesTag;
|
2026-03-12 14:57:34 +08:00
|
|
|
|
private int dropTimes = 0;
|
2026-01-30 11:47:32 +08:00
|
|
|
|
private boolean isDoublePayload;
|
2026-03-12 14:57:34 +08:00
|
|
|
|
private int trycount = 0;
|
2026-02-01 14:34:23 +08:00
|
|
|
|
|
2026-01-30 11:47:32 +08:00
|
|
|
|
public PIDControl pidControlX = null;
|
|
|
|
|
|
public PIDControl pidControlY = null;
|
|
|
|
|
|
public int checkThrowingErrorsTimes;
|
2026-03-12 14:57:34 +08:00
|
|
|
|
private int consecutiveTriggerCount = 0;
|
|
|
|
|
|
|
|
|
|
|
|
// ========== 【新增】2帧一处理计数器 ==========
|
|
|
|
|
|
private int frameCounter = 0;
|
|
|
|
|
|
|
|
|
|
|
|
// ========== 原有方法 ==========
|
|
|
|
|
|
public int getCheckThrowingErrorsTimes() { return checkThrowingErrorsTimes; }
|
|
|
|
|
|
public void setCheckThrowingErrorsTimes(int v) { checkThrowingErrorsTimes = v; }
|
|
|
|
|
|
public boolean isTriggerSuccess() { return isTriggerSuccess; }
|
|
|
|
|
|
public void setTriggerSuccess(boolean v) { isTriggerSuccess = v; }
|
|
|
|
|
|
public boolean isDoublePayload() { return isDoublePayload; }
|
|
|
|
|
|
public void setDoublePayload(boolean v) { isDoublePayload = v; }
|
|
|
|
|
|
public boolean isStartFastStick() { return startFastStick; }
|
|
|
|
|
|
public void setStartFastStick(boolean v) { startFastStick = v; }
|
|
|
|
|
|
public boolean isCanLanding() { return canLanding; }
|
|
|
|
|
|
public void setCanLanding(boolean v) {
|
|
|
|
|
|
startTime = 0;
|
|
|
|
|
|
endTime = 0;
|
|
|
|
|
|
canLanding = v;
|
2026-01-30 11:47:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-12 14:57:34 +08:00
|
|
|
|
private ApronArucoDetect() {}
|
2026-01-30 11:47:32 +08:00
|
|
|
|
private static class OpenCVHelperHolder {
|
|
|
|
|
|
private static final ApronArucoDetect INSTANCE = new ApronArucoDetect();
|
2026-03-12 14:57:34 +08:00
|
|
|
|
static { INSTANCE.init(); }
|
2026-01-30 11:47:32 +08:00
|
|
|
|
}
|
2026-03-12 14:57:34 +08:00
|
|
|
|
public static ApronArucoDetect getInstance() { return OpenCVHelperHolder.INSTANCE; }
|
2026-02-01 14:34:23 +08:00
|
|
|
|
|
2026-01-30 11:47:32 +08:00
|
|
|
|
public void init() {
|
2026-03-12 14:57:34 +08:00
|
|
|
|
pidControlX = new PIDControl(0.65f, 0.02f, 0.2f, 0.05f, 2.5f, 0.1f);
|
|
|
|
|
|
pidControlY = new PIDControl(0.65f, 0.02f, 0.2f, 0.05f, 2.5f, 0.1f);
|
|
|
|
|
|
// pidControlX = new PIDControl(0.8f, 0.002f, 0.09f, 0.05f, 2.0f, 0.05f);
|
|
|
|
|
|
// pidControlY = new PIDControl(0.8f, 0.002f, 0.09f, 0.05f, 2.0f, 0.05f);
|
2026-01-30 11:47:32 +08:00
|
|
|
|
pidControlX.reset();
|
|
|
|
|
|
pidControlY.reset();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-12 14:57:34 +08:00
|
|
|
|
public boolean startFastStick;
|
|
|
|
|
|
public boolean canLanding;
|
2026-01-30 11:47:32 +08:00
|
|
|
|
|
2026-03-12 14:57:34 +08:00
|
|
|
|
// ========== 【核心修改】主入口:2帧一处理 ==========
|
2026-01-30 11:47:32 +08:00
|
|
|
|
public void detectArucoTags(int height, int width, byte[] data, Dictionary dictionary) {
|
2026-02-01 14:34:23 +08:00
|
|
|
|
isTriggerSuccess = true;
|
2026-01-30 11:47:32 +08:00
|
|
|
|
Movement.getInstance().setVirtualStickEnableReason(2);
|
2026-03-12 14:57:34 +08:00
|
|
|
|
|
|
|
|
|
|
// 原有过滤
|
2026-01-30 11:47:32 +08:00
|
|
|
|
if (isStartAruco || startFastStick) {
|
2026-03-12 14:57:34 +08:00
|
|
|
|
LogUtil.log(TAG_LOG, "过滤:" + isStartAruco + startFastStick);
|
2026-02-01 14:34:23 +08:00
|
|
|
|
if (!isStartAruco && startFastStick) {
|
2026-01-30 11:47:32 +08:00
|
|
|
|
checkThrowingErrorsTimes++;
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-02-01 14:34:23 +08:00
|
|
|
|
|
2026-03-12 14:57:34 +08:00
|
|
|
|
// ========== 【关键】2帧一处理:0处理-1跳过-2处理-3跳过 ==========
|
|
|
|
|
|
int currentFrame = frameCounter++;
|
|
|
|
|
|
boolean shouldProcess = (currentFrame % 2 == 0); // 偶数帧处理,奇数帧跳过
|
|
|
|
|
|
|
|
|
|
|
|
if (!shouldProcess) {
|
|
|
|
|
|
// 【关键】跳过的帧:啥也不干,直接return,让飞机自己飘
|
|
|
|
|
|
LogUtil.log(TAG_LOG, "【跳过帧】" + currentFrame + " 让飞机自稳");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
LogUtil.log(TAG_LOG, "【处理帧】" + currentFrame + " 执行修正");
|
|
|
|
|
|
|
2026-01-30 11:47:32 +08:00
|
|
|
|
isStartAruco = true;
|
2026-02-27 10:18:56 +08:00
|
|
|
|
|
2026-01-30 11:47:32 +08:00
|
|
|
|
if (lastFuture != null && !lastFuture.isDone()) {
|
2026-03-12 14:57:34 +08:00
|
|
|
|
LogUtil.log(TAG_LOG, "break---");
|
2026-01-30 11:47:32 +08:00
|
|
|
|
lastFuture.cancel(true);
|
|
|
|
|
|
}
|
2026-02-01 14:34:23 +08:00
|
|
|
|
|
2026-03-12 14:57:34 +08:00
|
|
|
|
// 动态偏移(原有)
|
|
|
|
|
|
int ultHeight = Movement.getInstance().getUltrasonicHeight();
|
|
|
|
|
|
if (ultHeight >= 30) { LENS_OFFSET_X = 0; LENS_OFFSET_Y = 0; }
|
|
|
|
|
|
else if (ultHeight >= 20) { LENS_OFFSET_X = 0; LENS_OFFSET_Y = 0; }
|
|
|
|
|
|
else if (ultHeight >= 10) { LENS_OFFSET_X = 20; LENS_OFFSET_Y = 10; }
|
|
|
|
|
|
else if (ultHeight >= 5) { LENS_OFFSET_X = 30; LENS_OFFSET_Y = 20; }
|
|
|
|
|
|
else { LENS_OFFSET_X = 55; LENS_OFFSET_Y = 35; }
|
2026-02-01 14:34:23 +08:00
|
|
|
|
|
2026-03-12 14:57:34 +08:00
|
|
|
|
// ========== 原有完整逻辑(一点不改) ==========
|
2026-02-01 14:34:23 +08:00
|
|
|
|
lastFuture = executor.schedule(new Runnable() {
|
2026-01-30 11:47:32 +08:00
|
|
|
|
@Override
|
|
|
|
|
|
public void run() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
Mat yuvMat = new Mat(height + height / 2, width, CvType.CV_8UC1);
|
|
|
|
|
|
yuvMat.put(0, 0, data);
|
|
|
|
|
|
Mat rgbMat = new Mat();
|
|
|
|
|
|
Imgproc.cvtColor(yuvMat, rgbMat, Imgproc.COLOR_YUV2BGR_I420);
|
|
|
|
|
|
Mat grayImgMat = new Mat();
|
2026-02-01 14:34:23 +08:00
|
|
|
|
Imgproc.cvtColor(rgbMat, grayImgMat, Imgproc.COLOR_BGR2GRAY);
|
|
|
|
|
|
|
2026-03-12 14:57:34 +08:00
|
|
|
|
// ========== 【极致容错】双重检测策略 ==========
|
|
|
|
|
|
// 策略1:增强图检测(适应低光/反光)
|
|
|
|
|
|
Mat enhancedMat = createEnhancedImage(grayImgMat);
|
2026-01-30 11:47:32 +08:00
|
|
|
|
MatOfInt ids = new MatOfInt();
|
2026-02-01 14:34:23 +08:00
|
|
|
|
List<Mat> corners = new ArrayList<>();
|
|
|
|
|
|
|
2026-03-12 14:57:34 +08:00
|
|
|
|
DetectorParameters params = createUltraTolerantParams();
|
|
|
|
|
|
ArucoDetector detector = new ArucoDetector(dictionary, params);
|
|
|
|
|
|
detector.detectMarkers(enhancedMat, corners, ids);
|
2026-02-01 14:34:23 +08:00
|
|
|
|
|
2026-03-12 14:57:34 +08:00
|
|
|
|
// 策略2:如果失败,用原图兜底(适应过曝)
|
2026-02-01 14:34:23 +08:00
|
|
|
|
if (ids.empty()) {
|
|
|
|
|
|
corners.clear();
|
|
|
|
|
|
detector.detectMarkers(grayImgMat, corners, ids);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-12 14:57:34 +08:00
|
|
|
|
// 策略3:如果还失败,降采样检测(适应高分辨率模糊)
|
|
|
|
|
|
if (ids.empty()) {
|
|
|
|
|
|
corners.clear();
|
|
|
|
|
|
Mat downscaled = new Mat();
|
|
|
|
|
|
Imgproc.resize(grayImgMat, downscaled, new Size(grayImgMat.width() * 0.8, grayImgMat.height() * 0.8));
|
|
|
|
|
|
detector.detectMarkers(downscaled, corners, ids);
|
|
|
|
|
|
// 坐标映射回原图比例
|
|
|
|
|
|
if (!ids.empty()) {
|
|
|
|
|
|
for (Mat corner : corners) {
|
|
|
|
|
|
Core.multiply(corner, new Scalar(1.25, 1.25), corner);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
downscaled.release();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-01 14:34:23 +08:00
|
|
|
|
ids = keepOnly6(ids, corners);
|
2026-01-30 11:47:32 +08:00
|
|
|
|
|
2026-02-01 14:34:23 +08:00
|
|
|
|
boolean marker6Found = false;
|
2026-03-12 14:57:34 +08:00
|
|
|
|
Mat corner6 = new Mat();
|
|
|
|
|
|
|
2026-02-01 14:34:23 +08:00
|
|
|
|
if (!ids.empty()) {
|
2026-03-12 14:57:34 +08:00
|
|
|
|
trycount = 0;
|
2026-01-30 11:47:32 +08:00
|
|
|
|
arucoNotFoundTag = false;
|
|
|
|
|
|
int[] idArray = ids.toArray();
|
|
|
|
|
|
int ultrasonicHeight = Movement.getInstance().getUltrasonicHeight();
|
2026-02-01 14:34:23 +08:00
|
|
|
|
|
|
|
|
|
|
if (idArray.length == 1 && idArray[0] == 6) {
|
|
|
|
|
|
marker6Found = true;
|
|
|
|
|
|
corner6 = corners.get(0);
|
|
|
|
|
|
Point[] points = new Point[4];
|
|
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
|
|
|
|
double[] p = corner6.get(0, i);
|
|
|
|
|
|
points[i] = new Point(p[0], p[1]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
double pixelWidth = calculateDistance(points[0], points[1]);
|
|
|
|
|
|
|
2026-03-12 14:57:34 +08:00
|
|
|
|
double centerX = (points[0].x + points[1].x + points[2].x + points[3].x) / 4.0 + LENS_OFFSET_X;
|
|
|
|
|
|
double errX = Math.abs(centerX - rgbMat.width() / 2.0);
|
|
|
|
|
|
double centerY = (points[0].y + points[1].y + points[2].y + points[3].y) / 4.0 + LENS_OFFSET_Y;
|
|
|
|
|
|
double errY = Math.abs(centerY - rgbMat.height() / 2.0);
|
2026-02-01 14:34:23 +08:00
|
|
|
|
|
2026-03-12 14:57:34 +08:00
|
|
|
|
LogUtil.log(TAG_LOG, "像素" + (int) pixelWidth +
|
2026-02-01 14:34:23 +08:00
|
|
|
|
" errX=" + (int) errX + " errY=" + (int) errY + " (含偏移" + LENS_OFFSET_X + ")");
|
|
|
|
|
|
|
2026-03-12 14:57:34 +08:00
|
|
|
|
// 速降判断(原有)
|
|
|
|
|
|
if (!startFastStick) {
|
|
|
|
|
|
if (pixelWidth >= PIXEL_TRIGGER &&
|
|
|
|
|
|
ultrasonicHeight <= 4 &&
|
|
|
|
|
|
errX < CENTER_ERR_MAX &&
|
|
|
|
|
|
errY < CENTER_ERR_MAX) {
|
|
|
|
|
|
|
|
|
|
|
|
consecutiveTriggerCount++;
|
|
|
|
|
|
LogUtil.log(TAG_LOG, "速降条件满足,累计帧数: " + consecutiveTriggerCount);
|
|
|
|
|
|
|
|
|
|
|
|
if (consecutiveTriggerCount >= TRIGGER_FRAME_THRESHOLD) {
|
|
|
|
|
|
startFastStick = true;
|
|
|
|
|
|
consecutiveTriggerCount = 0;
|
|
|
|
|
|
|
|
|
|
|
|
LogUtil.log(TAG_LOG, "【触发速降】连续满足" + TRIGGER_FRAME_THRESHOLD +
|
|
|
|
|
|
"帧,pixel=" + (int) pixelWidth + " errX=" + (int) errX);
|
|
|
|
|
|
|
|
|
|
|
|
handler.postDelayed(new Runnable() {
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public void run() {
|
|
|
|
|
|
handler.post(runnable);
|
|
|
|
|
|
LogUtil.log(TAG_LOG, "延迟结束,开始速降");
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 300);
|
|
|
|
|
|
|
|
|
|
|
|
// 清理资源
|
|
|
|
|
|
enhancedMat.release();
|
|
|
|
|
|
grayImgMat.release();
|
|
|
|
|
|
rgbMat.release();
|
|
|
|
|
|
yuvMat.release();
|
|
|
|
|
|
ids.release();
|
|
|
|
|
|
if (!corners.isEmpty()) {
|
|
|
|
|
|
for (Mat c : corners) if (c != null) c.release();
|
|
|
|
|
|
}
|
|
|
|
|
|
isStartAruco = false;
|
|
|
|
|
|
return;
|
2026-02-27 10:18:56 +08:00
|
|
|
|
}
|
2026-03-12 14:57:34 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
if (consecutiveTriggerCount > 0) {
|
|
|
|
|
|
LogUtil.log(TAG_LOG, "速降条件中断,重置累计帧数(原因:像素=" + (int)pixelWidth +
|
|
|
|
|
|
" 超声=" + ultrasonicHeight + " errX=" + (int)errX + " errY=" + (int)errY + ")");
|
|
|
|
|
|
consecutiveTriggerCount = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-30 11:47:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-12 14:57:34 +08:00
|
|
|
|
|
|
|
|
|
|
// 原有修正逻辑
|
|
|
|
|
|
if (marker6Found) {
|
2026-02-01 14:34:23 +08:00
|
|
|
|
moveOnArucoDetected(new ArucoMarker(1, corner6, 0.24f), rgbMat.width(), rgbMat.height());
|
2026-01-30 11:47:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
dropTimesTag = true;
|
2026-03-12 14:57:34 +08:00
|
|
|
|
|
2026-02-01 14:34:23 +08:00
|
|
|
|
} else {
|
2026-03-12 14:57:34 +08:00
|
|
|
|
// 原有丢失处理
|
|
|
|
|
|
LogUtil.log(TAG_LOG, "找不到了二维码");
|
2026-01-30 11:47:32 +08:00
|
|
|
|
if (!arucoNotFoundTag) {
|
|
|
|
|
|
startTime = System.currentTimeMillis();
|
|
|
|
|
|
arucoNotFoundTag = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
endTime = System.currentTimeMillis();
|
2026-02-01 14:34:23 +08:00
|
|
|
|
long lostDuration = endTime - startTime;
|
2026-03-12 14:57:34 +08:00
|
|
|
|
|
|
|
|
|
|
if (lostDuration > 1000 && lostDuration <= 12000) {
|
|
|
|
|
|
if (Movement.getInstance().getUltrasonicHeight() <= 20) {
|
|
|
|
|
|
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, 3f);
|
2026-01-30 11:47:32 +08:00
|
|
|
|
if (dropTimes > Integer.parseInt(AMSConfig.getInstance().getAlternateLandingTimes())) {
|
2026-03-12 14:57:34 +08:00
|
|
|
|
LogUtil.log(TAG_LOG, "超过复降限制,去备降点");
|
|
|
|
|
|
|
2026-01-30 11:47:32 +08:00
|
|
|
|
AlternateLandingManager.getInstance().startTaskProcess(null);
|
2026-03-12 14:57:34 +08:00
|
|
|
|
Movement.getInstance().setAlternate(true);
|
|
|
|
|
|
|
|
|
|
|
|
// 清理资源
|
|
|
|
|
|
enhancedMat.release();
|
|
|
|
|
|
grayImgMat.release();
|
|
|
|
|
|
rgbMat.release();
|
|
|
|
|
|
yuvMat.release();
|
|
|
|
|
|
ids.release();
|
|
|
|
|
|
if (!corners.isEmpty()) {
|
|
|
|
|
|
for (Mat c : corners) if (c != null) c.release();
|
|
|
|
|
|
}
|
|
|
|
|
|
isStartAruco = false;
|
2026-01-30 11:47:32 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (dropTimesTag) {
|
|
|
|
|
|
dropTimesTag = false;
|
|
|
|
|
|
dropTimes++;
|
2026-03-12 14:57:34 +08:00
|
|
|
|
LogUtil.log(TAG_LOG, "复降第:" + dropTimes + "次");
|
2026-01-30 11:47:32 +08:00
|
|
|
|
}
|
2026-02-01 14:34:23 +08:00
|
|
|
|
} else {
|
2026-03-12 14:57:34 +08:00
|
|
|
|
LogUtil.log(TAG_LOG, "执行位移");
|
2026-02-01 14:34:23 +08:00
|
|
|
|
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, -0.3f);
|
2026-01-30 11:47:32 +08:00
|
|
|
|
}
|
2026-02-01 14:34:23 +08:00
|
|
|
|
} else if (lostDuration > 8000) {
|
2026-03-12 14:57:34 +08:00
|
|
|
|
LogUtil.log(TAG_LOG, "判定未识别到二维码,飞往备降点");
|
2026-02-01 14:34:23 +08:00
|
|
|
|
AlternateLandingManager.getInstance().startTaskProcess(null);
|
2026-01-30 11:47:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-12 14:57:34 +08:00
|
|
|
|
// 原有资源释放
|
|
|
|
|
|
enhancedMat.release();
|
2026-01-30 11:47:32 +08:00
|
|
|
|
grayImgMat.release();
|
2026-02-01 14:34:23 +08:00
|
|
|
|
rgbMat.release();
|
|
|
|
|
|
yuvMat.release();
|
|
|
|
|
|
ids.release();
|
|
|
|
|
|
|
|
|
|
|
|
if (!corners.isEmpty()) {
|
|
|
|
|
|
for (Mat c : corners) {
|
|
|
|
|
|
if (c != null) c.release();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-30 11:47:32 +08:00
|
|
|
|
isStartAruco = false;
|
2026-03-12 14:57:34 +08:00
|
|
|
|
|
2026-01-30 11:47:32 +08:00
|
|
|
|
} catch (Exception e) {
|
2026-03-12 14:57:34 +08:00
|
|
|
|
LogUtil.log(TAG_LOG, "Exception" + e);
|
2026-01-30 11:47:32 +08:00
|
|
|
|
isStartAruco = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-01 14:34:23 +08:00
|
|
|
|
}, 0, TimeUnit.MILLISECONDS);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-12 14:57:34 +08:00
|
|
|
|
// ========== 以下全部原有方法,一点不改 ==========
|
2026-02-01 14:34:23 +08:00
|
|
|
|
|
|
|
|
|
|
private static MatOfInt keepOnly6(MatOfInt ids, List<Mat> corners) {
|
|
|
|
|
|
if (ids.empty()) return ids;
|
|
|
|
|
|
int[] idArr = ids.toArray();
|
|
|
|
|
|
List<Integer> keepIdx = new ArrayList<>();
|
|
|
|
|
|
for (int i = 0; i < idArr.length; i++) if (idArr[i] == 6) keepIdx.add(i);
|
|
|
|
|
|
|
|
|
|
|
|
List<Mat> tmpCorners = new ArrayList<>(keepIdx.size());
|
|
|
|
|
|
for (int i : keepIdx) tmpCorners.add(corners.get(i));
|
|
|
|
|
|
|
|
|
|
|
|
corners.clear();
|
|
|
|
|
|
ids.release();
|
|
|
|
|
|
int[] newIds = new int[keepIdx.size()];
|
|
|
|
|
|
for (int j = 0; j < keepIdx.size(); j++) newIds[j] = 6;
|
2026-03-12 14:57:34 +08:00
|
|
|
|
corners.addAll(tmpCorners);
|
2026-02-01 14:34:23 +08:00
|
|
|
|
return new MatOfInt(newIds);
|
|
|
|
|
|
}
|
2026-01-30 11:47:32 +08:00
|
|
|
|
|
2026-03-12 14:57:34 +08:00
|
|
|
|
// ========== 【极致容错】图像增强:全高度段通用 ==========
|
|
|
|
|
|
private Mat createEnhancedImage(Mat src) {
|
2026-02-01 14:34:23 +08:00
|
|
|
|
Mat result = new Mat();
|
|
|
|
|
|
try {
|
2026-03-12 14:57:34 +08:00
|
|
|
|
// 1. 多尺度CLAHE(适应不同亮度)
|
|
|
|
|
|
Mat claheMat = new Mat();
|
|
|
|
|
|
Imgproc.createCLAHE(2.0, new Size(8, 8)).apply(src, claheMat);
|
2026-02-01 14:34:23 +08:00
|
|
|
|
|
2026-03-12 14:57:34 +08:00
|
|
|
|
// 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更敏感
|
2026-02-01 14:34:23 +08:00
|
|
|
|
filtered.release();
|
|
|
|
|
|
|
2026-03-12 14:57:34 +08:00
|
|
|
|
// 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();
|
2026-02-01 14:34:23 +08:00
|
|
|
|
|
2026-03-12 14:57:34 +08:00
|
|
|
|
// 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();
|
2026-02-01 14:34:23 +08:00
|
|
|
|
blurred.release();
|
|
|
|
|
|
|
2026-03-12 14:57:34 +08:00
|
|
|
|
return sharpened;
|
2026-02-01 14:34:23 +08:00
|
|
|
|
|
2026-03-12 14:57:34 +08:00
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
LogUtil.log(TAG_LOG, "增强失败: " + e.getMessage());
|
2026-02-01 14:34:23 +08:00
|
|
|
|
src.copyTo(result);
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
2026-01-30 11:47:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-12 14:57:34 +08:00
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-30 11:47:32 +08:00
|
|
|
|
private double calculateDistance(Point p1, Point p2) {
|
|
|
|
|
|
double dx = p2.x - p1.x;
|
|
|
|
|
|
double dy = p2.y - p1.y;
|
|
|
|
|
|
return Math.sqrt(dx * dx + dy * dy);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void setDetectedBigMarkers() {
|
2026-03-12 14:57:34 +08:00
|
|
|
|
startFastStick = false;
|
|
|
|
|
|
isStartAruco = false;
|
|
|
|
|
|
consecutiveTriggerCount = 0;
|
|
|
|
|
|
frameCounter = 0; // 【新增】重置帧计数
|
2026-01-30 11:47:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-01 14:34:23 +08:00
|
|
|
|
|
2026-03-12 14:57:34 +08:00
|
|
|
|
private double calculateYawErrorFromCorners(Point[] pts) {
|
|
|
|
|
|
double dxTop = pts[1].x - pts[0].x;
|
|
|
|
|
|
double dyTop = pts[1].y - pts[0].y;
|
|
|
|
|
|
double angleTop = Math.toDegrees(Math.atan2(dyTop, dxTop));
|
|
|
|
|
|
double dxBottom = pts[2].x - pts[3].x;
|
|
|
|
|
|
double dyBottom = pts[2].y - pts[3].y;
|
|
|
|
|
|
double angleBottom = Math.toDegrees(Math.atan2(dyBottom, dxBottom));
|
|
|
|
|
|
double yawError = (angleTop + angleBottom) / 2.0;
|
|
|
|
|
|
while (yawError > 180) yawError -= 360;
|
|
|
|
|
|
while (yawError < -180) yawError += 360;
|
|
|
|
|
|
return yawError;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void moveOnArucoDetected(ArucoMarker marker, int imageWidth, int imageHeight) {
|
|
|
|
|
|
Mat corner6 = marker.getConner();
|
2026-02-01 14:34:23 +08:00
|
|
|
|
Point[] pts = new Point[4];
|
|
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
|
|
|
|
double[] p = corner6.get(0, i);
|
|
|
|
|
|
pts[i] = new Point(p[0], p[1]);
|
2026-01-30 11:47:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-12 14:57:34 +08:00
|
|
|
|
double cx = (pts[0].x + pts[1].x + pts[2].x + pts[3].x) / 4.0 + LENS_OFFSET_X;
|
|
|
|
|
|
double cy = (pts[0].y + pts[1].y + pts[2].y + pts[3].y) / 4.0 + LENS_OFFSET_Y;
|
2026-02-01 14:34:23 +08:00
|
|
|
|
Point screenCenter = new Point(imageWidth / 2.0, imageHeight / 2.0);
|
2026-01-30 11:47:32 +08:00
|
|
|
|
|
2026-02-27 10:18:56 +08:00
|
|
|
|
double errX = (cx - screenCenter.x);
|
|
|
|
|
|
double errY = (cy - screenCenter.y);
|
2026-03-12 14:57:34 +08:00
|
|
|
|
int currentHeight = Movement.getInstance().getUltrasonicHeight();
|
2026-01-30 11:47:32 +08:00
|
|
|
|
|
2026-03-12 14:57:34 +08:00
|
|
|
|
double scaleFactor;
|
|
|
|
|
|
if (currentHeight >= 50) {
|
|
|
|
|
|
scaleFactor = 300.0; // 50m: 像素3, 大力修正
|
|
|
|
|
|
} else if (currentHeight >= 30) {
|
|
|
|
|
|
scaleFactor = 500.0; // 30m: 像素5
|
|
|
|
|
|
} else if (currentHeight >= 20) {
|
|
|
|
|
|
scaleFactor = 600.0; // 20m: 像素8
|
|
|
|
|
|
} else if (currentHeight >= 10) {
|
|
|
|
|
|
scaleFactor = 700.0; // 10m: 像素16
|
|
|
|
|
|
} else {
|
|
|
|
|
|
scaleFactor = 900.0; // 1m: 像素159, 温柔修正
|
|
|
|
|
|
}
|
2026-02-27 10:18:56 +08:00
|
|
|
|
|
2026-03-12 14:57:34 +08:00
|
|
|
|
pidControlX.setInputFilterAll((float) (errX / scaleFactor));
|
|
|
|
|
|
pidControlY.setInputFilterAll((float) (-errY / scaleFactor));
|
2026-02-27 10:18:56 +08:00
|
|
|
|
|
|
|
|
|
|
float rawVx = pidControlX.get_pid();
|
|
|
|
|
|
float rawVy = pidControlY.get_pid();
|
2026-03-12 14:57:34 +08:00
|
|
|
|
float yawRate = 0f;
|
2026-02-27 10:18:56 +08:00
|
|
|
|
|
2026-03-12 14:57:34 +08:00
|
|
|
|
float vx = (float) Math.max(-0.2, Math.min(0.2, rawVx));
|
|
|
|
|
|
float vy = (float) Math.max(-0.2, Math.min(0.2, rawVy));
|
2026-02-27 10:18:56 +08:00
|
|
|
|
|
2026-02-01 14:34:23 +08:00
|
|
|
|
double pixelWidth = Math.sqrt(Math.pow(pts[1].x - pts[0].x, 2) +
|
|
|
|
|
|
Math.pow(pts[1].y - pts[0].y, 2));
|
2026-01-30 11:47:32 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-12 14:57:34 +08:00
|
|
|
|
if (currentHeight >= 10 && currentHeight <= 55) {
|
|
|
|
|
|
double yawError = calculateYawErrorFromCorners(pts);
|
|
|
|
|
|
double absError = Math.abs(yawError);
|
2026-01-30 11:47:32 +08:00
|
|
|
|
|
2026-03-12 14:57:34 +08:00
|
|
|
|
if (absError > 10.0) {
|
|
|
|
|
|
float targetRate;
|
|
|
|
|
|
if (absError > 100.0) targetRate = 50.0f;
|
|
|
|
|
|
else if (absError > 50.0) targetRate = 30.0f;
|
|
|
|
|
|
else if (absError > 30.0) targetRate = 20.0f;
|
|
|
|
|
|
else targetRate = 10.0f;
|
|
|
|
|
|
yawRate = yawError > 0 ? targetRate : -targetRate;
|
2026-01-30 11:47:32 +08:00
|
|
|
|
|
2026-03-12 14:57:34 +08:00
|
|
|
|
String speedLabel = absError > 100 ? "高速50" :
|
|
|
|
|
|
absError > 50 ? "中速30" :
|
|
|
|
|
|
absError > 30 ? "低速20" : "微调10";
|
|
|
|
|
|
LogUtil.log(TAG_LOG, "机头矫正:误差=" + (int)yawError + "° 转速=" + yawRate + "°/s [" + speedLabel + "]");
|
|
|
|
|
|
} else {
|
|
|
|
|
|
yawRate = 0.0f;
|
|
|
|
|
|
LogUtil.log(TAG_LOG, "机头已对准:偏航误差=" + (int)yawError + "°");
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
yawRate = 0.0f;
|
|
|
|
|
|
}
|
2026-01-30 11:47:32 +08:00
|
|
|
|
|
2026-03-12 14:57:34 +08:00
|
|
|
|
float vz;
|
|
|
|
|
|
if (currentHeight <= 4) {
|
|
|
|
|
|
vz = 0.0f;
|
|
|
|
|
|
if (Math.abs(errX) > 120) {
|
|
|
|
|
|
vx = rawVx > 0 ? 0.135f : -0.135f;
|
|
|
|
|
|
} else if (Math.abs(errX) > 80) {
|
|
|
|
|
|
vx = rawVx > 0 ? 0.09f : -0.09f;
|
|
|
|
|
|
} else if (Math.abs(errX) > 60) {
|
|
|
|
|
|
vx = rawVx > 0 ? 0.07f : -0.07f;
|
|
|
|
|
|
} else if (Math.abs(errX) > 30) {
|
|
|
|
|
|
vx = rawVx > 0 ? 0.05f : -0.05f;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
vx = 0f;
|
|
|
|
|
|
}
|
2026-01-30 11:47:32 +08:00
|
|
|
|
|
2026-03-12 14:57:34 +08:00
|
|
|
|
if (Math.abs(errY) > 120) {
|
|
|
|
|
|
vy = rawVy > 0 ? 0.135f : -0.135f;
|
|
|
|
|
|
} else if (Math.abs(errY) > 80) {
|
|
|
|
|
|
vy = rawVy > 0 ? 0.09f : -0.09f;
|
|
|
|
|
|
} else if (Math.abs(errY) > 60) {
|
|
|
|
|
|
vy = rawVy > 0 ? 0.07f : -0.07f;
|
|
|
|
|
|
} else if (Math.abs(errY) > 30) {
|
|
|
|
|
|
vy = rawVy > 0 ? 0.05f : -0.05f;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
vy = 0f; // 【修正】
|
|
|
|
|
|
}
|
2026-01-30 11:47:32 +08:00
|
|
|
|
|
2026-03-12 14:57:34 +08:00
|
|
|
|
} else if (currentHeight <= 8) {
|
|
|
|
|
|
vz = SLOW_SUPER_SPEED;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
vz = SLOW_LAND_SPEED;
|
|
|
|
|
|
}
|
2026-01-30 11:47:32 +08:00
|
|
|
|
|
2026-03-12 14:57:34 +08:00
|
|
|
|
DroneHelper.getInstance().moveVxVyYawrateHeight(vx, vy, yawRate, vz);
|
2026-01-30 11:47:32 +08:00
|
|
|
|
|
2026-03-12 14:57:34 +08:00
|
|
|
|
LogUtil.log(TAG_LOG, "vx" + vx + "vy" + vy + " errX=" + (int) errX + " errY=" + (int) errY +
|
|
|
|
|
|
" pixelW=" + (int) pixelWidth + " vz=" + vz + " ult=" + currentHeight + " yaw=" + yawRate);
|
2026-01-30 11:47:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-12 14:57:34 +08:00
|
|
|
|
private int handlerCallbackCount = 0;
|
2026-01-30 11:47:32 +08:00
|
|
|
|
private Handler handler = new Handler(Looper.getMainLooper());
|
|
|
|
|
|
private Runnable runnable = new Runnable() {
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public void run() {
|
|
|
|
|
|
performOperation();
|
2026-03-12 14:57:34 +08:00
|
|
|
|
if (handlerCallbackCount < 20) {
|
|
|
|
|
|
handler.postDelayed(this, 50);
|
2026-01-30 11:47:32 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
performNextStep();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
private void performOperation() {
|
2026-03-12 14:57:34 +08:00
|
|
|
|
LogUtil.log(TAG_LOG, "快速下拉中..." + handlerCallbackCount);
|
2026-01-30 11:47:32 +08:00
|
|
|
|
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, -4);
|
2026-03-12 14:57:34 +08:00
|
|
|
|
handlerCallbackCount++;
|
2026-01-30 11:47:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void performNextStep() {
|
|
|
|
|
|
canLanding = true;
|
|
|
|
|
|
handlerCallbackCount = 0;
|
2026-03-12 14:57:34 +08:00
|
|
|
|
dropTimes = 0;
|
|
|
|
|
|
handler.removeCallbacks(runnable);
|
2026-01-30 11:47:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|