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

595 lines
25 KiB
Java
Raw Normal View History

2026-01-30 11:47:32 +08:00
package com.aros.apron.tools;
import android.os.Handler;
import android.os.Looper;
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;
import dji.sdk.keyvalue.key.FlightControllerKey;
import dji.sdk.keyvalue.key.KeyTools;
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跳过
2026-04-03 20:41:05 +08:00
* 先预处理检测失败再回退原图检测
2026-03-12 14:57:34 +08:00
*/
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
// ========== 原有状态 ==========
2026-04-03 20:41:05 +08:00
private boolean isTriggerSuccess;
2026-03-12 14:57:34 +08:00
private boolean arucoNotFoundTag = false;
2026-02-01 14:34:23 +08:00
private boolean isStartAruco = false;
2026-04-03 20:41:05 +08:00
public boolean isStartAruco() {
return isStartAruco;
}
public void setStartAruco(boolean startAruco) {
isStartAruco = startAruco;
}
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);
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-04-03 20:41:05 +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-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-04-03 20:41:05 +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-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-04-03 20:41:05 +08:00
Mat corner6 = new Mat();
2026-02-01 14:34:23 +08:00
2026-04-03 20:41:05 +08:00
DetectorParameters parameters = new DetectorParameters();
ArucoDetector detector = new ArucoDetector(dictionary, parameters);
2026-02-01 14:34:23 +08:00
2026-04-03 20:41:05 +08:00
// ========== 【关键修改】第1次预处理图检测 ==========
Mat processedMat = fixedPreprocess(grayImgMat);
detector.detectMarkers(processedMat, corners, ids);
LogUtil.log(TAG_LOG, "预处理图检测: " + (ids.empty() ? "未检出" : "成功,数量=" + corners.size()));
// ========== 【关键修改】第2次如果失败回退原图检测 ==========
2026-02-01 14:34:23 +08:00
if (ids.empty()) {
2026-04-03 20:41:05 +08:00
LogUtil.log(TAG_LOG, "预处理失败,回退原图检测");
corners.clear(); // 清空之前的结果
2026-02-01 14:34:23 +08:00
detector.detectMarkers(grayImgMat, corners, ids);
2026-04-03 20:41:05 +08:00
LogUtil.log(TAG_LOG, "原图检测: " + (ids.empty() ? "仍未检出" : "成功,数量=" + corners.size()));
2026-02-01 14:34:23 +08:00
}
2026-04-03 20:41:05 +08:00
// 释放预处理图(必须!避免内存泄漏)
processedMat.release();
2026-03-12 14:57:34 +08:00
2026-04-03 20:41:05 +08:00
// 只保留6号码
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;
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);
return;
}
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);
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-04-03 20:41:05 +08:00
Movement.getInstance().setAlternate(true);
2026-01-30 11:47:32 +08:00
}
}
2026-04-03 20:41:05 +08:00
// 释放资源
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-04-03 20:41:05 +08:00
/**
* 简化预处理双边滤波+CLAHE+高反差保留
* 参数别改适配H30T广角画质
*/
/**
* 增强版预处理双边滤波 + CLAHE + Unsharp Mask反锐化掩膜
* 参数别改amount=1.2 FPV 运动模糊的推荐强度
*/
private Mat fixedPreprocess(Mat src) {
2026-02-01 14:34:23 +08:00
Mat result = new Mat();
try {
2026-04-03 20:41:05 +08:00
// 【新增】判断亮度,区分白天/夜景
Scalar meanValue = Core.mean(src);
double meanBrightness = meanValue.val[0];
if (meanBrightness < 80) { // 夜景模式FPV晚上通常<80
LogUtil.log(TAG_LOG, "【夜景预处理】亮度=" + (int)meanBrightness);
// Step 1: 中值滤波(去椒盐噪点,比双边滤波更适合夜景)
Mat denoised = new Mat();
Imgproc.medianBlur(src, denoised, 5); // 5x5去噪且保边
// Step 2: 直方图均衡化(全局对比度拉伸,把黑灰白拉开)
Mat equalized = new Mat();
Imgproc.equalizeHist(denoised, equalized);
// Step 3: 轻度CLAHE局部微调避免过曝
Mat clahe = new Mat();
Imgproc.createCLAHE(2.0, new Size(8, 8)).apply(equalized, clahe); // 2.0比白天4.0保守
// Step 4: 夜景下降低锐化强度(避免放大残余噪点)
Mat blurred = new Mat();
Imgproc.GaussianBlur(clahe, blurred, new Size(0, 0), 2.0);
Mat detail = new Mat();
Core.subtract(clahe, blurred, detail);
double amount = 0.6; // 【关键】夜景降到0.6白天1.2会放大噪点
Core.multiply(detail, new Scalar(amount), detail);
Core.add(clahe, detail, result);
// 限制范围
Core.min(result, new Scalar(255), result);
Core.max(result, new Scalar(0), result);
// 释放
denoised.release();
equalized.release();
blurred.release();
detail.release();
clahe.release();
LogUtil.log(TAG_LOG, "夜景处理完成:中值滤波+均衡化+弱锐化(amount=" + amount + ")");
return result;
}
// 【原逻辑】白天模式(亮度>=80
LogUtil.log(TAG_LOG, "【白天预处理】亮度=" + (int)meanBrightness);
2026-02-01 14:34:23 +08:00
2026-04-03 20:41:05 +08:00
// Step 1: 双边滤波(保边缘降噪)
2026-03-12 14:57:34 +08:00
Mat filtered = new Mat();
2026-04-03 20:41:05 +08:00
Imgproc.bilateralFilter(src, filtered, 9, 75, 75);
2026-02-01 14:34:23 +08:00
2026-04-03 20:41:05 +08:00
// Step 2: CLAHE 局部对比度增强
Mat clahe = new Mat();
Imgproc.createCLAHE(4.0, new Size(8, 8)).apply(filtered, clahe);
filtered.release();
2026-02-01 14:34:23 +08:00
2026-04-03 20:41:05 +08:00
// Step 3: Unsharp Mask反锐化掩膜
2026-03-12 14:57:34 +08:00
Mat blurred = new Mat();
2026-04-03 20:41:05 +08:00
Imgproc.GaussianBlur(clahe, blurred, new Size(0, 0), 2.0);
Mat detail = new Mat();
Core.subtract(clahe, blurred, detail);
double amount = 1.2; // 白天可以用1.2
Core.multiply(detail, new Scalar(amount), detail);
Core.add(clahe, detail, result);
Core.min(result, new Scalar(255), result);
Core.max(result, new Scalar(0), result);
2026-02-01 14:34:23 +08:00
blurred.release();
2026-04-03 20:41:05 +08:00
detail.release();
clahe.release();
2026-02-01 14:34:23 +08:00
2026-04-03 20:41:05 +08:00
LogUtil.log(TAG_LOG, "白天处理完成:双边滤波+CLAHE+UnsharpMask(amount=" + amount + ")");
return result;
2026-02-01 14:34:23 +08:00
2026-03-12 14:57:34 +08:00
} catch (Exception e) {
2026-04-03 20:41:05 +08:00
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
}
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
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-03-12 14:57:34 +08:00
pidControlX.setInputFilterAll((float) (errX / scaleFactor));
pidControlY.setInputFilterAll((float) (-errY / scaleFactor));
float rawVx = pidControlX.get_pid();
float rawVy = pidControlY.get_pid();
2026-03-12 14:57:34 +08:00
float yawRate = 0f;
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-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-04-03 20:41:05 +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-04-03 20:41:05 +08:00
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, -5);
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
}
}