融合降落
This commit is contained in:
parent
1b0de5e340
commit
b601be0e49
|
|
@ -0,0 +1,670 @@
|
|||
package com.aros.apron.mix;
|
||||
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 com.aros.apron.tools.DroneHelper;
|
||||
import com.aros.apron.tools.LogUtil;
|
||||
import com.aros.apron.tools.PIDControl;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* 2帧一处理:0处理-1跳过-2处理-3跳过
|
||||
* 先预处理检测,失败再回退原图检测
|
||||
*/
|
||||
public class Aprondown {
|
||||
|
||||
private static final String TAG = "Aprondown";
|
||||
|
||||
// ========== 原有参数 ==========
|
||||
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;
|
||||
|
||||
// ========== 原有状态 ==========
|
||||
private boolean isTriggerSuccess;
|
||||
private boolean arucoNotFoundTag = false;
|
||||
private boolean isStartAruco = false;
|
||||
|
||||
public boolean isStartAruco() {
|
||||
return isStartAruco;
|
||||
}
|
||||
|
||||
public void setStartAruco(boolean startAruco) {
|
||||
isStartAruco = startAruco;
|
||||
}
|
||||
|
||||
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
|
||||
Future<?> lastFuture = null;
|
||||
private String TAG_LOG = getClass().getSimpleName();
|
||||
Double resultYaw = 0.0;
|
||||
private boolean triggerToAlternateLandingPoint;
|
||||
long startTime;
|
||||
long endTime;
|
||||
private int sigleMarkerDetectFailsTimes;
|
||||
private boolean dropTimesTag;
|
||||
private int dropTimes = 0;
|
||||
|
||||
public int getDropTimes() {
|
||||
return dropTimes;
|
||||
}
|
||||
|
||||
public void setDropTimes(int dropTimes) {
|
||||
this.dropTimes = dropTimes;
|
||||
}
|
||||
|
||||
private boolean isDoublePayload;
|
||||
private int trycount = 0;
|
||||
|
||||
public PIDControl pidControlX = null;
|
||||
public PIDControl pidControlY = null;
|
||||
public int checkThrowingErrorsTimes;
|
||||
private int consecutiveTriggerCount = 0;
|
||||
|
||||
// ========== 【新增】2帧一处理计数器 ==========
|
||||
private int frameCounter = 0;
|
||||
|
||||
private long lastHeightCheckTime = 0;
|
||||
private double lastUltrasonicHeight = 0;
|
||||
private static final long HEIGHT_STABLE_THRESHOLD = 45000; // 45秒
|
||||
private static final double HEIGHT_CHANGE_THRESHOLD = 1; // 10厘米
|
||||
private boolean isHeightStableMonitoring = false;
|
||||
|
||||
// ========== 原有方法 ==========
|
||||
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;
|
||||
}
|
||||
|
||||
private Aprondown() {}
|
||||
private static class OpenCVHelperHolder {
|
||||
private static final com.aros.apron.mix.Aprondown INSTANCE = new com.aros.apron.mix.Aprondown();
|
||||
static { INSTANCE.init(); }
|
||||
}
|
||||
public static com.aros.apron.mix.Aprondown getInstance() { return com.aros.apron.mix.Aprondown.OpenCVHelperHolder.INSTANCE; }
|
||||
|
||||
public void init() {
|
||||
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.reset();
|
||||
pidControlY.reset();
|
||||
}
|
||||
|
||||
public boolean startFastStick;
|
||||
public boolean canLanding;
|
||||
|
||||
// ========== 【核心修改】主入口:2帧一处理 + 预处理回退 ==========
|
||||
public void detectArucoTags(int height, int width, byte[] data, Dictionary dictionary) {
|
||||
isTriggerSuccess = true;
|
||||
Movement.getInstance().setVirtualStickEnableReason(2);
|
||||
|
||||
// 原有过滤
|
||||
if (isStartAruco || startFastStick) {
|
||||
LogUtil.log(TAG_LOG, "过滤:" + isStartAruco + startFastStick);
|
||||
if (!isStartAruco && startFastStick) {
|
||||
checkThrowingErrorsTimes++;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// ========== 【关键】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 + " 执行修正");
|
||||
|
||||
isStartAruco = true;
|
||||
|
||||
if (lastFuture != null && !lastFuture.isDone()) {
|
||||
LogUtil.log(TAG_LOG, "break---");
|
||||
lastFuture.cancel(true);
|
||||
}
|
||||
|
||||
// 动态偏移(原有)
|
||||
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; }
|
||||
|
||||
// ========== 【核心逻辑】先预处理检测,失败回退原图 ==========
|
||||
lastFuture = executor.schedule(new Runnable() {
|
||||
@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();
|
||||
Imgproc.cvtColor(rgbMat, grayImgMat, Imgproc.COLOR_BGR2GRAY);
|
||||
|
||||
MatOfInt ids = new MatOfInt();
|
||||
List<Mat> corners = new ArrayList<>();
|
||||
Mat corner6 = new Mat();
|
||||
|
||||
DetectorParameters parameters = new DetectorParameters();
|
||||
ArucoDetector detector = new ArucoDetector(dictionary, parameters);
|
||||
|
||||
// ========== 【关键修改】第1次:预处理图检测 ==========
|
||||
Mat processedMat = fixedPreprocess(grayImgMat);
|
||||
detector.detectMarkers(processedMat, corners, ids);
|
||||
LogUtil.log(TAG_LOG, "预处理图检测: " + (ids.empty() ? "未检出" : "成功,数量=" + corners.size()));
|
||||
|
||||
// ========== 【关键修改】第2次:如果失败,回退原图检测 ==========
|
||||
if (ids.empty()) {
|
||||
LogUtil.log(TAG_LOG, "预处理失败,回退原图检测");
|
||||
corners.clear(); // 清空之前的结果
|
||||
detector.detectMarkers(grayImgMat, corners, ids);
|
||||
LogUtil.log(TAG_LOG, "原图检测: " + (ids.empty() ? "仍未检出" : "成功,数量=" + corners.size()));
|
||||
}
|
||||
|
||||
// 释放预处理图(必须!避免内存泄漏)
|
||||
processedMat.release();
|
||||
|
||||
// 只保留6号码
|
||||
ids = keepOnly6(ids, corners);
|
||||
|
||||
boolean marker6Found = false;
|
||||
|
||||
int ultrasonicHeight = Movement.getInstance().getUltrasonicHeight();
|
||||
|
||||
// ========== 【新增】高度稳定性监测 ==========
|
||||
if (ultrasonicHeight > 0) {
|
||||
if (!isHeightStableMonitoring) {
|
||||
// 开始监测
|
||||
isHeightStableMonitoring = true;
|
||||
lastHeightCheckTime = System.currentTimeMillis();
|
||||
lastUltrasonicHeight = ultrasonicHeight;
|
||||
LogUtil.log(TAG_LOG, "fpv开始监测高度稳定性,基准高度: " + ultrasonicHeight + " 分米");
|
||||
} else {
|
||||
// 检查高度变化
|
||||
long currentTime = System.currentTimeMillis();
|
||||
double heightChange = Math.abs(ultrasonicHeight - lastUltrasonicHeight);
|
||||
|
||||
if (heightChange > HEIGHT_CHANGE_THRESHOLD) {
|
||||
// 高度有明显变化,重置计时器
|
||||
lastHeightCheckTime = currentTime;
|
||||
lastUltrasonicHeight = ultrasonicHeight;
|
||||
//LogUtil.log(TAG_LOG, "高度变化: " + heightChange + " 分米,重置计时器");
|
||||
} else if (currentTime - lastHeightCheckTime > HEIGHT_STABLE_THRESHOLD) {
|
||||
// 45秒内高度没有变动,执行拉高切换
|
||||
LogUtil.log(TAG_LOG, "45秒内高度未变动,执行拉高切换到云台");
|
||||
|
||||
Apronmixvalue.getInstance().inchangeTrytimes();
|
||||
if(Apronmixvalue.getInstance().getTrytimes()>5){
|
||||
AlternateLandingManager.getInstance().startTaskProcess(null);
|
||||
Movement.getInstance().setAlternate(true);
|
||||
return;
|
||||
}
|
||||
Apronmixvalue.getInstance().switchthemmodeGime();
|
||||
Apronmixvalue.getInstance().pullup();
|
||||
|
||||
// 重置监测状态
|
||||
isHeightStableMonitoring = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if (!ids.empty()) {
|
||||
trycount = 0;
|
||||
arucoNotFoundTag = false;
|
||||
int[] idArray = ids.toArray();
|
||||
|
||||
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]);
|
||||
|
||||
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);
|
||||
|
||||
LogUtil.log(TAG_LOG, "像素" + (int) pixelWidth +
|
||||
" errX=" + (int) errX + " errY=" + (int) errY + " (含偏移" + LENS_OFFSET_X + ")");
|
||||
|
||||
// 速降判断(原有)
|
||||
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;
|
||||
}
|
||||
} else {
|
||||
if (consecutiveTriggerCount > 0) {
|
||||
LogUtil.log(TAG_LOG, "速降条件中断,重置累计帧数(原因:像素=" + (int)pixelWidth +
|
||||
" 超声=" + ultrasonicHeight + " errX=" + (int)errX + " errY=" + (int)errY + ")");
|
||||
consecutiveTriggerCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 原有修正逻辑
|
||||
if (marker6Found) {
|
||||
moveOnArucoDetected(new ArucoMarker(1, corner6, 0.24f), rgbMat.width(), rgbMat.height());
|
||||
}
|
||||
dropTimesTag = true;
|
||||
|
||||
} else {
|
||||
// 原有丢失处理
|
||||
LogUtil.log(TAG_LOG, "找不到了二维码");
|
||||
if (!arucoNotFoundTag) {
|
||||
startTime = System.currentTimeMillis();
|
||||
arucoNotFoundTag = true;
|
||||
}
|
||||
endTime = System.currentTimeMillis();
|
||||
long lostDuration = endTime - startTime;
|
||||
|
||||
if (lostDuration > 1000 && lostDuration <= 12000) {
|
||||
if (Movement.getInstance().getUltrasonicHeight() <= 20) {
|
||||
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, 3f);
|
||||
if (dropTimes > 1) {
|
||||
LogUtil.log(TAG_LOG, "超过复降限制,去备降点");
|
||||
//切换
|
||||
|
||||
Apronmixvalue.getInstance().inchangeTrytimes();
|
||||
if(Apronmixvalue.getInstance().getTrytimes()>5){
|
||||
AlternateLandingManager.getInstance().startTaskProcess(null);
|
||||
Movement.getInstance().setAlternate(true);
|
||||
return;
|
||||
}
|
||||
Apronmixvalue.getInstance().switchthemmodeGime();
|
||||
Apronmixvalue.getInstance().pullup();
|
||||
|
||||
return;
|
||||
}
|
||||
if (dropTimesTag) {
|
||||
dropTimesTag = false;
|
||||
dropTimes++;
|
||||
LogUtil.log(TAG_LOG, "复降第:" + dropTimes + "次");
|
||||
}
|
||||
} else {
|
||||
LogUtil.log(TAG_LOG, "执行位移");
|
||||
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, -0.3f);
|
||||
}
|
||||
} else if (lostDuration > 8000) {
|
||||
LogUtil.log(TAG_LOG, "判定未识别到二维码,飞往备降点");
|
||||
|
||||
Apronmixvalue.getInstance().inchangeTrytimes();
|
||||
if(Apronmixvalue.getInstance().getTrytimes()>5){
|
||||
AlternateLandingManager.getInstance().startTaskProcess(null);
|
||||
Movement.getInstance().setAlternate(true);
|
||||
return;
|
||||
}
|
||||
Apronmixvalue.getInstance().switchthemmodeGime();
|
||||
Apronmixvalue.getInstance().pullup();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 释放资源
|
||||
grayImgMat.release();
|
||||
rgbMat.release();
|
||||
yuvMat.release();
|
||||
ids.release();
|
||||
|
||||
if (!corners.isEmpty()) {
|
||||
for (Mat c : corners) {
|
||||
if (c != null) c.release();
|
||||
}
|
||||
}
|
||||
isStartAruco = false;
|
||||
|
||||
} catch (Exception e) {
|
||||
LogUtil.log(TAG_LOG, "Exception" + e);
|
||||
isStartAruco = false;
|
||||
}
|
||||
}
|
||||
}, 0, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
// ========== 以下全部原有方法,一点不改 ==========
|
||||
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;
|
||||
corners.addAll(tmpCorners);
|
||||
return new MatOfInt(newIds);
|
||||
}
|
||||
|
||||
private Mat fixedPreprocess(Mat src) {
|
||||
Mat result = new Mat();
|
||||
try {
|
||||
// 【新增】判断亮度,区分白天/夜景
|
||||
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);
|
||||
|
||||
// Step 1: 双边滤波(保边缘降噪)
|
||||
Mat filtered = new Mat();
|
||||
Imgproc.bilateralFilter(src, filtered, 9, 75, 75);
|
||||
|
||||
// Step 2: CLAHE 局部对比度增强
|
||||
Mat clahe = new Mat();
|
||||
Imgproc.createCLAHE(4.0, new Size(8, 8)).apply(filtered, clahe);
|
||||
filtered.release();
|
||||
|
||||
// Step 3: Unsharp Mask(反锐化掩膜)
|
||||
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 = 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);
|
||||
|
||||
blurred.release();
|
||||
detail.release();
|
||||
clahe.release();
|
||||
|
||||
LogUtil.log(TAG_LOG, "白天处理完成:双边滤波+CLAHE+UnsharpMask(amount=" + amount + ")");
|
||||
return result;
|
||||
|
||||
} catch (Exception e) {
|
||||
LogUtil.log(TAG_LOG, "预处理失败: " + e.getMessage());
|
||||
src.copyTo(result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
startFastStick = false;
|
||||
isStartAruco = false;
|
||||
consecutiveTriggerCount = 0;
|
||||
frameCounter = 0; // 【新增】重置帧计数
|
||||
}
|
||||
|
||||
|
||||
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();
|
||||
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]);
|
||||
}
|
||||
|
||||
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;
|
||||
Point screenCenter = new Point(imageWidth / 2.0, imageHeight / 2.0);
|
||||
|
||||
double errX = (cx - screenCenter.x);
|
||||
double errY = (cy - screenCenter.y);
|
||||
int currentHeight = Movement.getInstance().getUltrasonicHeight();
|
||||
|
||||
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, 温柔修正
|
||||
}
|
||||
|
||||
pidControlX.setInputFilterAll((float) (errX / scaleFactor));
|
||||
pidControlY.setInputFilterAll((float) (-errY / scaleFactor));
|
||||
|
||||
float rawVx = pidControlX.get_pid();
|
||||
float rawVy = pidControlY.get_pid();
|
||||
float yawRate = 0f;
|
||||
|
||||
float vx = (float) Math.max(-0.2, Math.min(0.2, rawVx));
|
||||
float vy = (float) Math.max(-0.2, Math.min(0.2, rawVy));
|
||||
|
||||
double pixelWidth = Math.sqrt(Math.pow(pts[1].x - pts[0].x, 2) +
|
||||
Math.pow(pts[1].y - pts[0].y, 2));
|
||||
if(Apronmixvalue.getInstance().isIsaglinetrue()==false){
|
||||
if (currentHeight >= 10 && currentHeight <= 55) {
|
||||
double yawError = calculateYawErrorFromCorners(pts);
|
||||
double absError = Math.abs(yawError);
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
Apronmixvalue.getInstance().setIsaglinetrue(true);
|
||||
|
||||
LogUtil.log(TAG_LOG, "机头已对准:偏航误差=" + (int)yawError + "°");
|
||||
}
|
||||
} else {
|
||||
yawRate = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
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; // 【修正】
|
||||
}
|
||||
|
||||
} else if (currentHeight <= 8) {
|
||||
vz = SLOW_SUPER_SPEED;
|
||||
} else {
|
||||
vz = SLOW_LAND_SPEED;
|
||||
}
|
||||
|
||||
DroneHelper.getInstance().moveVxVyYawrateHeight(vx, vy, yawRate, vz);
|
||||
|
||||
LogUtil.log(TAG_LOG, "vx" + vx + "vy" + vy + " errX=" + (int) errX + " errY=" + (int) errY +
|
||||
" pixelW=" + (int) pixelWidth + " vz=" + vz + " ult=" + currentHeight + " yaw=" + yawRate);
|
||||
}
|
||||
|
||||
private int handlerCallbackCount = 0;
|
||||
private Handler handler = new Handler(Looper.getMainLooper());
|
||||
private Runnable runnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
performOperation();
|
||||
if (handlerCallbackCount < 20) {
|
||||
handler.postDelayed(this, 50);
|
||||
} else {
|
||||
performNextStep();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private void performOperation() {
|
||||
LogUtil.log(TAG_LOG, "快速下拉中..." + handlerCallbackCount);
|
||||
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, -5);
|
||||
handlerCallbackCount++;
|
||||
}
|
||||
|
||||
private void performNextStep() {
|
||||
canLanding = true;
|
||||
handlerCallbackCount = 0;
|
||||
dropTimes = 0;
|
||||
handler.removeCallbacks(runnable);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,920 @@
|
|||
package com.aros.apron.mix;
|
||||
|
||||
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 com.aros.apron.tools.DroneHelper;
|
||||
import com.aros.apron.tools.LogUtil;
|
||||
import com.aros.apron.tools.PIDControl;
|
||||
|
||||
import org.opencv.core.CvType;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.MatOfInt;
|
||||
import org.opencv.core.Point;
|
||||
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.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 多二维码融合版本(V2.0):
|
||||
* 1. 【关键修改】先用6号二维码进行机头旋转对准,转到位(isYawAligned=true)才进入后续阶段
|
||||
* 2. 旋转阶段(vz=0)不下降,丢失时悬停等待
|
||||
* 3. 【禁用】5号二维码逻辑已注释掉
|
||||
* 4. 旋转完成后:识别1-4号/小码进行水平对准+下降
|
||||
*/
|
||||
public class Aprongim {
|
||||
|
||||
private static final String TAG = "Aprongim";
|
||||
|
||||
// ========== 原有参数(参数别改) ==========
|
||||
private static double LENS_OFFSET_X = 0;
|
||||
private static double LENS_OFFSET_Y = 0;
|
||||
private static final int CENTER_ERR_MAX = 50;
|
||||
private static final float SLOW_LAND_SPEED = -0.3f;
|
||||
private static final float SLOW_SUPER_SPEED = -0.15f;
|
||||
private static final int PIXEL_TRIGGER = 360;
|
||||
private static final int TRIGGER_FRAME_THRESHOLD = 2;
|
||||
|
||||
// ========== 原有状态 ==========
|
||||
private boolean isTriggerSuccess;
|
||||
private boolean arucoNotFoundTag = false;
|
||||
private boolean isStartAruco = false;
|
||||
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
|
||||
Future<?> lastFuture = null;
|
||||
private String TAG_LOG = getClass().getSimpleName();
|
||||
private boolean triggerToAlternateLandingPoint;
|
||||
long startTime;
|
||||
long endTime;
|
||||
private int sigleMarkerDetectFailsTimes;
|
||||
private boolean dropTimesTag;
|
||||
private int dropTimes = 0;
|
||||
|
||||
public int getDropTimes() {
|
||||
return dropTimes;
|
||||
}
|
||||
|
||||
public void setDropTimes(int dropTimes) {
|
||||
this.dropTimes = dropTimes;
|
||||
}
|
||||
|
||||
private boolean isDoublePayload;
|
||||
private int trycount = 0;
|
||||
|
||||
public PIDControl pidControlX = null;
|
||||
public PIDControl pidControlY = null;
|
||||
public int checkThrowingErrorsTimes;
|
||||
private int consecutiveTriggerCount = 0;
|
||||
private int frameCounter = 0;
|
||||
|
||||
// ========== 【新增】阶段控制标志 ==========
|
||||
// 偏航是否已对准(6号旋转阶段完成标志)
|
||||
private volatile boolean isYawAligned = false;
|
||||
// 当前激活的降落模式:0=旋转阶段(6号), 1=小码, 2=1-4大码, 3=6号纯下降
|
||||
private int currentLandingMode = 0;
|
||||
|
||||
// ========== 【新增】连续降落条件判断 ==========
|
||||
// 记录上一帧满足的降落条件类型:0=无, 1=单码精准降落(15/17号), 2=几何中心降落
|
||||
private int lastLandingCondition = 0;
|
||||
|
||||
// ========== 【新增】高度稳定性监测 ==========
|
||||
private long lastHeightCheckTime = 0;
|
||||
private double lastUltrasonicHeight = 0;
|
||||
private static final long HEIGHT_STABLE_THRESHOLD = 45000; // 45秒
|
||||
private static final double HEIGHT_CHANGE_THRESHOLD = 1; // 10厘米
|
||||
private boolean isHeightStableMonitoring = false;
|
||||
|
||||
// ========== 标准方法 ==========
|
||||
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; }
|
||||
|
||||
private boolean setMF=false;
|
||||
public boolean isCanLanding() { return canLanding; }
|
||||
public boolean isStartAruco() {
|
||||
return isStartAruco;
|
||||
}
|
||||
|
||||
public void setStartAruco(boolean startAruco) {
|
||||
isStartAruco = startAruco;
|
||||
}
|
||||
public void setCanLanding(boolean v) {
|
||||
startTime = 0;
|
||||
endTime = 0;
|
||||
canLanding = v;
|
||||
}
|
||||
|
||||
private Aprongim() {}
|
||||
private static class OpenCVHelperHolder {
|
||||
private static final com.aros.apron.mix.Aprongim INSTANCE = new com.aros.apron.mix.Aprongim();
|
||||
static { INSTANCE.init(); }
|
||||
}
|
||||
public static com.aros.apron.mix.Aprongim getInstance() { return com.aros.apron.mix.Aprongim.OpenCVHelperHolder.INSTANCE; }
|
||||
|
||||
public void init() {
|
||||
// 参数别改:PID初始值
|
||||
pidControlX = new PIDControl(0.7f, 0.02f, 0.22f, 0.05f, 2.5f, 0.1f);
|
||||
pidControlY = new PIDControl(0.7f, 0.02f, 0.22f, 0.05f, 2.5f, 0.1f);
|
||||
pidControlX.reset();
|
||||
pidControlY.reset();
|
||||
}
|
||||
|
||||
public boolean startFastStick;
|
||||
public boolean canLanding;
|
||||
|
||||
// ========== 【新增】计算二维码像素宽度 ==========
|
||||
private double calculatePixelWidth(Mat corner) {
|
||||
try {
|
||||
Point[] pts = new Point[4];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
double[] p = corner.get(0, i);
|
||||
pts[i] = new Point(p[0], p[1]);
|
||||
}
|
||||
// 计算上下两边长度的平均值作为像素宽度
|
||||
double top = Math.sqrt(Math.pow(pts[1].x - pts[0].x, 2) + Math.pow(pts[1].y - pts[0].y, 2));
|
||||
double bottom = Math.sqrt(Math.pow(pts[2].x - pts[3].x, 2) + Math.pow(pts[2].y - pts[3].y, 2));
|
||||
return (top + bottom) / 2.0;
|
||||
} catch (Exception e) {
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
// ========== 【核心】主入口:3帧一处理 ==========
|
||||
public void detectArucoTags(int height, int width, byte[] data, Dictionary dictionary) {
|
||||
isTriggerSuccess = true;
|
||||
Movement.getInstance().setVirtualStickEnableReason(2);
|
||||
|
||||
if (isStartAruco || startFastStick) {
|
||||
LogUtil.log(TAG_LOG, "过滤:" + isStartAruco + startFastStick);
|
||||
if (!isStartAruco && startFastStick) {
|
||||
checkThrowingErrorsTimes++;
|
||||
}
|
||||
return;
|
||||
}
|
||||
// 改为 3 帧一处理(10Hz,接近 DJI 建议上限)
|
||||
int currentFrame = frameCounter++;
|
||||
if (currentFrame % 3 != 0) { // 0处理,1、2跳过
|
||||
return;
|
||||
}
|
||||
if (currentFrame % 30 != 0) { // 0处理,1、2跳过
|
||||
//DroneHelper.getInstance().setGimbalPitchdown();
|
||||
}
|
||||
|
||||
isStartAruco = true;
|
||||
if (lastFuture != null && !lastFuture.isDone()) {
|
||||
lastFuture.cancel(true);
|
||||
}
|
||||
int ultHeight = Movement.getInstance().getUltrasonicHeight();
|
||||
|
||||
if(ultHeight <=10 && setMF==false){
|
||||
//切换mf对焦模式
|
||||
setMF=true;
|
||||
}
|
||||
if(isDoublePayload){
|
||||
if (ultHeight <=5)
|
||||
{
|
||||
LENS_OFFSET_X=-125;
|
||||
LENS_OFFSET_Y=120;
|
||||
}else if(ultHeight<10){
|
||||
LENS_OFFSET_X=-85;
|
||||
LENS_OFFSET_Y=80;
|
||||
} else if (ultHeight<20) {
|
||||
LENS_OFFSET_X=-70;
|
||||
LENS_OFFSET_Y=60;
|
||||
}
|
||||
}else{
|
||||
if (ultHeight <=5)
|
||||
{
|
||||
LENS_OFFSET_X=120;
|
||||
LENS_OFFSET_Y = 100;
|
||||
}else if(ultHeight<10){
|
||||
LENS_OFFSET_X=80;
|
||||
LENS_OFFSET_Y = 80;
|
||||
} else if (ultHeight<20) {
|
||||
LENS_OFFSET_X=60;
|
||||
LENS_OFFSET_Y = 60;
|
||||
}
|
||||
}
|
||||
|
||||
lastFuture = executor.schedule(new Runnable() {
|
||||
@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();
|
||||
Imgproc.cvtColor(rgbMat, grayImgMat, Imgproc.COLOR_BGR2GRAY);
|
||||
|
||||
MatOfInt ids = new MatOfInt();
|
||||
List<Mat> corners = new ArrayList<>();
|
||||
DetectorParameters parameters = new DetectorParameters();
|
||||
ArucoDetector detector = new ArucoDetector(dictionary, parameters);
|
||||
|
||||
detector.detectMarkers(grayImgMat, corners, ids);
|
||||
|
||||
int ultHeight=Movement.getInstance().getUltrasonicHeight();
|
||||
|
||||
// ========== 【新增】高度稳定性监测 ==========
|
||||
if (ultHeight > 0) {
|
||||
if (!isHeightStableMonitoring) {
|
||||
// 开始监测
|
||||
isHeightStableMonitoring = true;
|
||||
lastHeightCheckTime = System.currentTimeMillis();
|
||||
lastUltrasonicHeight = ultHeight;
|
||||
LogUtil.log(TAG_LOG, "port开始监测高度稳定性,基准高度: " + ultHeight + " 分米");
|
||||
} else {
|
||||
// 检查高度变化
|
||||
long currentTime = System.currentTimeMillis();
|
||||
double heightChange = Math.abs(ultHeight - lastUltrasonicHeight);
|
||||
|
||||
if (heightChange > HEIGHT_CHANGE_THRESHOLD) {
|
||||
// 高度有明显变化,重置计时器
|
||||
lastHeightCheckTime = currentTime;
|
||||
lastUltrasonicHeight = ultHeight;
|
||||
// LogUtil.log(TAG_LOG, "高度变化: " + heightChange + " 分米,重置计时器");
|
||||
} else if (currentTime - lastHeightCheckTime > HEIGHT_STABLE_THRESHOLD) {
|
||||
// 45秒内高度没有变动,执行拉高切换
|
||||
LogUtil.log(TAG_LOG, "45秒内高度未变动,执行拉高切换到下视觉");
|
||||
|
||||
Apronmixvalue.getInstance().inchangeTrytimes();
|
||||
|
||||
if(Apronmixvalue.getInstance().getTrytimes()>5){
|
||||
AlternateLandingManager.getInstance().startTaskProcess(null);
|
||||
Movement.getInstance().setAlternate(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// 切换到下视觉降落
|
||||
Apronmixvalue.getInstance().switchthemmodefpv();
|
||||
Apronmixvalue.getInstance().pullup();
|
||||
|
||||
// 重置监测状态
|
||||
isHeightStableMonitoring = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!ids.empty()) {
|
||||
trycount = 0;
|
||||
arucoNotFoundTag = false;
|
||||
|
||||
List<ArucoMarker> smallMarkers = new ArrayList<>();
|
||||
List<ArucoMarker> bigMarkers1_4 = new ArrayList<>();
|
||||
// 【禁用】5号逻辑注释掉,但保留变量避免编译错误
|
||||
// List<ArucoMarker> bigMarker5 = new ArrayList<>();
|
||||
List<ArucoMarker> bigMarker6 = new ArrayList<>();
|
||||
|
||||
int[] idArray = ids.toArray();
|
||||
List<Integer> excludeIds = isDoublePayload ?
|
||||
Arrays.asList(12, 11, 21, 14, 13, 22) :
|
||||
Arrays.asList(12, 11, 21, 20, 19, 25);
|
||||
|
||||
// 【新增】记录所有检测到的码的像素信息
|
||||
StringBuilder detectInfo = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < idArray.length; i++) {
|
||||
int id = idArray[i];
|
||||
Mat corner = corners.get(i);
|
||||
double pixelW = calculatePixelWidth(corner);
|
||||
|
||||
if (id >= 11 && id <= 25 && !excludeIds.contains(id)) {
|
||||
smallMarkers.add(new ArucoMarker(id, corner, 0.03f));
|
||||
detectInfo.append(String.format("小码%d(%.0fpx) ", id, pixelW));
|
||||
} else if (id >= 1 && id <= 4) {
|
||||
bigMarkers1_4.add(new ArucoMarker(id, corner, 0.18f));
|
||||
detectInfo.append(String.format("大码%d(%.0fpx) ", id, pixelW));
|
||||
}
|
||||
// 【禁用】5号二维码逻辑已注释
|
||||
/*
|
||||
else if (id == 5) {
|
||||
bigMarker5.add(new ArucoMarker(5, corner, 0.15f));
|
||||
detectInfo.append(String.format("5号(%.0fpx) ", pixelW));
|
||||
}
|
||||
*/
|
||||
else if (id == 6) {
|
||||
bigMarker6.add(new ArucoMarker(6, corner, 0.24f));
|
||||
detectInfo.append(String.format("6号(%.0fpx) ", pixelW));
|
||||
}
|
||||
}
|
||||
// 输出检测到的所有码及其像素宽度
|
||||
if (detectInfo.length() > 0) {
|
||||
LogUtil.log(TAG_LOG, "【检测到】" + detectInfo.toString());
|
||||
}
|
||||
|
||||
isYawAligned=Apronmixvalue.getInstance().isIsaglinetrue();
|
||||
|
||||
// ========== 【关键修改】阶段0:6号旋转阶段(最高优先级) ==========
|
||||
// 只要看到6号且还没对准,就只用6号旋转,不进入其他任何阶段
|
||||
if (!bigMarker6.isEmpty() && !isYawAligned) {
|
||||
currentLandingMode = 0;
|
||||
processMarker6YawOnly(bigMarker6.get(0), rgbMat.width(), rgbMat.height(), ultHeight);
|
||||
}
|
||||
// ========== 【关键】只有旋转完成后(isYawAligned=true),才识别其他码 ==========
|
||||
else if (isYawAligned) {
|
||||
// 优先级1:小码模式(高度<25dm且≥3个码)
|
||||
if (!smallMarkers.isEmpty() && ultHeight < 25) {
|
||||
currentLandingMode = 1;
|
||||
processSmallMarkers(smallMarkers, rgbMat.width(), rgbMat.height(), ultHeight);
|
||||
}
|
||||
// 优先级2:1-4号几何中心下降
|
||||
else if (!bigMarkers1_4.isEmpty()) {
|
||||
currentLandingMode = 2;
|
||||
processBigMarkersCenter(bigMarkers1_4, rgbMat.width(), rgbMat.height(), ultHeight);
|
||||
}
|
||||
// 优先级3:6号纯下降(旋转完成后,没看到1-4和小码时)
|
||||
else if (!bigMarker6.isEmpty()) {
|
||||
currentLandingMode = 3;
|
||||
processLandingOnly(bigMarker6.get(0), rgbMat.width(), rgbMat.height(), ultHeight);
|
||||
}
|
||||
// 旋转完成后丢失其他码,但还有6号:继续用6号下降
|
||||
else {
|
||||
handleLostMarker(ultHeight);
|
||||
}
|
||||
} else {
|
||||
LogUtil.log(TAG_LOG, "【等待6号】未检测到6号二维码,无法开始旋转对准");
|
||||
handleLostMarker(ultHeight);
|
||||
}
|
||||
dropTimesTag = true;
|
||||
} else {
|
||||
// 丢失处理(包含旋转阶段保护)
|
||||
handleLostMarker(ultHeight);
|
||||
}
|
||||
grayImgMat.release();
|
||||
rgbMat.release();
|
||||
yuvMat.release();
|
||||
ids.release();
|
||||
if (!corners.isEmpty()) {
|
||||
for (Mat c : corners) if (c != null) c.release();
|
||||
}
|
||||
isStartAruco = false;
|
||||
|
||||
} catch (Exception e) {
|
||||
LogUtil.log(TAG_LOG, "Exception: " + e);
|
||||
isStartAruco = false;
|
||||
}
|
||||
}
|
||||
}, 0, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
// ========== 【新增】6号专用旋转阶段(纯旋转,不下降) ==========
|
||||
private void processMarker6YawOnly(ArucoMarker marker, int imgWidth, int imgHeight, int ultHeight) {
|
||||
Mat corner = marker.getConner();
|
||||
Point[] pts = new Point[4];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
double[] p = corner.get(0, i);
|
||||
pts[i] = new Point(p[0], p[1]);
|
||||
}
|
||||
|
||||
// 使用6号的角点计算yaw误差
|
||||
double yawError = calculateYawErrorFromCorners(pts);
|
||||
double absYaw = Math.abs(yawError);
|
||||
|
||||
// 计算6号的像素宽度用于日志
|
||||
double pixelWidth = calculatePixelWidth(corner);
|
||||
|
||||
if (absYaw < 5.0) {
|
||||
// 旋转到位,设置标志,下一帧将进入正常下降流程
|
||||
isYawAligned = true;
|
||||
|
||||
Apronmixvalue.getInstance().setIsaglinetrue(true);
|
||||
|
||||
LogUtil.log(TAG_LOG, String.format("【6号旋转到位】误差=%.1f° pixel=%.0f 进入下降阶段", yawError, pixelWidth));
|
||||
// 发送悬停指令(停止旋转)
|
||||
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, 0f);
|
||||
} else {
|
||||
// 未对准:计算转速(分段)
|
||||
float yawRate;
|
||||
if (absYaw > 100.0) yawRate = 50.0f;
|
||||
else if (absYaw > 50.0) yawRate = 30.0f;
|
||||
else if (absYaw > 30.0) yawRate = 20.0f;
|
||||
else if (absYaw > 20.0) yawRate = 10.0f;
|
||||
else if (absYaw > 10.0) yawRate = 5.0f;
|
||||
else if (absYaw > 5.0) yawRate = 3.0f;
|
||||
else yawRate = 0.0f;
|
||||
|
||||
// 【注意】如果实际测试还是单向转,把下一行改成:yawRate = yawError > 0 ? -yawRate : yawRate;
|
||||
yawRate = yawError > 0 ? yawRate : -yawRate;
|
||||
|
||||
LogUtil.log(TAG_LOG, String.format("【6号旋转中】误差=%.1f° 转速=%.1f pixel=%.0f ult=%d",
|
||||
yawError, yawRate, pixelWidth, ultHeight));
|
||||
|
||||
// 【关键】只旋转,不水平移动,不下降(vz=0)
|
||||
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, yawRate, 0f);
|
||||
}
|
||||
}
|
||||
|
||||
private void processSmallMarkers(List<ArucoMarker> markers, int imgWidth, int imgHeight, int ultHeight) {
|
||||
// 【关键修改】必须检测到3个及以上小码才处理,否则进入丢失保护
|
||||
if (markers == null || markers.size() < 3) {
|
||||
LogUtil.log(TAG_LOG, "【小码数量不足】检测到" + (markers == null ? 0 : markers.size()) + "个,需要≥3个");
|
||||
// 数量不足时,如果有6号就用6号下降,否则悬停
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. 计算几何中心和统计信息
|
||||
double sumX = 0, sumY = 0;
|
||||
int validCount = 0;
|
||||
double totalPixelWidth = 0;
|
||||
|
||||
ArucoMarker target15 = null; // 单挂目标
|
||||
ArucoMarker target17 = null; // 双挂目标
|
||||
|
||||
for (ArucoMarker marker : markers) {
|
||||
Mat corner = marker.getConner();
|
||||
totalPixelWidth += calculatePixelWidth(corner);
|
||||
|
||||
double cx = 0, cy = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
double[] p = corner.get(0, i);
|
||||
cx += p[0];
|
||||
cy += p[1];
|
||||
}
|
||||
cx /= 4.0;
|
||||
cy /= 4.0;
|
||||
sumX += cx;
|
||||
sumY += cy;
|
||||
validCount++;
|
||||
|
||||
int id = marker.getId();
|
||||
if (id == 15) target15 = marker;
|
||||
if (id == 17) target17 = marker;
|
||||
}
|
||||
|
||||
if (validCount == 0) return;
|
||||
|
||||
double avgPixelWidth = totalPixelWidth / validCount;
|
||||
double geoCenterX = sumX / validCount;
|
||||
double geoCenterY = sumY / validCount;
|
||||
double offsetX = geoCenterX - imgWidth / 2.0 - LENS_OFFSET_X;
|
||||
double offsetY = geoCenterY - imgHeight / 2.0 + LENS_OFFSET_Y;
|
||||
|
||||
double absX = Math.abs(offsetX);
|
||||
double absY = Math.abs(offsetY);
|
||||
double z = ultHeight / 10.0;
|
||||
|
||||
// 降落条件判断(连续帧)
|
||||
boolean isSpecialLanding = false;
|
||||
double singleErrX = 0, singleErrY = 0;
|
||||
|
||||
// 单挂模式:检查15号
|
||||
if (!isDoublePayload && target15 != null && ultHeight <= 4) {
|
||||
Mat corner = target15.getConner();
|
||||
double cx = 0, cy = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
double[] p = corner.get(0, i);
|
||||
cx += p[0];
|
||||
cy += p[1];
|
||||
}
|
||||
cx /= 4.0; cy /= 4.0;
|
||||
singleErrX = cx - imgWidth / 2.0 - LENS_OFFSET_X;
|
||||
singleErrY = cy - imgHeight / 2.0 + LENS_OFFSET_Y;
|
||||
|
||||
if (Math.abs(singleErrX) < 60 && Math.abs(singleErrY) < 60) {
|
||||
isSpecialLanding = true;
|
||||
offsetX = singleErrX;
|
||||
offsetY = singleErrY;
|
||||
absX = Math.abs(singleErrX);
|
||||
absY = Math.abs(singleErrY);
|
||||
}
|
||||
}
|
||||
|
||||
// 双挂模式:检查17号
|
||||
if (isDoublePayload && target17 != null && ultHeight <= 4) {
|
||||
Mat corner = target17.getConner();
|
||||
double cx = 0, cy = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
double[] p = corner.get(0, i);
|
||||
cx += p[0];
|
||||
cy += p[1];
|
||||
}
|
||||
cx /= 4.0; cy /= 4.0;
|
||||
singleErrX = cx - imgWidth / 2.0 - LENS_OFFSET_X;
|
||||
singleErrY = cy - imgHeight / 2.0 + LENS_OFFSET_Y;
|
||||
|
||||
if (Math.abs(singleErrX) < 60 && Math.abs(singleErrY) < 60) {
|
||||
isSpecialLanding = true;
|
||||
offsetX = singleErrX;
|
||||
offsetY = singleErrY;
|
||||
absX = Math.abs(singleErrX);
|
||||
absY = Math.abs(singleErrY);
|
||||
}
|
||||
}
|
||||
|
||||
// 连续帧判断
|
||||
int currentCondition = 0;
|
||||
if (isSpecialLanding) {
|
||||
currentCondition = 1;
|
||||
} else if (ultHeight <= 4 && absX < 60 && absY < 60 && validCount >= 8) {
|
||||
currentCondition = 2;
|
||||
}
|
||||
|
||||
if (currentCondition == 0 || currentCondition != lastLandingCondition) {
|
||||
if (consecutiveTriggerCount > 0) {
|
||||
consecutiveTriggerCount = 0;
|
||||
LogUtil.log(TAG_LOG, "【计数器清零】条件变化 last=" + lastLandingCondition + " curr=" + currentCondition);
|
||||
}
|
||||
lastLandingCondition = currentCondition;
|
||||
}
|
||||
|
||||
if (currentCondition > 0) {
|
||||
consecutiveTriggerCount++;
|
||||
if (consecutiveTriggerCount >= TRIGGER_FRAME_THRESHOLD) {
|
||||
startFastStick = true;
|
||||
consecutiveTriggerCount = 0;
|
||||
lastLandingCondition = 0;
|
||||
|
||||
LogUtil.log(TAG_LOG, currentCondition == 1 ?
|
||||
"【降落触发】单双挂精准条件满足" :
|
||||
"【降落触发】几何中心对准,码数量=" + validCount);
|
||||
|
||||
handler.postDelayed(() -> handler.post(runnable), 300);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
double outX = 0, outY = 0, outZ = 0;
|
||||
|
||||
// 分段PID控制(原有逻辑保持不变)
|
||||
if(z <= 0.4){
|
||||
pidControlX.setInputFilterAll((float)offsetX/1750);
|
||||
pidControlY.setInputFilterAll(-(float)offsetY/1750);
|
||||
if (pidControlX.get_pid()<0){
|
||||
if (pidControlX.get_pid()<-0.125){
|
||||
outX=absX<120?0:-0.125;
|
||||
}else{
|
||||
outX=absX<120?0:pidControlX.get_pid();
|
||||
}
|
||||
}else{
|
||||
if (pidControlX.get_pid()>0.125){
|
||||
outX=absX<120?0:0.125;
|
||||
}else{
|
||||
outX=absX<120?0:pidControlX.get_pid();
|
||||
}
|
||||
}
|
||||
if (pidControlY.get_pid()<0){
|
||||
if (pidControlY.get_pid()<-0.125){
|
||||
outY=absY<120?0:-0.125;
|
||||
}else{
|
||||
outY=absY<120?0:pidControlY.get_pid();
|
||||
}
|
||||
}else{
|
||||
if (pidControlY.get_pid()>0.125){
|
||||
outY=absY<120?0:0.125;
|
||||
}else{
|
||||
outY=absY<120?0:pidControlY.get_pid();
|
||||
}
|
||||
}
|
||||
}else if(z <= 0.7){
|
||||
pidControlX.setInputFilterAll((float)offsetX/1200);
|
||||
pidControlY.setInputFilterAll(-(float)offsetY/1200);
|
||||
double pidX = pidControlX.get_pid();
|
||||
double pidY = pidControlY.get_pid();
|
||||
|
||||
if (pidX < 0){
|
||||
outX = (pidX < -0.135) ? (absX < 120 ? 0 : -0.135) : (absX < 120 ? 0 : pidX);
|
||||
}else{
|
||||
outX = (pidX > 0.135) ? (absX < 120 ? 0 : 0.135) : (absX < 120 ? 0 : pidX);
|
||||
}
|
||||
|
||||
if (pidY < 0){
|
||||
outY = (pidY < -0.135) ? (absY < 120 ? 0 : -0.135) : (absY < 120 ? 0 : pidY);
|
||||
}else{
|
||||
outY = (pidY > 0.135) ? (absY < 120 ? 0 : 0.135) : (absY < 120 ? 0 : pidY);
|
||||
}
|
||||
outZ = (absX < 250 && absY < 250) ? -0.1 : 0;
|
||||
|
||||
}else if(z <= 1.0){
|
||||
pidControlX.setInputFilterAll((float)offsetX/1200);
|
||||
pidControlY.setInputFilterAll(-(float)offsetY/1200);
|
||||
double pidX = pidControlX.get_pid();
|
||||
double pidY = pidControlY.get_pid();
|
||||
|
||||
if (pidX < 0){
|
||||
outX = (pidX < -0.145) ? (absX < 120 ? 0 : -0.145) : (absX < 120 ? 0 : pidX);
|
||||
}else{
|
||||
outX = (pidX > 0.145) ? (absX < 120 ? 0 : 0.145) : (absX < 120 ? 0 : pidX);
|
||||
}
|
||||
|
||||
if (pidY < 0){
|
||||
outY = (pidY < -0.175) ? (absY < 120 ? 0 : -0.175) : (absY < 120 ? 0 : pidY);
|
||||
}else{
|
||||
outY = (pidY > 0.175) ? (absY < 120 ? 0 : 0.175) : (absY < 120 ? 0 : pidY);
|
||||
}
|
||||
outZ = (absX < 200 && absY < 200) ? -0.2 : 0;
|
||||
|
||||
}else if(z <= 1.5){
|
||||
pidControlX.setInputFilterAll((float)offsetX/1450);
|
||||
pidControlY.setInputFilterAll(-(float)offsetY/1450);
|
||||
double pidX = pidControlX.get_pid();
|
||||
double pidY = pidControlY.get_pid();
|
||||
|
||||
if (pidX < 0){
|
||||
outX = (pidX < -0.185) ? (absX < 120 ? 0 : -0.185) : (absX < 120 ? 0 : pidX);
|
||||
}else{
|
||||
outX = (pidX > 0.185) ? (absX < 120 ? 0 : 0.185) : (absX < 120 ? 0 : pidX);
|
||||
}
|
||||
if (pidY < 0){
|
||||
outY = (pidY < -0.185) ? (absY < 120 ? 0 : -0.185) : (absY < 120 ? 0 : pidY);
|
||||
}else{
|
||||
outY = (pidY > 0.185) ? (absY < 120 ? 0 : 0.185) : (absY < 120 ? 0 : pidY);
|
||||
}
|
||||
outZ = (absX < 180 && absY < 180) ? -0.4 : 0;
|
||||
|
||||
}else if(z <= 2.0){
|
||||
pidControlX.setInputFilterAll((float)offsetX/1350);
|
||||
pidControlY.setInputFilterAll(-(float)offsetY/1350);
|
||||
outX = absX < 120 ? 0 : pidControlX.get_pid();
|
||||
outY = absY < 120 ? 0 : pidControlY.get_pid();
|
||||
outZ = (absX < 200 && absY < 200) ? -0.4 : 0;
|
||||
|
||||
}else if(z <= 3.0){
|
||||
pidControlX.setInputFilterAll((float)offsetX/1250);
|
||||
pidControlY.setInputFilterAll(-(float)offsetY/1250);
|
||||
outX = absX < 120 ? 0 : pidControlX.get_pid();
|
||||
outY = absY < 120 ? 0 : pidControlY.get_pid();
|
||||
outZ = (absX < 200 && absY < 200) ? -0.4 : 0;
|
||||
|
||||
}else {
|
||||
pidControlX.setInputFilterAll((float)offsetX/850);
|
||||
pidControlY.setInputFilterAll(-(float)offsetY/850);
|
||||
outX = absX < 80 ? 0 : pidControlX.get_pid();
|
||||
outY = absY < 80 ? 0 : pidControlY.get_pid();
|
||||
outZ = (absX < 130 && absY < 130) ? -0.4 : 0;
|
||||
}
|
||||
|
||||
LogUtil.log(TAG_LOG, String.format("【执行移动】小码 vx=%.3f vy=%.3f vz=%.3f pixel=%.0f ult=%d errX=%.0f errY=%.0f",
|
||||
outX, outY, outZ, avgPixelWidth, ultHeight, absX, absY));
|
||||
DroneHelper.getInstance().moveVxVyYawrateHeight((float)outX, (float)outY, 0, (float)outZ);
|
||||
}
|
||||
|
||||
// ========== 【修改】1-4号几何中心处理(旋转完成后才进入) ==========
|
||||
private void processBigMarkersCenter(List<ArucoMarker> markers, int imgWidth, int imgHeight, int ultHeight) {
|
||||
double sumX = 0, sumY = 0;
|
||||
StringBuilder markerInfo = new StringBuilder();
|
||||
|
||||
for (ArucoMarker marker : markers) {
|
||||
Mat corner = marker.getConner();
|
||||
double cx = 0, cy = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
double[] p = corner.get(0, i);
|
||||
cx += p[0];
|
||||
cy += p[1];
|
||||
}
|
||||
sumX += cx / 4.0;
|
||||
sumY += cy / 4.0;
|
||||
|
||||
double pixelW = calculatePixelWidth(corner);
|
||||
markerInfo.append(String.format("[ID%d:%.0fpx]", marker.getId(), pixelW));
|
||||
}
|
||||
|
||||
double geoCenterX = sumX / markers.size();
|
||||
double geoCenterY = sumY / markers.size();
|
||||
double offsetX = geoCenterX - imgWidth / 2.0;
|
||||
double offsetY = geoCenterY - imgHeight / 2.0;
|
||||
double absX = Math.abs(offsetX);
|
||||
double absY = Math.abs(offsetY);
|
||||
|
||||
double z = ultHeight / 10.0;
|
||||
double outX = 0, outY = 0, outZ = 0;
|
||||
|
||||
if (z >= 5.0) {
|
||||
pidControlX.setInputFilterAll((float)offsetX/300);
|
||||
pidControlY.setInputFilterAll(-(float)offsetY/300);
|
||||
outX = absX < 50 ? 0 : pidControlX.get_pid();
|
||||
outY = absY < 50 ? 0 : pidControlY.get_pid();
|
||||
outX = Math.max(-0.20, Math.min(0.20, outX));
|
||||
outY = Math.max(-0.20, Math.min(0.20, outY));
|
||||
outZ = (absX < 100 && absY < 100) ? -0.4 : 0;
|
||||
}else if(z>=4.0){
|
||||
pidControlX.setInputFilterAll((float)offsetX/600);
|
||||
pidControlY.setInputFilterAll(-(float)offsetY/600);
|
||||
outX = absX < 50 ? 0 : pidControlX.get_pid();
|
||||
outY = absY < 50 ? 0 : pidControlY.get_pid();
|
||||
outX = Math.max(-0.20, Math.min(0.20, outX));
|
||||
outY = Math.max(-0.20, Math.min(0.20, outY));
|
||||
outZ = (absX < 120 && absY < 120) ? -0.4 : 0;
|
||||
}else if(z>=2.0){
|
||||
pidControlX.setInputFilterAll((float)offsetX/900);
|
||||
pidControlY.setInputFilterAll(-(float)offsetY/900);
|
||||
outX = absX < 50 ? 0 : pidControlX.get_pid();
|
||||
outY = absY < 50 ? 0 : pidControlY.get_pid();
|
||||
outX = Math.max(-0.20, Math.min(0.20, outX));
|
||||
outY = Math.max(-0.20, Math.min(0.20, outY));
|
||||
outZ = (absX < 140 && absY < 140) ? -0.4 : 0;
|
||||
}else if(z>=1.0){
|
||||
pidControlX.setInputFilterAll((float)offsetX/1000);
|
||||
pidControlY.setInputFilterAll(-(float)offsetY/1000);
|
||||
outX = absX < 50 ? 0 : pidControlX.get_pid();
|
||||
outY = absY < 50 ? 0 : pidControlY.get_pid();
|
||||
outX = Math.max(-0.20, Math.min(0.20, outX));
|
||||
outY = Math.max(-0.20, Math.min(0.20, outY));
|
||||
outZ = (absX < 150 && absY < 150) ? -0.1 : 0;
|
||||
}else if(z>=0.5){
|
||||
outZ = (absX < 150 && absY < 150) ? -0.1 : 0;
|
||||
}
|
||||
|
||||
LogUtil.log(TAG_LOG, String.format("【执行移动】大码几何中心 vx=%.3f vy=%.3f vz=%.3f ult=%d %s errX=%.0f errY=%.0f",
|
||||
outX, outY, outZ, ultHeight, markerInfo.toString(), absX, absY));
|
||||
DroneHelper.getInstance().moveVxVyYawrateHeight((float)outX, (float)outY, 0f, (float)outZ);
|
||||
}
|
||||
|
||||
// ========== 【修改】6号纯下降模式(旋转完成后使用) ==========
|
||||
private void processLandingOnly(ArucoMarker marker, int imgWidth, int imgHeight, int ultHeight) {
|
||||
Mat corner = marker.getConner();
|
||||
Point[] pts = new Point[4];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
double[] p = corner.get(0, i);
|
||||
pts[i] = new Point(p[0], p[1]);
|
||||
}
|
||||
|
||||
double pixelWidth = calculatePixelWidth(corner);
|
||||
double cx = 0, cy = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
cx += pts[i].x;
|
||||
cy += pts[i].y;
|
||||
}
|
||||
cx /= 4.0;
|
||||
cy /= 4.0;
|
||||
double offsetX = cx - imgWidth / 2.0;
|
||||
double offsetY = cy - imgHeight / 2.0;
|
||||
|
||||
float vz;
|
||||
|
||||
if (ultHeight >= 50) {
|
||||
vz = -0.7f;
|
||||
} else if(ultHeight<=20){
|
||||
vz = 0.0f;
|
||||
}else{
|
||||
vz=-0.3f;
|
||||
}
|
||||
|
||||
LogUtil.log(TAG_LOG, String.format("【执行移动】6号纯下降 vz=%.3f pixel=%.0f ult=%d errX=%.0f errY=%.0f",
|
||||
vz, pixelWidth, ultHeight, Math.abs(offsetX), Math.abs(offsetY)));
|
||||
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, vz);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// ========== 【修改】handleLostMarker 增加旋转阶段保护 ==========
|
||||
private void handleLostMarker(int ultHeight) {
|
||||
if (!arucoNotFoundTag) {
|
||||
startTime = System.currentTimeMillis();
|
||||
arucoNotFoundTag = true;
|
||||
}
|
||||
endTime = System.currentTimeMillis();
|
||||
long lostDuration = endTime - startTime;
|
||||
|
||||
// 【关键新增】旋转阶段保护:还没对准(isYawAligned=false)时,悬停等待,不下降
|
||||
if (!isYawAligned) {
|
||||
LogUtil.log(TAG_LOG, "【旋转阶段丢失】未对准6号,悬停等待 vz=0 lostDuration=" + lostDuration);
|
||||
// 悬停(不上升也不下降)
|
||||
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, 0f);
|
||||
// 10秒还没看到6号,去备降点
|
||||
if (lostDuration > 10000) {
|
||||
LogUtil.log(TAG_LOG, "【旋转超时】10秒未检测到6号,飞往备降点");
|
||||
//切换(切换++)
|
||||
|
||||
Apronmixvalue.getInstance().inchangeTrytimes();
|
||||
if(Apronmixvalue.getInstance().getTrytimes()>5){
|
||||
AlternateLandingManager.getInstance().startTaskProcess(null);
|
||||
Movement.getInstance().setAlternate(true);
|
||||
return;
|
||||
}
|
||||
|
||||
Apronmixvalue.getInstance().switchthemmodefpv();
|
||||
Apronmixvalue.getInstance().pullup();
|
||||
|
||||
|
||||
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(isYawAligned){
|
||||
// 原有逻辑(旋转完成后的丢失处理)
|
||||
if (lostDuration > 1000 && lostDuration <= 12000) {
|
||||
if (ultHeight <= 20) {
|
||||
// 低空丢失:上升
|
||||
LogUtil.log(TAG_LOG, "【执行移动】丢失上升 vz=3.0");
|
||||
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, 3f);
|
||||
if (dropTimes > 1) {
|
||||
LogUtil.log(TAG_LOG, "超过复降限制,去备降点");
|
||||
|
||||
Apronmixvalue.getInstance().inchangeTrytimes();
|
||||
|
||||
if(Apronmixvalue.getInstance().getTrytimes()>5){
|
||||
AlternateLandingManager.getInstance().startTaskProcess(null);
|
||||
Movement.getInstance().setAlternate(true);
|
||||
return;
|
||||
}
|
||||
Apronmixvalue.getInstance().switchthemmodefpv();
|
||||
Apronmixvalue.getInstance().pullup();
|
||||
|
||||
return;
|
||||
}
|
||||
if (dropTimesTag) {
|
||||
dropTimesTag = false;
|
||||
dropTimes++;
|
||||
LogUtil.log(TAG_LOG, "复降第:" + dropTimes + "次");
|
||||
}
|
||||
} else {
|
||||
// 高空丢失:下降寻找
|
||||
LogUtil.log(TAG_LOG, "【执行移动】丢失下降 vz=-0.3");
|
||||
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, -0.3f);
|
||||
}
|
||||
} else if (lostDuration > 8000) {
|
||||
LogUtil.log(TAG_LOG, "判定未识别到二维码,飞往备降点");
|
||||
Apronmixvalue.getInstance().inchangeTrytimes();
|
||||
|
||||
if(Apronmixvalue.getInstance().getTrytimes()>5){
|
||||
AlternateLandingManager.getInstance().startTaskProcess(null);
|
||||
Movement.getInstance().setAlternate(true);
|
||||
return;
|
||||
}
|
||||
Apronmixvalue.getInstance().switchthemmodefpv();
|
||||
Apronmixvalue.getInstance().pullup();
|
||||
|
||||
}
|
||||
}
|
||||
// if(lostDuration > 8000) {
|
||||
// LogUtil.log(TAG_LOG, "判定未识别到二维码,飞往备降点");
|
||||
// AlternateLandingManager.getInstance().startTaskProcess(null);
|
||||
// Movement.getInstance().setAlternate(true);
|
||||
// }
|
||||
}
|
||||
|
||||
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() {
|
||||
startFastStick = false;
|
||||
isStartAruco = false;
|
||||
consecutiveTriggerCount = 0;
|
||||
frameCounter = 0;
|
||||
// 【关键】重置旋转标志,下次降落重新用6号旋转
|
||||
isYawAligned = false;
|
||||
currentLandingMode = 0;
|
||||
lastLandingCondition = 0;
|
||||
}
|
||||
|
||||
private int handlerCallbackCount = 0;
|
||||
private Handler handler = new Handler(Looper.getMainLooper());
|
||||
private Runnable runnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
performOperation();
|
||||
if (handlerCallbackCount < 20) {
|
||||
handler.postDelayed(this, 50);
|
||||
} else {
|
||||
performNextStep();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private void performOperation() {
|
||||
LogUtil.log(TAG_LOG, String.format("【执行移动】速降 vz=-4 count=%d/20", handlerCallbackCount));
|
||||
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, -5);
|
||||
handlerCallbackCount++;
|
||||
}
|
||||
|
||||
private void performNextStep() {
|
||||
canLanding = true;
|
||||
handlerCallbackCount = 0;
|
||||
dropTimes = 0;
|
||||
handler.removeCallbacks(runnable);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
package com.aros.apron.mix;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.aros.apron.entity.Movement;
|
||||
import com.aros.apron.entity.Synchronizedstatus;
|
||||
import com.aros.apron.tools.DroneHelper;
|
||||
import com.aros.apron.tools.LogUtil;
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import dji.v5.common.callback.CommonCallbacks;
|
||||
import dji.v5.common.error.IDJIError;
|
||||
import dji.v5.manager.aircraft.virtualstick.VirtualStickManager;
|
||||
|
||||
public class Apronmixvalue {
|
||||
private Apronmixvalue() {
|
||||
}
|
||||
|
||||
private static class ApronmixvalueHolder {
|
||||
private static final Apronmixvalue INSTANCE = new Apronmixvalue();
|
||||
}
|
||||
|
||||
public static Apronmixvalue getInstance() {
|
||||
return ApronmixvalueHolder.INSTANCE;
|
||||
}
|
||||
|
||||
private volatile boolean isaglinetrue = false; //机头是否对准
|
||||
|
||||
private volatile int trytimes = 0; //复降次数
|
||||
|
||||
public void switchthemmodefpv(){
|
||||
Synchronizedstatus.setAprongim(false);
|
||||
}
|
||||
public void switchthemmodeGime(){
|
||||
Synchronizedstatus.setAprongim(true);
|
||||
}
|
||||
|
||||
public boolean isIsaglinetrue() {
|
||||
return isaglinetrue;
|
||||
}
|
||||
|
||||
public void setIsaglinetrue(boolean isaglinetrue) {
|
||||
this.isaglinetrue = isaglinetrue;
|
||||
}
|
||||
|
||||
public int getTrytimes() {
|
||||
return trytimes;
|
||||
}
|
||||
public void inchangeTrytimes() {
|
||||
this.trytimes++;
|
||||
}
|
||||
public Handler handler = new Handler(Looper.getMainLooper());
|
||||
public void pullup(){
|
||||
Synchronizedstatus.setSwitchtime(false);
|
||||
Aprongim.getInstance().setDropTimes(0);
|
||||
Aprondown.getInstance().setDropTimes(0);
|
||||
|
||||
// 如果高度已经大于 40 分米,直接切换,不用上升
|
||||
if (Movement.getInstance().getUltrasonicHeight() >= 40) {
|
||||
Synchronizedstatus.setSwitchtime(true);
|
||||
return;
|
||||
}
|
||||
|
||||
Runnable runnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (Movement.getInstance().getUltrasonicHeight() < 50) {
|
||||
DroneHelper.getInstance().moveVxVyYawrateHeight(0f, 0f, 0f, 3f);
|
||||
handler.postDelayed(this, 200);
|
||||
} else {
|
||||
handler.removeCallbacks(this);
|
||||
Synchronizedstatus.setSwitchtime(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
// 开始循环
|
||||
handler.post(runnable);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue