From 489e19fb9ba0eb560eacc7cf64fb1385fee2984f Mon Sep 17 00:00:00 2001 From: Tao <168403401+Taoxuan-168@users.noreply.github.com> Date: Mon, 10 Aug 2026 11:08:16 +0800 Subject: [PATCH] feat(nn): add depth calibration params to Yolo26Depth API (#137) get_depth/get_depth_image now accept cal_a/cal_b so callers can pass a per-camera scale calibration d_real = exp(a*log(d)+b); example app exposes them as optional CLI args. Co-authored-by: Taoxuan-168 --- .../nn/include/maix_nn_yolo26_depth.hpp | 37 ++++++++++++++++--- examples/nn_yolo26_depth/main/src/main.cpp | 25 +++++++++---- 2 files changed, 49 insertions(+), 13 deletions(-) diff --git a/components/nn/include/maix_nn_yolo26_depth.hpp b/components/nn/include/maix_nn_yolo26_depth.hpp index 7d28cfdf..d6a1f570 100644 --- a/components/nn/include/maix_nn_yolo26_depth.hpp +++ b/components/nn/include/maix_nn_yolo26_depth.hpp @@ -187,11 +187,16 @@ namespace maix::nn * @param fit image resize fit mode if input image not equal to model' input size, * will auto resize to model's input size then detect, and recover to image input size. * Default Fit.FIT_CONTAIN, see image.Fit. + * @param cal_a calibration scale factor a, the depth is converted by d_real = exp(a * log(d) + b). + * Default 1.0 (no calibration). Set both cal_a/cal_b to fit your camera/scene. + * @param cal_b calibration offset b, see cal_a. Default 0.0 (no calibration). * @throw If error occurred, will raise err::Exception, you can find reason in log, mostly caused by args error or hardware error. - * @return result, a tensor.Tensor object. If in dual_buff mode, value can be None(in Python) or nullptr(in C++) when not ready. In C++, you need to delete it after use. + * @return result, a tensor.Tensor object with calibrated depth in meters. If in dual_buff mode, value can be None(in Python) or nullptr(in C++) when not ready. In C++, you need to delete it after use. * @maixpy maix.nn.YOLO26Depth.get_depth + * @maixcdk maix.nn.YOLO26Depth.get_depth */ - tensor::Tensor *get_depth(image::Image &img, image::Fit fit = image::FIT_CONTAIN) + tensor::Tensor *get_depth(image::Image &img, image::Fit fit = image::FIT_CONTAIN, + float cal_a = 1.0f, float cal_b = 0.0f) { if (_model == nullptr) return nullptr; std::unique_ptr outputs(_model->forward_image(img, {}, {}, fit, false, false)); @@ -205,6 +210,7 @@ namespace maix::nn _output_h = info.height; _output_w = info.width; tensor::Tensor *result = new tensor::Tensor(t->shape(), t->dtype(), t->data(), true); + _apply_calibration((float *)result->data(), info.width * info.height, cal_a, cal_b); return result; } @@ -217,14 +223,19 @@ namespace maix::nn * @param cmap Color map used convert grayscale distance estimation image to RGB image. * Diiferent cmap will influence finally image. * Default image.CMap.JET (near red/yellow, far blue). + * @param cal_a calibration scale factor a, the depth is converted by d_real = exp(a * log(d) + b). + * Default 1.0 (no calibration). Set both cal_a/cal_b to fit your camera/scene. + * @param cal_b calibration offset b, see cal_a. Default 0.0 (no calibration). * @throw If error occurred, will raise err::Exception, you can find reason in log, mostly caused by args error or hardware error. - * @return result, a image::Image object. If in dual_buff mode, value can be None(in Python) or nullptr(in C++) when not ready. In C++, you need to delete it after use. + * @return result, a image::Image object with calibrated depth heatmap. If in dual_buff mode, value can be None(in Python) or nullptr(in C++) when not ready. In C++, you need to delete it after use. * @maixpy maix.nn.YOLO26Depth.get_depth_image + * @maixcdk maix.nn.YOLO26Depth.get_depth_image */ image::Image *get_depth_image(image::Image &img, image::Fit fit = image::FIT_CONTAIN, - image::CMap cmap = image::CMap::JET) + image::CMap cmap = image::CMap::JET, + float cal_a = 1.0f, float cal_b = 0.0f) { - std::unique_ptr depth(get_depth(img, fit)); + std::unique_ptr depth(get_depth(img, fit, cal_a, cal_b)); if (!depth) return nullptr; return depth_to_image(*depth, img.width(), img.height(), fit, cmap); } @@ -392,6 +403,22 @@ namespace maix::nn return transform; } + /** + * Apply log-affine depth calibration in place: d_real = exp(a * log(d) + b). + * With default a=1, b=0 the data is unchanged. + */ + void _apply_calibration(float *data, int count, float cal_a, float cal_b) + { + if (cal_a == 1.0f && cal_b == 0.0f) + return; + for (int i = 0; i < count; i++) + { + float d = data[i]; + if (std::isfinite(d) && d > 0) + data[i] = expf(cal_a * logf(d) + cal_b); + } + } + /** * Convert depth map to RGB888 heatmap: disparity(1/depth) + min-max normalize + cmap lookup. * Return a new image::Image, caller should delete it. diff --git a/examples/nn_yolo26_depth/main/src/main.cpp b/examples/nn_yolo26_depth/main/src/main.cpp index 3abafd22..d4492fde 100644 --- a/examples/nn_yolo26_depth/main/src/main.cpp +++ b/examples/nn_yolo26_depth/main/src/main.cpp @@ -2,11 +2,11 @@ * YOLO26-depth example * * Usage: - * - 单张图片推理并保存热力图: - * ./nn_yolo26_depth mud_model_path image_path [output_path] - * 例: ./nn_yolo26_depth /tmp/yolo26n-depth.mud /tmp/bus.jpg /tmp/bus_heatmap.jpg - * - 实时摄像头推理并显示热力图: - * ./nn_yolo26_depth mud_model_path + * - 单张图片推理并保存热力图(可选传 cal_a cal_b 校准): + * ./nn_yolo26_depth mud_model_path image_path [output_path] [cal_a cal_b] + * 例: ./nn_yolo26_depth /tmp/yolo26n-depth.mud /tmp/bus.jpg /tmp/bus_heatmap.jpg 0.85 0.40 + * - 实时摄像头推理并显示热力图(可选传 cal_a cal_b): + * ./nn_yolo26_depth mud_model_path [cal_a cal_b] * 点击画面可添加/取消距离探针,最多保留 5 个,距离随画面实时更新。 * * 依赖(设备上需存在): @@ -166,6 +166,15 @@ int _main(int argc, char *argv[]) const char *model_path = argv[1]; bool dual_buff = false; image::CMap cmap = image::CMap::JET; + // 可选深度校准参数(默认不校准): d_real = exp(cal_a * log(d_raw) + cal_b) + float cal_a = 1.0f; + float cal_b = 0.0f; + if (argc >= 6) + { + cal_a = std::stof(argv[4]); + cal_b = std::stof(argv[5]); + log::info("depth calibration enabled: cal_a=%f cal_b=%f", cal_a, cal_b); + } log::info("model path: %s", model_path); nn::YOLO26Depth model(model_path, dual_buff); @@ -189,8 +198,8 @@ int _main(int argc, char *argv[]) img->width(), img->height(), model.input_width(), model.input_height()); } - // 推理得到深度热力图(自动 resize 回原图尺寸) - maix::image::Image *heatmap = model.get_depth_image(*img, image::FIT_CONTAIN, cmap); + // 推理得到深度热力图(自动 resize 回原图尺寸; 可传 cal_a/cal_b 校准) + maix::image::Image *heatmap = model.get_depth_image(*img, image::FIT_CONTAIN, cmap, cal_a, cal_b); err::check_null_raise(heatmap, "get_depth_image failed"); // 保存热力图 @@ -246,7 +255,7 @@ int _main(int argc, char *argv[]) was_pressed = pressed; } - std::unique_ptr depth(model.get_depth(*img, image::FIT_CONTAIN)); + std::unique_ptr depth(model.get_depth(*img, image::FIT_CONTAIN, cal_a, cal_b)); if (!depth) continue;