From 418a4aa0e0029c84ddc8a267176222eec35e3df3 Mon Sep 17 00:00:00 2001 From: Aliothmoon <107878625+Aliothmoon@users.noreply.github.com> Date: Wed, 24 Jun 2026 10:58:22 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20ncnn=20=E5=AF=B9=E9=BD=90=20fastdeploy?= =?UTF-8?q?=20=E7=9A=84=20det=20=E7=BC=A9=E6=94=BE=E4=B8=8E=20rec=20?= =?UTF-8?q?=E9=A2=84=E5=A4=84=E7=90=86=20(#17182)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 对齐 OcrPackNcnn det 缩放算法与 fastdeploy - 小 ROI 下 ceil 直接就爆了,识别会出现问题 * fix: ncnn 对齐 fastdeploy de1通道序/打分/裁剪 * fix: OcrPackNcnn det 无框兜底走整图 rec,尺寸过滤对齐 fastdeploy --- .../Config/Miscellaneous/OcrPackNcnn.cpp | 86 ++++++++++++------- 1 file changed, 53 insertions(+), 33 deletions(-) diff --git a/src/MaaCore/Config/Miscellaneous/OcrPackNcnn.cpp b/src/MaaCore/Config/Miscellaneous/OcrPackNcnn.cpp index b1645ec1c8..43ce1547f8 100644 --- a/src/MaaCore/Config/Miscellaneous/OcrPackNcnn.cpp +++ b/src/MaaCore/Config/Miscellaneous/OcrPackNcnn.cpp @@ -71,7 +71,7 @@ OcrPack::ResultsVec OcrPack::recognize(const cv::Mat& image, bool without_det, c { if (!check_and_load()) { Log.error(__FUNCTION__, "check_and_load failed"); - return {}; + return { }; } auto start_time = std::chrono::steady_clock::now(); @@ -166,7 +166,7 @@ constexpr float kDetThresh = 0.3f; constexpr float kDetBoxThresh = 0.6f; constexpr float kDetUnclipRatio = 1.5f; constexpr int kDetMinSize = 3; -constexpr int kDetMaxCandidates = 100; +constexpr int kDetMaxCandidates = 1000; constexpr int kRecImgH = 48; constexpr int kRecImgW = 320; @@ -215,29 +215,32 @@ void order_box(const cv::Point2f in[4], cv::Point2f out[4]) out[3] = bl; } -float box_score(const cv::Mat& prob, const cv::Point2f box[4]) +// 对齐 fastdeploy +float poly_score(const cv::Mat& prob, const std::vector& contour) { const int w = prob.cols; const int h = prob.rows; - float xs[4] = { box[0].x, box[1].x, box[2].x, box[3].x }; - float ys[4] = { box[0].y, box[1].y, box[2].y, box[3].y }; - int xmin = std::max(0, static_cast(std::floor(*std::min_element(xs, xs + 4)))); - int xmax = std::min(w - 1, static_cast(std::ceil(*std::max_element(xs, xs + 4)))); - int ymin = std::max(0, static_cast(std::floor(*std::min_element(ys, ys + 4)))); - int ymax = std::min(h - 1, static_cast(std::ceil(*std::max_element(ys, ys + 4)))); + int xmin = w, xmax = 0, ymin = h, ymax = 0; + for (const auto& p : contour) { + xmin = std::min(xmin, p.x); + xmax = std::max(xmax, p.x); + ymin = std::min(ymin, p.y); + ymax = std::max(ymax, p.y); + } + xmin = std::clamp(xmin, 0, w - 1); + xmax = std::clamp(xmax, 0, w - 1); + ymin = std::clamp(ymin, 0, h - 1); + ymax = std::clamp(ymax, 0, h - 1); if (xmax < xmin || ymax < ymin) { return 0.f; } cv::Mat mask = cv::Mat::zeros(ymax - ymin + 1, xmax - xmin + 1, CV_8UC1); - std::array shifted; - for (int i = 0; i < 4; ++i) { - shifted[i] = cv::Point(static_cast(box[i].x) - xmin, static_cast(box[i].y) - ymin); - } - std::vector> polys = { { shifted.begin(), shifted.end() } }; - cv::fillPoly(mask, polys, cv::Scalar(1)); - if (cv::countNonZero(mask) == 0) { - return 0.f; + std::vector shifted; + shifted.reserve(contour.size()); + for (const auto& p : contour) { + shifted.emplace_back(p.x - xmin, p.y - ymin); } + cv::fillPoly(mask, std::vector> { shifted }, cv::Scalar(1)); cv::Mat region = prob(cv::Rect(xmin, ymin, xmax - xmin + 1, ymax - ymin + 1)); return static_cast(cv::mean(region, mask)[0]); } @@ -372,16 +375,19 @@ std::vector asst::OcrPackNcnn::detect(const cv::Mat& if (std::max(src_h, src_w) > kDetLimitSideLen) { ratio = static_cast(kDetLimitSideLen) / std::max(src_h, src_w); } - auto align32 = [](int v) { - return std::max(32, ((v + 31) / 32) * 32); + // 与 fastdeploy 的 OcrDetectorGetInfo 对齐 + // 之前用 ceil 对齐:小 ROI 上 ceil 会过度横向拉伸(如 106→128 比 round 的 106→96 多拉 ~21%), + // 改变文字长宽比,使 DBNet 概率脊偏扁、检测框竖向过紧而切掉小字首笔,导致 rec 误识 + // (实测「聚羽之地」被读成「袭羽之地」;改 round 后框 16px→20px,两个字段均正确且置信度更高)。 + auto align32 = [](const int v) { + return std::max(32, static_cast(std::round(v / 32.f)) * 32); }; - const int resize_h = align32(static_cast(std::lround(src_h * ratio))); - const int resize_w = align32(static_cast(std::lround(src_w * ratio))); + const int resize_h = align32(static_cast(src_h * ratio)); + const int resize_w = align32(static_cast(src_w * ratio)); const float ratio_h = static_cast(resize_h) / src_h; const float ratio_w = static_cast(resize_w) / src_w; - ncnn::Mat in = - ncnn::Mat::from_pixels_resize(image.data, ncnn::Mat::PIXEL_BGR2RGB, src_w, src_h, resize_w, resize_h); + ncnn::Mat in = ncnn::Mat::from_pixels_resize(image.data, ncnn::Mat::PIXEL_BGR, src_w, src_h, resize_w, resize_h); in.substract_mean_normalize(kDetMean, kDetNorm); ncnn::Mat out; @@ -390,7 +396,7 @@ std::vector asst::OcrPackNcnn::detect(const cv::Mat& ex.input("in0", in); if (ex.extract("out0", out) != 0) { Log.error("OcrPackNcnn det extract failed"); - return {}; + return { }; } } ncnn::Mat plane = out.channel(0); @@ -405,11 +411,11 @@ std::vector asst::OcrPackNcnn::detect(const cv::Mat& const int limit = std::min(static_cast(contours.size()), kDetMaxCandidates); for (int ci = 0; ci < limit; ++ci) { const auto& contour = contours[ci]; - if (contour.size() < 4) { + if (contour.size() <= 2) { continue; } cv::RotatedRect rr = cv::minAreaRect(contour); - if (std::min(rr.size.width, rr.size.height) < kDetMinSize) { + if (std::max(rr.size.width, rr.size.height) < kDetMinSize) { continue; } cv::Point2f raw[4]; @@ -417,7 +423,7 @@ std::vector asst::OcrPackNcnn::detect(const cv::Mat& cv::Point2f box[4]; order_box(raw, box); - const float score = box_score(prob, box); + const float score = poly_score(prob, contour); if (score < kDetBoxThresh) { continue; } @@ -430,7 +436,7 @@ std::vector asst::OcrPackNcnn::detect(const cv::Mat& const float dist = area * kDetUnclipRatio / length; cv::RotatedRect rr2(rr.center, cv::Size2f(rr.size.width + 2.f * dist, rr.size.height + 2.f * dist), rr.angle); - if (std::min(rr2.size.width, rr2.size.height) < kDetMinSize + 2) { + if (std::max(rr2.size.width, rr2.size.height) < kDetMinSize + 2) { continue; } cv::Point2f raw2[4]; @@ -442,6 +448,12 @@ std::vector asst::OcrPackNcnn::detect(const cv::Mat& p.x = std::clamp(p.x / ratio_w, 0.f, static_cast(src_w)); p.y = std::clamp(p.y / ratio_h, 0.f, static_cast(src_h)); } + // 对齐 fastdeploy FilterTagDetRes:丢弃过细框 + const int rect_w = static_cast(cv::norm(db.pts[0] - db.pts[1])); + const int rect_h = static_cast(cv::norm(db.pts[0] - db.pts[3])); + if (rect_w <= 4 || rect_h <= 4) { + continue; + } results.push_back(db); } @@ -476,8 +488,9 @@ cv::Mat asst::OcrPackNcnn::crop_rotated(const cv::Mat& image, const DetBox& box) { 0, static_cast(h) } }; const cv::Mat m = cv::getPerspectiveTransform(src, dst); cv::Mat crop; - cv::warpPerspective(image, crop, m, cv::Size(w, h)); - if (h > 0 && static_cast(w) / h < 0.7f) { + // 对齐 fastdeploy GetRotateCropImage BORDER_REPLICATE + h>=1.5w 旋转 + cv::warpPerspective(image, crop, m, cv::Size(w, h), cv::INTER_LINEAR, cv::BORDER_REPLICATE); + if (static_cast(h) >= static_cast(w) * 1.5f) { cv::rotate(crop, crop, cv::ROTATE_90_COUNTERCLOCKWISE); } return crop; @@ -495,10 +508,11 @@ std::pair asst::OcrPackNcnn::recognize_line(const cv::Mat& l cv::Mat resized; cv::resize(line, resized, cv::Size(resized_w, kRecImgH)); - cv::Mat canvas(kRecImgH, kRecImgW, CV_8UC3, cv::Scalar(128, 128, 128)); + // 对齐 fastdeploy + cv::Mat canvas(kRecImgH, kRecImgW, CV_8UC3, cv::Scalar(127, 127, 127)); resized.copyTo(canvas(cv::Rect(0, 0, resized_w, kRecImgH))); - ncnn::Mat in = ncnn::Mat::from_pixels(canvas.data, ncnn::Mat::PIXEL_BGR2RGB, kRecImgW, kRecImgH); + ncnn::Mat in = ncnn::Mat::from_pixels(canvas.data, ncnn::Mat::PIXEL_BGR, kRecImgW, kRecImgH); in.substract_mean_normalize(kRecMean, kRecNorm); ncnn::Mat out; @@ -555,7 +569,7 @@ std::pair asst::OcrPackNcnn::recognize_line(const cv::Mat& l asst::OcrPackNcnn::ResultsVec asst::OcrPackNcnn::recognize(const cv::Mat& image_in, bool without_det) { if (!m_loaded) { - return {}; + return { }; } cv::Mat image = ensure_continuous_bgr(image_in); @@ -568,6 +582,12 @@ asst::OcrPackNcnn::ResultsVec asst::OcrPackNcnn::recognize(const cv::Mat& image_ } const std::vector boxes = detect(image); + if (boxes.empty()) { + // 对齐 fastdeploy PPOCRv2/v3 det 无框时整图当一行送 rec 小 roi 不处理直接就爆了 + auto [text, score] = recognize_line(image); + results.emplace_back(Rect(0, 0, image.cols, image.rows), score, std::move(text)); + return results; + } for (const auto& box : boxes) { cv::Mat crop = crop_rotated(image, box); if (crop.empty()) {