diff --git a/configs/test_cam1_face_det_rtsp_server.json b/configs/test_cam1_face_det_rtsp_server.json index c425c00..f41ba1f 100644 --- a/configs/test_cam1_face_det_rtsp_server.json +++ b/configs/test_cam1_face_det_rtsp_server.json @@ -24,8 +24,8 @@ "type": "preprocess", "role": "filter", "enable": true, - "dst_w": 640, - "dst_h": 640, + "dst_w": 1280, + "dst_h": 720, "dst_format": "rgb", "keep_ratio": false, "use_rga": true @@ -36,12 +36,13 @@ "role": "filter", "enable": true, "model_path": "./models/RetinaFace_mobile320.rknn", - "conf": 0.2, + "conf": 0.3, "nms": 0.4, "max_faces": 10, "output_landmarks": true, "input_format": "bgr", - "face_class_index": -1 + "face_class_index": 1, + "letterbox": true }, { "id": "osd_cam1", diff --git a/plugins/ai_face_det/ai_face_det_node.cpp b/plugins/ai_face_det/ai_face_det_node.cpp index 9d6b73b..2b15453 100644 --- a/plugins/ai_face_det/ai_face_det_node.cpp +++ b/plugins/ai_face_det/ai_face_det_node.cpp @@ -368,6 +368,42 @@ void ResizeRgbBilinear(const uint8_t* src, int src_w, int src_h, int src_stride, } } +struct LetterboxInfo { + float scale = 1.0f; + int pad_x = 0; + int pad_y = 0; +}; + +void LetterboxRgb(const uint8_t* src, int src_w, int src_h, int src_stride, + uint8_t* dst, int dst_w, int dst_h, bool swap_rb, + uint8_t pad_value, LetterboxInfo& info) { + info = {}; + if (!src || !dst || src_w <= 0 || src_h <= 0 || dst_w <= 0 || dst_h <= 0 || src_stride <= 0) return; + + const float s = std::min(static_cast(dst_w) / static_cast(src_w), + static_cast(dst_h) / static_cast(src_h)); + const int rw = std::max(1, static_cast(std::round(static_cast(src_w) * s))); + const int rh = std::max(1, static_cast(std::round(static_cast(src_h) * s))); + const int px = (dst_w - rw) / 2; + const int py = (dst_h - rh) / 2; + + info.scale = s; + info.pad_x = px; + info.pad_y = py; + + // fill background + std::fill(dst, dst + static_cast(dst_w) * static_cast(dst_h) * 3, pad_value); + + std::vector tmp(static_cast(rw) * static_cast(rh) * 3); + ResizeRgbBilinear(src, src_w, src_h, src_stride, tmp.data(), rw, rh, swap_rb); + + for (int y = 0; y < rh; ++y) { + uint8_t* drow = dst + (static_cast(py + y) * static_cast(dst_w) + static_cast(px)) * 3; + const uint8_t* srow = tmp.data() + static_cast(y) * static_cast(rw) * 3; + memcpy(drow, srow, static_cast(rw) * 3); + } +} + } // namespace class AiFaceDetNode : public INode { @@ -382,6 +418,7 @@ public: nms_thresh_ = config.ValueOr("nms", 0.4f); max_faces_ = std::max(1, config.ValueOr("max_faces", 10)); output_landmarks_ = config.ValueOr("output_landmarks", true); + letterbox_ = config.ValueOr("letterbox", letterbox_); face_class_index_ = config.ValueOr("face_class_index", face_class_index_); if (face_class_index_ != 0 && face_class_index_ != 1) { face_class_index_ = -1; // auto @@ -541,22 +578,30 @@ private: const uint8_t* input_ptr = nullptr; - // Fast path: already packed, correct size, no channel swap. - if (!need_swap && src_w == in_w && src_h == in_h && - static_cast(src_stride) == src_row && frame->data_size >= src_row * static_cast(src_h)) { - input_ptr = src; + LetterboxInfo lb; + if (!letterbox_) { + // Fast path: already packed, correct size, no channel swap. + if (!need_swap && src_w == in_w && src_h == in_h && + static_cast(src_stride) == src_row && frame->data_size >= src_row * static_cast(src_h)) { + input_ptr = src; + } else { + input_buf_.resize(in_size); + if (src_w == in_w && src_h == in_h && static_cast(src_stride) == src_row) { + memcpy(input_buf_.data(), src, in_size); + if (need_swap) { + for (size_t i = 0; i < in_size; i += 3) { + std::swap(input_buf_[i], input_buf_[i + 2]); + } + } + } else { + ResizeRgbBilinear(src, src_w, src_h, src_stride, input_buf_.data(), in_w, in_h, need_swap); + } + input_ptr = input_buf_.data(); + } } else { input_buf_.resize(in_size); - if (src_w == in_w && src_h == in_h && static_cast(src_stride) == src_row) { - memcpy(input_buf_.data(), src, in_size); - if (need_swap) { - for (size_t i = 0; i < in_size; i += 3) { - std::swap(input_buf_[i], input_buf_[i + 2]); - } - } - } else { - ResizeRgbBilinear(src, src_w, src_h, src_stride, input_buf_.data(), in_w, in_h, need_swap); - } + LetterboxRgb(src, src_w, src_h, src_stride, input_buf_.data(), in_w, in_h, + need_swap, 0, lb); input_ptr = input_buf_.data(); } @@ -609,7 +654,7 @@ private: det.img_h = src_h; det.model_name = "retinaface"; - DecodeRetinaFace(tensors, src_w, src_h, in_w, in_h, det); + DecodeRetinaFace(tensors, src_w, src_h, in_w, in_h, lb, det); if (!logged_decode_.exchange(true)) { LogInfo("[ai_face_det] decoded faces=" + std::to_string(det.faces.size())); } @@ -619,6 +664,7 @@ private: void DecodeRetinaFace(const std::vector& outs, int orig_w, int orig_h, int in_w, int in_h, + const LetterboxInfo& lb, FaceDetResult& out) { // Find loc/conf/landms tensors. std::vector locs; @@ -681,6 +727,8 @@ private: int face_idx = face_class_index_; float max_p0 = 0.0f; float max_p1 = 0.0f; + double sum_p0 = 0.0; + double sum_p1 = 0.0; if (face_idx < 0) { for (int i = 0; i < n; ++i) { const float s0 = conf.data[static_cast(i) * 2 + 0]; @@ -694,8 +742,13 @@ private: const float p0 = 1.0f - p1; if (p0 > max_p0) max_p0 = p0; if (p1 > max_p1) max_p1 = p1; + sum_p0 += p0; + sum_p1 += p1; } - face_idx = (max_p1 >= max_p0) ? 1 : 0; + const double mean_p0 = sum_p0 / static_cast(n); + const double mean_p1 = sum_p1 / static_cast(n); + // Face channel is typically sparse (low mean). Background channel has high mean. + face_idx = (mean_p1 <= mean_p0) ? 1 : 0; } if (!logged_conf_probe_.exchange(true)) { @@ -712,8 +765,8 @@ private: return; } - const float sx = static_cast(orig_w) / static_cast(in_w); - const float sy = static_cast(orig_h) / static_cast(in_h); + const bool use_lb = letterbox_ && lb.scale > 0.0f; + const float inv_s = use_lb ? (1.0f / lb.scale) : 1.0f; std::vector boxes; std::vector scores; @@ -756,10 +809,19 @@ private: float x2 = (cx + ww * 0.5f) * static_cast(in_w); float y2 = (cy + hh * 0.5f) * static_cast(in_h); - x1 *= sx; - x2 *= sx; - y1 *= sy; - y2 *= sy; + if (use_lb) { + x1 = (x1 - static_cast(lb.pad_x)) * inv_s; + x2 = (x2 - static_cast(lb.pad_x)) * inv_s; + y1 = (y1 - static_cast(lb.pad_y)) * inv_s; + y2 = (y2 - static_cast(lb.pad_y)) * inv_s; + } else { + const float sx = static_cast(orig_w) / static_cast(in_w); + const float sy = static_cast(orig_h) / static_cast(in_h); + x1 *= sx; + x2 *= sx; + y1 *= sy; + y2 *= sy; + } Rect bb; bb.x = static_cast(ClampInt(static_cast(x1), 0, orig_w - 1)); @@ -778,8 +840,17 @@ private: for (int k = 0; k < 5; ++k) { const float lx = lmk.data[static_cast(i) * 10 + k * 2 + 0]; const float ly = lmk.data[static_cast(i) * 10 + k * 2 + 1]; - const float px = (p.cx + lx * var0 * p.w) * static_cast(in_w) * sx; - const float py = (p.cy + ly * var0 * p.h) * static_cast(in_h) * sy; + float px = (p.cx + lx * var0 * p.w) * static_cast(in_w); + float py = (p.cy + ly * var0 * p.h) * static_cast(in_h); + if (use_lb) { + px = (px - static_cast(lb.pad_x)) * inv_s; + py = (py - static_cast(lb.pad_y)) * inv_s; + } else { + const float sx = static_cast(orig_w) / static_cast(in_w); + const float sy = static_cast(orig_h) / static_cast(in_h); + px *= sx; + py *= sy; + } pts[k].x = static_cast(ClampInt(static_cast(px), 0, orig_w - 1)); pts[k].y = static_cast(ClampInt(static_cast(py), 0, orig_h - 1)); } @@ -825,6 +896,7 @@ private: int max_faces_ = 10; bool output_landmarks_ = true; int face_class_index_ = -1; // -1=auto, 0/1=explicit + bool letterbox_ = false; std::string input_format_ = "rgb";