diff --git a/docs/doc/en/sidebar.yaml b/docs/doc/en/sidebar.yaml index ae7751ce..3673557e 100644 --- a/docs/doc/en/sidebar.yaml +++ b/docs/doc/en/sidebar.yaml @@ -89,6 +89,8 @@ items: label: Hand geature classifier - file: vision/depth_anything.md label: Depth-Anything Monocular Depth Estimation + - file: vision/yolo26-depth.md + label: Monocular Depth Estimation with YOLO26-depth - file: vision/yolo_world.md label: YOLO-World deten any object - file: vision/maixhub_train.md diff --git a/docs/doc/en/vision/yolo26-depth.md b/docs/doc/en/vision/yolo26-depth.md new file mode 100644 index 00000000..d16d3d62 --- /dev/null +++ b/docs/doc/en/vision/yolo26-depth.md @@ -0,0 +1,336 @@ +--- +title: MaixCAM2 MaixPy Monocular Depth Estimation with YOLO26-depth +update: + - date: 2026-08-08 + version: v1.0 + author: Tao + content: Add YOLO26-depth code and documentation support +--- + +## Introduction + +YOLO26-depth is a monocular depth estimation model released by Ultralytics. It predicts a per-pixel depth map from a single RGB image, where each pixel outputs an estimated distance in meters. Compared with Depth-Anything-V2, YOLO26-depth runs inference faster at the same resolution (YOLO26n-depth is about 7.7x faster than Depth-Anything V2 Small on T4 TensorRT fp16), making it suitable for real-time depth estimation scenarios. For details, see the official open-source repository or the Ultralytics depth estimation documentation. + +The model uses a log-depth head, outputs unbounded depth (about 0.02~150m), and has built-in cross-domain calibration parameters (`cal_a`/`cal_b`) that map relative depth to absolute meters. + +## Supported Devices + +| Device | Supported | +| --- | --- | +| MaixCAM2 | ✅ | +| MaixCAM | ❌ | + +## Using YOLO26-depth on MaixCAM2 MaixPy + +After putting the model files on the device, run the code directly: + +```python +from maix import app, camera, display, image, nn + +cmap = image.CMap.JET +# Optional calibration parameters: d_real = exp(cal_a * log(d_raw) + cal_b), no calibration by default (1.0, 0.0) +CAL_A, CAL_B = 1.0,0.0 +model = nn.YOLO26Depth(model="/root/yolo26n-depth.mud", dual_buff=True) + +cam = camera.Camera(model.input_width(), model.input_height(), model.input_format()) +disp = display.Display() + +while not app.need_exit(): + img = cam.read() + res = model.get_depth_image(img, image.Fit.FIT_CONTAIN, cmap, CAL_A, CAL_B) + if res: + disp.show(res) +``` + +Here `get_depth_image` directly returns a pseudo-color depth heatmap. `cmap` can be set to a colormap; all supported pseudo-colors can be found in the `maix.image.CMap` API documentation. Default is JET (red/yellow near, blue far, high contrast). + +If you want the raw model output (float32 depth data in meters), you can also use the `get_depth` method: + +```python +depth = model.get_depth(img, image.Fit.FIT_CONTAIN, CAL_A, CAL_B) +# Convert to numpy for use +import maix.tensor as mt +depth_np = mt.tensor_to_numpy_float32(depth).reshape(model.output_height(), model.output_width()) +``` + +## Reading Distance at a Specified Position + +`get_distance` can directly read the estimated distance (in meters) at a coordinate in the image, without parsing the depth tensor yourself: + +```python +dist = model.get_distance(depth, x, y, img.width(), img.height(), image.Fit.FIT_CONTAIN) +print(f"distance at ({x},{y}): {dist:.2f} m") +``` + +## Notes + +### Depth Scale Calibration + +YOLO26-depth outputs relative log depth; absolute meters depend on the global calibration parameters built into the model. This global calibration is fitted on a "indoor + outdoor mixed domain" and may be inaccurate for a specific camera or close-range scenes (<1m): + +- Close range (tens of centimeters) may be displayed magnified as 1m+, and near-end measurement accuracy is limited +- If you need to recover absolute meters for a specific camera/scene, you can collect several known-distance points, fit `d_real = exp(a * log(d_raw) + b)` to get `a` and `b`, and pass them when calling `get_depth`/`get_depth_image` + +### How to Calibrate (Automatic Collection Script) + +Calibration essence: collect paired data of "real distance vs model output" and fit two parameters with log-log linear regression: + +``` +log(d_real) = a * log(d_raw) + b => d_real = exp(a * log(d_raw) + b) +``` + +An automatic collection script is provided below. Run it on the device; it automatically outputs the distance predicted at the center point, collects points one by one via the touchscreen, automatically fits, and outputs `cal_a` and `cal_b`. + +**① Script content**: + +```python +"""YOLO26-depth automatic collection + calibration fitting script +Usage: + The screen shows the target distance and real-time center depth; place the object at the target distance and tap the screen to record + After collecting all distances, automatically fit: d_real = exp(a * log(d_raw) + b) +""" + +import math +import numpy as np +from maix import app, camera, display, image, nn, touchscreen, time + +MODEL = "/root/yolo26n-depth.mud" +DISTANCES_M = [0.15, 0.25, 0.40, 0.60, 0.80, 1.00, 1.50, 2.00] +FRAMES_PER_SAMPLE = 10 +SETTLE_FRAMES = 5 +OUT_FILE = "/root/calib_data.txt" + + +def main(): + model = nn.YOLO26Depth(MODEL, dual_buff=False) + cam = camera.Camera(model.input_width(), model.input_height(), model.input_format()) + disp = display.Display() + touch = touchscreen.TouchScreen() + cam_w, cam_h = cam.width(), cam.height() + cx, cy = cam_w // 2, cam_h // 2 + print(f"model {model.input_width()}x{model.input_height()}, camera {cam_w}x{cam_h}") + + def center_dist(img): + """Return the center distance in meters, or NaN if invalid.""" + d = model.get_depth(img, image.Fit.FIT_CONTAIN) + if d is None: + return float("nan") + v = model.get_distance(d, cx, cy, cam_w, cam_h, image.Fit.FIT_CONTAIN) + del d + return v + + for _ in range(10): # warmup + img = cam.read() + if img is not None: + center_dist(img) + + samples, idx, was_pressed = [], 0, False + print(f"start collecting {len(DISTANCES_M)} distances: {DISTANCES_M}, place the object and tap the screen") + + while idx < len(DISTANCES_M) and not app.need_exit(): + d_real = DISTANCES_M[idx] + img = cam.read() + if img is None: + continue + d_raw = center_dist(img) + + show = img.copy() + show.draw_string(10, 10, f"Target: {d_real:.2f} m ({idx+1}/{len(DISTANCES_M)})", image.COLOR_WHITE) + show.draw_string(10, 40, f"Center depth: {d_raw:.3f} m", image.COLOR_WHITE) + show.draw_string(10, 70, "Place object, tap to record", image.COLOR_WHITE) + show.draw_circle(cx, cy, 6, image.COLOR_RED, -1) + disp.show(show) + del show + + try: + pressed = bool(touch.read()[2]) + except Exception: + pressed = False + if was_pressed and not pressed: + vals = [] + for _ in range(SETTLE_FRAMES): + cam.read() + for _ in range(FRAMES_PER_SAMPLE): + f = cam.read() + if f is None: + continue + v = center_dist(f) + if not math.isnan(v): + vals.append(v) + if vals: + samples.append((d_real, float(np.mean(vals)))) + print(f"[{len(samples)}/{len(DISTANCES_M)}] real={d_real:.3f}m model={np.mean(vals):.3f}m") + idx += 1 + was_pressed = pressed + time.sleep_ms(10) + + print("\n===== collection done =====") + if len(samples) < 3: + print("fewer than 3 valid samples, cannot fit") + return + + d_real = np.array([s[0] for s in samples]) + d_raw = np.array([s[1] for s in samples]) + a, b = np.polyfit(np.log(d_raw), np.log(d_real), 1) + for r, w, c in zip(d_real, d_raw, np.exp(a * np.log(d_raw) + b)): + print(f" real={r:.3f}m model={w:.3f}m calibrated={c:.3f}m error={abs(c-r)/r*100:.1f}%") + print(f"\n>>> cal_a = {a:.6f}\n>>> cal_b = {b:.6f}") + with open(OUT_FILE, "w") as f: + f.write(f"cal_a = {a:.6f}\ncal_b = {b:.6f}\n") + for r, w in samples: + f.write(f"{r:.3f} {w:.3f}\n") + print(f"data saved to {OUT_FILE}") + + +if __name__ == "__main__": + main() +``` + +**② Calibration steps**: + +1. Run the script in MaixVision +2. Modify the distance list `DISTANCES_M` at the top of the script as needed (cover your usage range, collect more groups): + ```python + DISTANCES_M = [0.15, 0.25, 0.40, 0.60, 0.80, 1.00, 1.50, 2.00] + ``` +3. The screen shows `Target: 0.15 m (1/8)` and the real-time `Center depth`: + - Take a textured object (palm/cardboard box/phone), place it at exactly 0.15m in front of the lens (measure from the lens plane to the object surface with a tape measure) + - Tap the screen -> automatically collect 10 frames and average -> move to the next group + - Repeat until all distances are collected +4. The script automatically fits and outputs: + ``` + >>> cal_a = 0.850000 + >>> cal_b = 0.400000 + data saved to /root/calib_data.txt + ``` + +**③ Apply the calibration parameters**: + +Pass the output `cal_a`/`cal_b` to the inference API: + +```python +CAL_A, CAL_B = 1.0, 0.0 # replace with the fitting result +res = model.get_depth_image(img, image.Fit.FIT_CONTAIN, cmap, CAL_A, CAL_B) +# or read the raw depth: +depth = model.get_depth(img, image.Fit.FIT_CONTAIN, CAL_A, CAL_B) +``` + +**Notes**: +- The object should be as large as possible and cover the center of the image (the script uses the center pixel) +- Measure the real distance from the **lens plane**, not the device body +- Calibration only corrects overall scale offset; it cannot recover the resolution lost by model quantization +- Re-calibrate after changing the camera or mounting angle (`a`/`b` are strongly camera-dependent) + +### Output Is a Relative Value + +Similar to Depth-Anything, if no calibration is applied, the absolute value of the model output varies with the image content (e.g., the value range is larger when the scene depth span is large), and the depth will flicker slightly between consecutive video frames. + +### Converting to Different Resolutions +The default resolution is 680*480. If you want to use another resolution, you can convert the model yourself as follows: + +**①Download the original pt model:** + +Visit the [Yolo26-Depth official website](https://docs.ultralytics.com/zh/tasks/depth) to download models of different precision (default 768*768) + +**②Export the onnx model at the desired resolution:** + +A one-click export script is provided here to export an ONNX model at the desired resolution: +```python +"""YOLO26-depth.pt -> ONNX at any size. + # YOLO26 depth models require ultralytics>=8.4.49 + +Usage: + python export_to_onnx.py yolo26n-depth.pt --imgsz 640 # fixed 640x640 + python export_to_onnx.py yolo26s-depth.pt --imgsz 640x480 # any widthxheight (stride-aligned automatically) + python export_to_onnx.py yolo26n-depth.pt --imgsz 640 --dynamic # dynamic size +""" +import argparse +from ultralytics import YOLO + +a = argparse.ArgumentParser(description="Export ONNX at any size") +a.add_argument("model", nargs="?", default="yolo26n-depth.pt") +a.add_argument("--imgsz", default="768", help="any size: 768 or 640x480 (widthxheight)") +a.add_argument("--dynamic", action="store_true", help="export dynamic size (H/W/batch not fixed)") +a.add_argument("--batch", type=int, default=1) +args = a.parse_args() + +imgsz = args.imgsz +if "x" in imgsz: + w, h = imgsz.lower().split("x") + imgsz = (int(h), int(w)) # ultralytics uses (H, W) +else: + imgsz = int(imgsz) +``` +**③Write the conversion config:** +```json +{ + "input": "./yolo26s-depth_640x480.onnx", + "output_dir": "./output", + "output_name": "yolo26s-depth_640x480.axmodel", + "model_type": "ONNX", + "target_hardware": "AX620E", + "npu_mode": "NPU2", + "onnx_opt": { + "disable_onnx_optimization": false, + "enable_onnxsim": true + }, + "quant": { + "input_configs": [ + { + "tensor_name": "images", + "calibration_dataset": "./calib_data.zip", + "calibration_size": -1, + "calibration_mean": [0, 0, 0], + "calibration_std": [255, 255, 255] + } + ], + "layer_configs": [ + { + "start_tensor_names": ["DEFAULT"], + "end_tensor_names": ["DEFAULT"], + "data_type": "U8" + } + ], + "calibration_method": "MinMax", + "precision_analysis": true, + "precision_analysis_method": "EndToEnd", + "precision_analysis_mode": "Reference" + }, + "input_processors": [ + { + "tensor_name": "images", + "tensor_layout": "NCHW", + "src_dtype": "U8", + "src_layout": "NHWC" + } + ], + "compiler": { + "check": 0 + } +} +``` +The quantization dataset calib_data.zip can contain a few COCO dataset images + +**④Start conversion:** + +Run the command in the pulsar2 container to start model conversion: +```bash +pulsar2 build --config config.json +``` + +⑤Write the MUD file: +```mud +[basic] +type = axmodel +model_npu = yolo26n-depth_640x480_npu.axmodel +model_vnpu = yolo26n-depth_640x480_vnpu.axmodel + +[extra] +model_type = yolo26_depth +input_type = rgb + +input_cache = true +output_cache = true +input_cache_flush = false +output_cache_inval = true +``` diff --git a/docs/doc/zh/sidebar.yaml b/docs/doc/zh/sidebar.yaml index 3b030cec..8e5b8752 100644 --- a/docs/doc/zh/sidebar.yaml +++ b/docs/doc/zh/sidebar.yaml @@ -89,6 +89,8 @@ items: label: 手势分类器 - file: vision/depth_anything.md label: Depth-Anything 单目深度估计 + - file: vision/yolo26-depth.md + label: YOLO26-Depth 单目深度估计 - file: vision/yolo_world.md label: YOLO-World 检测任意物体 - file: vision/maixhub_train.md diff --git a/docs/doc/zh/vision/yolo26-depth.md b/docs/doc/zh/vision/yolo26-depth.md new file mode 100644 index 00000000..5986e9ff --- /dev/null +++ b/docs/doc/zh/vision/yolo26-depth.md @@ -0,0 +1,336 @@ +--- +title: MaixCAM2 MaixPy 使用 YOLO26-depth 单目估计深度距离 +update: + - date: 2026-08-08 + version: v1.0 + author: Tao + content: 增加 YOLO26-depth 代码和文档支持 +--- + +## 简介 + +YOLO26-depth 是 Ultralytics 推出的单目深度估计模型,可以从单张 RGB 图像预测逐像素深度图,每个像素输出以米为单位的估计距离。与 Depth-Anything-V2 相比,YOLO26-depth 在同样的分辨率下推理速度更快(YOLO26n-depth 在 T4 TensorRT fp16 下比 Depth-Anything V2 Small 快约 7.7 倍),适合需要实时深度估计的场景。具体介绍可以看官方开源仓库或者 Ultralytics 深度估计文档。 + +模型采用 log-depth 头,输出无界深度(约 0.02~150m),并内置了跨域校准参数(`cal_a`/`cal_b`)将相对深度映射为绝对米数。 + +## 支持的设备 + +| 设备 | 是否支持 | +| --- | --- | +| MaixCAM2 | ✅ | +| MaixCAM | ❌ | + +## 在 MaixCAM2 MaixPy 上使用 YOLO26-depth + +将模型文件放到设备上后,直接运行代码即可: + +```python +from maix import app, camera, display, image, nn + +cmap = image.CMap.JET +# 可选校准参数: d_real = exp(cal_a * log(d_raw) + cal_b), 默认不校准(1.0, 0.0) +CAL_A, CAL_B = 1.0,0.0 +model = nn.YOLO26Depth(model="/root/yolo26n-depth.mud", dual_buff=True) + +cam = camera.Camera(model.input_width(), model.input_height(), model.input_format()) +disp = display.Display() + +while not app.need_exit(): + img = cam.read() + res = model.get_depth_image(img, image.Fit.FIT_CONTAIN, cmap, CAL_A, CAL_B) + if res: + disp.show(res) +``` + +这里通过 `get_depth_image` 直接获得一张伪彩色深度热力图,`cmap` 可以指定为彩色,支持的所有伪彩色可以看 `maix.image.CMap` API 文档,默认 JET(近处红/黄、远处蓝,对比鲜明)。 + +如果你想要模型原始输出(float32 深度数据,单位米),也可以用 `get_depth` 方法: + +```python +depth = model.get_depth(img, image.Fit.FIT_CONTAIN, CAL_A, CAL_B) +# 转换为 numpy 使用 +import maix.tensor as mt +depth_np = mt.tensor_to_numpy_float32(depth).reshape(model.output_height(), model.output_width()) +``` + +## 在指定位置读取距离 + +`get_distance` 可以直接读取画面中某个坐标的估计距离(米),不需要自己解析深度张量: + +```python +dist = model.get_distance(depth, x, y, img.width(), img.height(), image.Fit.FIT_CONTAIN) +print(f"({x},{y}) 处距离: {dist:.2f} m") +``` + +## 注意点 + +### 深度尺度校准 + +YOLO26-depth 输出的是相对对数深度,绝对米数依赖模型内置的全局校准参数。该全局校准按"室内+室外混合域"拟合,对特定摄像头或近距离场景(<1m)可能不准: + +- 近距离(十几厘米)可能被放大显示为 1m+,且端测量精度受限 +- 若需要恢复特定摄像头/场景的绝对米数,可以采集若干已知距离点,拟合 `d_real = exp(a * log(d_raw) + b)` 得到 `a`、`b`,在调用 `get_depth`/`get_depth_image` 时传入即可 + +### 如何校准(自动采集脚本) + +校准本质:采集"真实距离 vs 模型输出"的配对数据,用 log-log 线性回归拟合两个参数: + +``` +log(d_real) = a * log(d_raw) + b => d_real = exp(a * log(d_raw) + b) +``` + +下面提供自动采集脚本,在设备上运行,自动输出中心点预测的距离,通过触摸屏逐点采集,自动拟合并输出 `cal_a`、`cal_b`。 + +**① 脚本内容**: + +```python +"""YOLO26-depth 自动采集 + 校准拟合脚本 +操作: + 屏幕显示目标距离与实时中心深度, 物体放到目标距离后点击屏幕记录 + 采完所有距离后自动拟合: d_real = exp(a * log(d_raw) + b) +""" + +import math +import numpy as np +from maix import app, camera, display, image, nn, touchscreen, time + +MODEL = "/root/yolo26n-depth.mud" +DISTANCES_M = [0.15, 0.25, 0.40, 0.60, 0.80, 1.00, 1.50, 2.00] +FRAMES_PER_SAMPLE = 10 +SETTLE_FRAMES = 5 +OUT_FILE = "/root/calib_data.txt" + + +def main(): + model = nn.YOLO26Depth(MODEL, dual_buff=False) + cam = camera.Camera(model.input_width(), model.input_height(), model.input_format()) + disp = display.Display() + touch = touchscreen.TouchScreen() + cam_w, cam_h = cam.width(), cam.height() + cx, cy = cam_w // 2, cam_h // 2 + print(f"model {model.input_width()}x{model.input_height()}, camera {cam_w}x{cam_h}") + + def center_dist(img): + """返回画面中心距离(米), 无效返回 NaN。""" + d = model.get_depth(img, image.Fit.FIT_CONTAIN) + if d is None: + return float("nan") + v = model.get_distance(d, cx, cy, cam_w, cam_h, image.Fit.FIT_CONTAIN) + del d + return v + + for _ in range(10): # 预热 + img = cam.read() + if img is not None: + center_dist(img) + + samples, idx, was_pressed = [], 0, False + print(f"开始采集 {len(DISTANCES_M)} 个距离: {DISTANCES_M}, 放好物体后点击屏幕") + + while idx < len(DISTANCES_M) and not app.need_exit(): + d_real = DISTANCES_M[idx] + img = cam.read() + if img is None: + continue + d_raw = center_dist(img) + + show = img.copy() + show.draw_string(10, 10, f"Target: {d_real:.2f} m ({idx+1}/{len(DISTANCES_M)})", image.COLOR_WHITE) + show.draw_string(10, 40, f"Center depth: {d_raw:.3f} m", image.COLOR_WHITE) + show.draw_string(10, 70, "Place object, tap to record", image.COLOR_WHITE) + show.draw_circle(cx, cy, 6, image.COLOR_RED, -1) + disp.show(show) + del show + + try: + pressed = bool(touch.read()[2]) + except Exception: + pressed = False + if was_pressed and not pressed: + vals = [] + for _ in range(SETTLE_FRAMES): + cam.read() + for _ in range(FRAMES_PER_SAMPLE): + f = cam.read() + if f is None: + continue + v = center_dist(f) + if not math.isnan(v): + vals.append(v) + if vals: + samples.append((d_real, float(np.mean(vals)))) + print(f"[{len(samples)}/{len(DISTANCES_M)}] 真实={d_real:.3f}m 模型={np.mean(vals):.3f}m") + idx += 1 + was_pressed = pressed + time.sleep_ms(10) + + print("\n===== 采集完成 =====") + if len(samples) < 3: + print("有效样本不足 3 组, 无法拟合") + return + + d_real = np.array([s[0] for s in samples]) + d_raw = np.array([s[1] for s in samples]) + a, b = np.polyfit(np.log(d_raw), np.log(d_real), 1) + for r, w, c in zip(d_real, d_raw, np.exp(a * np.log(d_raw) + b)): + print(f" 真实={r:.3f}m 模型={w:.3f}m 校准后={c:.3f}m 误差={abs(c-r)/r*100:.1f}%") + print(f"\n>>> cal_a = {a:.6f}\n>>> cal_b = {b:.6f}") + with open(OUT_FILE, "w") as f: + f.write(f"cal_a = {a:.6f}\ncal_b = {b:.6f}\n") + for r, w in samples: + f.write(f"{r:.3f} {w:.3f}\n") + print(f"数据已保存到 {OUT_FILE}") + + +if __name__ == "__main__": + main() +``` + +**② 校准步骤**: + +1. 在Maixvision运行脚本 +2. 按需修改脚本顶部的距离列表 `DISTANCES_M`(覆盖你的使用范围,多采几组): + ```python + DISTANCES_M = [0.15, 0.25, 0.40, 0.60, 0.80, 1.00, 1.50, 2.00] + ``` +3. 屏幕显示 `Target: 0.15 m (1/8)` 与实时 `Center depth`: + - 拿一个有纹理的物体(手掌/纸盒/手机),放在镜头前正好 0.15m 处(用卷尺从镜头平面量到物体表面) + - 点击屏幕 → 自动采集 10 帧取平均 → 进入下一组 + - 重复直到采完全部距离 +4. 脚本自动拟合并输出: + ``` + >>> cal_a = 0.850000 + >>> cal_b = 0.400000 + 数据已保存到 /root/calib_data.txt + ``` + +**③ 应用校准参数**: + +把输出的 `cal_a`/`cal_b` 传入推理 API 即可: + +```python +CAL_A, CAL_B = 1.0, 0.0 # 替换成拟合结果 +res = model.get_depth_image(img, image.Fit.FIT_CONTAIN, cmap, CAL_A, CAL_B) +# 或读取原始深度: +depth = model.get_depth(img, image.Fit.FIT_CONTAIN, CAL_A, CAL_B) +``` + +**注意事项**: +- 物体尽量大、覆盖画面中心(脚本取的是中心像素) +- 真实距离从**镜头平面**量起,不是机身 +- 校准只能修正整体尺度偏移,无法恢复模型量化丢掉的分辨率 +- 换摄像头/换安装角度后需重新校准(`a`、`b` 与摄像头强相关) + +### 输出为相对值 + +和 Depth-Anything 类似,若不做校准,模型输出的绝对值会随图像内容变化(比如场景深度范围大时数值跨度大),连续视频帧间深度会有轻微闪动。 + +### 转换不同分辨率 +默认采用的分辨率为 680*480, 如果你期望使用其它分辨率,可以按照以下方法自行转换: + +**①下载原始pt模型:** + +访问[Yolo26-Depth官网](https://docs.ultralytics.com/zh/tasks/depth)下载不同精度的模型(默认768*768) + +**②导出所需分辨率的onnx模型:** + +这里提供一键导出脚本,用于导出所需的分辨率的onnx模型: +```python +"""YOLO26-depth.pt -> 任意尺寸 ONNX。 + # YOLO26 depth 模型需要 ultralytics>=8.4.49 + +用法: + python export_to_onnx.py yolo26n-depth.pt --imgsz 640 # 固定 640x640 + python export_to_onnx.py yolo26s-depth.pt --imgsz 640x480 # 任意宽x高(自动对齐 stride) + python export_to_onnx.py yolo26n-depth.pt --imgsz 640 --dynamic # 动态尺寸 +""" +import argparse +from ultralytics import YOLO + +a = argparse.ArgumentParser(description="导出任意尺寸 ONNX") +a.add_argument("model", nargs="?", default="yolo26n-depth.pt") +a.add_argument("--imgsz", default="768", help="任意尺寸: 768 或 640x480(宽x高)") +a.add_argument("--dynamic", action="store_true", help="导出动态尺寸(H/W/batch 不固定)") +a.add_argument("--batch", type=int, default=1) +args = a.parse_args() + +imgsz = args.imgsz +if "x" in imgsz: + w, h = imgsz.lower().split("x") + imgsz = (int(h), int(w)) # ultralytics 用 (H, W) +else: + imgsz = int(imgsz) +``` +**③编写转换配置:** +```json +{ + "input": "./yolo26s-depth_640x480.onnx", + "output_dir": "./output", + "output_name": "yolo26s-depth_640x480.axmodel", + "model_type": "ONNX", + "target_hardware": "AX620E", + "npu_mode": "NPU2", + "onnx_opt": { + "disable_onnx_optimization": false, + "enable_onnxsim": true + }, + "quant": { + "input_configs": [ + { + "tensor_name": "images", + "calibration_dataset": "./calib_data.zip", + "calibration_size": -1, + "calibration_mean": [0, 0, 0], + "calibration_std": [255, 255, 255] + } + ], + "layer_configs": [ + { + "start_tensor_names": ["DEFAULT"], + "end_tensor_names": ["DEFAULT"], + "data_type": "U8" + } + ], + "calibration_method": "MinMax", + "precision_analysis": true, + "precision_analysis_method": "EndToEnd", + "precision_analysis_mode": "Reference" + }, + "input_processors": [ + { + "tensor_name": "images", + "tensor_layout": "NCHW", + "src_dtype": "U8", + "src_layout": "NHWC" + } + ], + "compiler": { + "check": 0 + } +} +``` +其中量化数据集calib_data.zip可以放几张COCO数据集 + +**④开始转换:** + +在pulsar2容器内运行命令开始转换模型: +```bash +pulsar2 build --config config.json +``` + +⑤编写MUD文件: +```mud +[basic] +type = axmodel +model_npu = yolo26n-depth_640x480_npu.axmodel +model_vnpu = yolo26n-depth_640x480_vnpu.axmodel + +[extra] +model_type = yolo26_depth +input_type = rgb + +input_cache = true +output_cache = true +input_cache_flush = false +output_cache_inval = true +``` \ No newline at end of file