/* * FileName:CoreLogicFactory.cpp * Version:V1.0 * Description: * Created On:Mon Sep 10 11:13:16 UTC 2018 * Modified date: * Author:Sky */ #include "CheckUtil.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include "snowflake.hpp" int _sysmkdir_2(const std::string &dir) { int ret = mkdir(dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); if (ret && errno == EEXIST) { // printf("dir[%s] already exist.\n", dir.c_str()); } else if (ret) { printf("create dir[%s] error: %d %s\n", dir.c_str(), ret, strerror(errno)); return -1; } else { printf("create dir[%s] success.\n", dir.c_str()); } return 0; } std::string __getParentDir_2(const std::string &dir) { std::string pdir = dir; if (pdir.length() < 1 || (pdir[0] != '/')) { return ""; } while (pdir.length() > 1 && (pdir[pdir.length() - 1] == '/')) pdir = pdir.substr(0, pdir.length() - 1); pdir = pdir.substr(0, pdir.find_last_of('/')); return pdir; } long CheckUtil::getcurTime() { struct timeval tv; gettimeofday(&tv, NULL); return ((long)tv.tv_sec) * 1000 + ((long)tv.tv_usec) / 1000; } std::string CheckUtil::getCurrentDate() { struct timeval tv; gettimeofday(&tv, NULL); // 获取当前时间 // 将 tv_sec 转换为 time_t 类型,代表自1970年1月1日起的秒数 time_t raw_time = tv.tv_sec; // 使用 localtime 将 time_t 转换为 tm 结构体 struct tm *time_info = localtime(&raw_time); // 使用字符串流格式化为 "YYYY-MM-DD" std::ostringstream oss; oss << (time_info->tm_year + 1900) << "-" // tm_year 是从1900年开始的 << (time_info->tm_mon + 1) << "-" // tm_mon 是从0开始的 << time_info->tm_mday; // tm_mday 是当前月的天数 return oss.str(); // 返回日期字符串 } std::string CheckUtil::getCurTimeHMS() { struct timeval tv; gettimeofday(&tv, NULL); // 获取当前时间 time_t raw_time = tv.tv_sec; struct tm *time_info = localtime(&raw_time); int millisec = tv.tv_usec / 1000; // 微秒转毫秒 std::ostringstream oss; oss << std::setfill('0') << std::setw(2) << time_info->tm_hour << ":" << std::setfill('0') << std::setw(2) << time_info->tm_min << ":" << std::setfill('0') << std::setw(2) << time_info->tm_sec << "." << std::setfill('0') << std::setw(3) << millisec; return oss.str(); // 返回格式 "HH:MM:SS.mmm" } std::string CheckUtil::Op_float2String(float nvalue) { char buffer[20]; sprintf(buffer, "%.1f", nvalue); std::string st1 = buffer; return st1; } int64_t CheckUtil::getSnowId() { using snowflake_t = snowflake<1534832906275L, std::mutex>; static snowflake_t uuid; static bool bInit = false; if (!bInit) { uuid.init(1, 1); bInit = true; } return uuid.nextid(); return 0; } bool CheckUtil::JudgRect(cv::Rect roi, int img_w, int img_h) { if (img_w <= 0 || img_h <= 0) { return false; } if (roi.x < 0 || roi.x >= img_w) { return false; } if (roi.width <= 0 || roi.width >= img_w) { return false; } if (roi.y < 0 || roi.y >= img_h) { return false; } if (roi.height <= 0 || roi.height >= img_h) { return false; } if (roi.x + roi.width >= img_w) { return false; } if (roi.y + roi.height >= img_h) { return false; } return true; } bool CheckUtil::JudgRect_SZ(cv::Rect roi, int w, int h) { if (roi.x < 0 || roi.x >= w) { return false; } if (roi.width <= 0 || roi.width != w) { return false; } if (roi.y < 0 || roi.y >= h) { return false; } if (roi.height <= 0 || roi.height != h) { return false; } return true; } bool CheckUtil::compareIgnoreCase(const std::string &str1, const std::string &str2) { // 将 str1 和 str2 转换为小写后进行比较 std::string lower_str1 = str1; std::string lower_str2 = str2; // 使用 std::transform 将字符串转换为小写 std::transform(lower_str1.begin(), lower_str1.end(), lower_str1.begin(), ::tolower); std::transform(lower_str2.begin(), lower_str2.end(), lower_str2.begin(), ::tolower); // 比较两个转换后的字符串 return lower_str1 == lower_str2; } bool CheckUtil::RoiInImg(cv::Rect roi, cv::Mat img) { if (roi.width <= 0 || roi.height <= 0) { return false; } if ((roi & cv::Rect(0, 0, img.cols, img.rows)) == roi) { return true; } return false; } int CheckUtil::printROI(cv::Rect roi, std::string str) { printf("%s x %d y %d w %d h %d\n", str.c_str(), roi.x, roi.y, roi.width, roi.height); return 0; } float CheckUtil::CalIoU(cv::Rect rect1, cv::Rect rect2) { // 计算交集区域 cv::Rect intersection = rect1 & rect2; // 计算并集区域 cv::Rect union_rect = rect1 | rect2; // 计算交集区域和并集区域的面积 double intersection_area = intersection.area(); double union_area = union_rect.area(); // 计算IoU值 double iou = intersection_area / union_area; return iou; } float CheckUtil::CalIoU_t(cv::Rect rect1, cv::Rect rect2) { // 计算交集区域 cv::Rect intersection = rect1 & rect2; // 计算并集区域 cv::Rect union_rect = rect1; // 计算交集区域和并集区域的面积 double intersection_area = intersection.area(); double union_area = union_rect.area(); // 计算IoU值 double iou = intersection_area / union_area; return iou; } int CheckUtil::CheckRect(cv::Rect &roi, int img_w, int img_h) { if (roi.x < 0 || roi.x >= img_w) { roi.x = 0; } if (roi.y < 0 || roi.y >= img_h) { roi.y = 0; } if (roi.width <= 0 || roi.width > img_w) { roi.width = 1; } if (roi.height <= 0 || roi.height > img_h) { roi.height = 1; } if (roi.x + roi.width > img_w) { roi.x = img_w - roi.width; if (roi.x < 0) { roi.x = 0; roi.width = img_w; } } if (roi.y + roi.height > img_h) { roi.y = img_h - roi.height; if (roi.y < 0) { roi.y = 0; roi.height = img_h; } } return 0; } int CheckUtil::SizeRect(cv::Rect &roi, int img_w, int img_h, int addw, int addh) { if (roi.width + 2 * addw > img_w) { return 1; } if (roi.height + 2 * addh > img_h) { return 1; } int sx = roi.x - addw; int ex = roi.x + roi.width + addw; if (sx < 0) { sx = 0; } if (ex > img_w) { ex = img_w; } int sy = roi.y - addh; int ey = roi.y + roi.height + addh; if (sy < 0) { sy = 0; } if (ey > img_h) { ey = img_h; } roi.x = sx; roi.width = ex - sx; roi.y = sy; roi.height = ey - sy; return 0; } int CheckUtil::CalHj(const cv::Mat &img, const cv::Mat &mask, int b_value) { cv::Mat imgf; if (img.type() != CV_32F) img.convertTo(imgf, CV_32F); else imgf = img; // 2. 减去常数并取绝对值 cv::Mat absDiff = cv::abs(imgf - b_value); // 3. 用 mask 计算平均值 cv::Scalar meanValue123 = cv::mean(absDiff, mask); return meanValue123[0]; } int CheckUtil::CalHj(const cv::Mat &img, const cv::Mat &mask, const cv::Mat &backgroundimg) { cv::Mat imgf; if (img.type() != CV_32F) img.convertTo(imgf, CV_32F); else imgf = img; cv::Scalar meanValue = cv::mean(backgroundimg); int b_value = meanValue[0]; // 2. 减去常数并取绝对值 cv::Mat absDiff = cv::abs(imgf - b_value); // 3. 用 mask 计算平均值 cv::Scalar meanValue123 = cv::mean(absDiff, mask); return meanValue123[0]; } int CheckUtil::CalHjWeighted(const cv::Mat &img, const cv::Mat &mask, int b_value, float power) { cv::Mat imgf; if (img.type() != CV_32F) img.convertTo(imgf, CV_32F); else imgf = img; cv::Mat absDiff = cv::abs(imgf - b_value); cv::Mat weight; cv::pow(absDiff, power, weight); cv::Mat maskedDiff, maskedWeight; absDiff.copyTo(maskedDiff, mask); weight.copyTo(maskedWeight, mask); cv::Scalar weightedSum = cv::sum(maskedDiff.mul(weight)); cv::Scalar weightSum = cv::sum(maskedWeight); if (weightSum[0] < 1e-6) return 0; return static_cast(weightedSum[0] / weightSum[0]); } float CheckUtil::CalImgBrightness(cv::Mat imgRoi) { if (imgRoi.empty()) { return 0.0f; } cv::Mat mat_mean, mat_stddev; cv::meanStdDev(imgRoi, mat_mean, mat_stddev); // 求灰度图像的均值、均方差 float m = mat_mean.at(0, 0); return m; } float CheckUtil::Cal2PointAngle(cv::Point p_left, cv::Point p_right) { double angle = std::atan2(p_left.y - p_right.y, p_right.x - p_left.x); return angle * 180 / 3.1415926; } float CheckUtil::getLongAxisAngle(cv::RotatedRect rect) { // 获取宽度边对应的角度 float angle = rect.angle; // 判断长轴:若 width >= height,长轴是 width 边;否则是 height 边(垂直于 width) float long_axis_angle = (rect.size.width >= rect.size.height) ? angle : angle + 90.0f; // 将角度归一化到 [0, 180) 范围(因为直线方向没有正反) long_axis_angle = std::fmod(long_axis_angle, 180.0f); if (long_axis_angle < 0) { long_axis_angle += 180.0f; } return long_axis_angle; } std::vector CheckUtil::sort_vertices(cv::RotatedRect rrect) { cv::Point2f pts[4]; rrect.points(pts); std::vector vertices(pts, pts + 4); // 按 y 坐标升序排序 std::sort(vertices.begin(), vertices.end(), [](const cv::Point2f& a, const cv::Point2f& b) { return a.y < b.y; }); if (vertices[0].x > vertices[1].x) std::swap(vertices[0], vertices[1]); if (vertices[2].x > vertices[3].x) std::swap(vertices[2], vertices[3]); return vertices; } std::vector CheckUtil::getLargestContourROI(const cv::Mat &binaryImg, bool &found) { std::vector> contours; std::vector hierarchy; // 查找轮廓 cv::findContours(binaryImg.clone(), contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE); found = false; if (contours.empty()) { return std::vector(); // 返回空Point向量 } // 找到面积最大的轮廓 double maxArea = 0; int maxAreaIdx = -1; for (size_t i = 0; i < contours.size(); i++) { double area = cv::contourArea(contours[i]); if (area > maxArea) { maxArea = area; maxAreaIdx = i; } } // 如果没有找到有效轮廓(比如所有轮廓面积都为0) if (maxAreaIdx == -1 || maxArea <= 0) { return std::vector(); } found = true; // 返回最大轮廓的边界矩形 return contours[maxAreaIdx]; } std::string CheckUtil::GetRectString(cv::Rect rect) { std::string str = "[" + std::to_string(rect.x) + "," + std::to_string(rect.y) + "," + std::to_string(rect.width) + "," + std::to_string(rect.height) + "]"; return str; } int CheckUtil::CreateDir(const std::string &dir) { int ret = 0; if (dir.empty()) return -1; std::string pdir; if ((ret = _sysmkdir_2(dir)) == -1) { pdir = __getParentDir_2(dir); if ((ret = CreateDir(pdir)) == 0) { ret = CreateDir(dir); } } return ret; } bool CheckUtil::bcalDis(cv::Point p1, cv::Point p2, int disT) { double dis = std::sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y)); if (dis < disT) return true; return false; } double CheckUtil::calDis(cv::Point p1, cv::Point p2) { double dis = std::sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y)); return dis; } void CheckUtil::PrintRect(cv::Rect roi, std::string str) { printf("%s x %d y %d w %d h %d \n", str.c_str(), roi.x, roi.y, roi.width, roi.height); } int CheckUtil::cutSmallImg(cv::Mat img, std::vector &samllRoiList, cv::Rect config_roi, int config_SmallImg_Width, int config_SmallImg_Height, int config_MinOverlap_Width, int config_MinOverlap_Height) { if (img.empty()) { printf("error >>>> img.empty \n"); return 1; /* code */ } if (!RoiInImg(config_roi, img)) { printf("error >>>> roi != img size \n"); return 2; } if (config_SmallImg_Width <= 0 || config_SmallImg_Height <= 0 || config_SmallImg_Width > img.cols || config_SmallImg_Height > img.rows) { printf("error >>>>config_SmallImg_Width %d config_SmallImg_Height %d \n ", config_SmallImg_Width, config_SmallImg_Height); return 3; } if (config_MinOverlap_Width < 0 || config_MinOverlap_Height < 0 || config_MinOverlap_Width > img.cols || config_MinOverlap_Height > img.rows || config_MinOverlap_Width >= config_SmallImg_Width || config_MinOverlap_Height >= config_SmallImg_Height) { printf("error >>>>config_MinOverlap_Width %d config_MinOverlap_Height %d \n ", config_MinOverlap_Width, config_MinOverlap_Height); return 4; } int AI_Img_width = config_SmallImg_Width; int AI_Img_height = config_SmallImg_Height; int start_x = config_roi.x; int start_y = config_roi.y; int end_x = config_roi.width + config_roi.x; int end_y = config_roi.height + config_roi.y; // 有效图片 宽 高 int det_width = config_roi.width; int det_height = config_roi.height; if (AI_Img_width > det_width || AI_Img_height > det_height) { printf("error >>>>config_SmallImg_Width %d != roi width %d \n ", config_SmallImg_Width, det_width); printf("error >>>>config_SmallImg_Height %d != roi height %d \n ", config_SmallImg_Height, det_height); return 5; } // printf("config_SmallImg_Width %d config_SmallImg_Height %d \n ", config_SmallImg_Width, config_SmallImg_Height); // printf("config_MinOverlap_Width %d config_MinOverlap_Height %d \n ", config_MinOverlap_Width, config_MinOverlap_Height); /////////////////、计算宽度方向 块的个数 和 重叠 /////////////////////// // 可分为多少块 宽度度方向 float fBlocknum_x = det_width * 1.0f / AI_Img_width; // 块的个数 int nBlocknum_x = std::ceil(fBlocknum_x); // 如果 有重叠要求 if (config_MinOverlap_Width >= 0) { float fconfig_BlocknuNum_x = (det_width - AI_Img_width) * 1.0f / (config_SmallImg_Width - config_MinOverlap_Width) + 1; int nconfig_BlocknuNum_x = std::ceil(fconfig_BlocknuNum_x); if (nBlocknum_x < nconfig_BlocknuNum_x) { nBlocknum_x = nconfig_BlocknuNum_x; } } int use_MinOverlap_Width = 0; // 计算重叠率 if (nBlocknum_x > 1) { // 有多个块,要判断 块的重叠是否满足要求 int nSumLen_x = nBlocknum_x * AI_Img_width; // float fOverlap_x = (nSumLen_x - det_width) * 1.0f / (nBlocknum_x - 1); use_MinOverlap_Width = int(fOverlap_x); } // printf("nBlocknum_x %d use_MinOverlap_Width %d \n", nBlocknum_x, use_MinOverlap_Width); /////////////////、计算高度方向 块的个数 和 重叠 /////////////////////// // 可分为多少块 高度方向 float fBlocknum_y = det_height * 1.0f / AI_Img_height; // 块的个数 int nBlocknum_y = std::ceil(fBlocknum_y); // 如果 有重叠要求 if (config_MinOverlap_Height >= 0) { float fconfig_BlocknuNum_y = (det_height - AI_Img_height) * 1.0f / (config_SmallImg_Height - config_MinOverlap_Height) + 1; int nconfig_BlocknuNum_y = std::ceil(fconfig_BlocknuNum_y); if (nBlocknum_y < nconfig_BlocknuNum_y) { nBlocknum_y = nconfig_BlocknuNum_y; } } int use_MinOverlap_Height = 0; // 计算重叠率 if (nBlocknum_y > 1) { // 有多个块,要判断 块的重叠是否满足要求 int nSumLen_y = nBlocknum_y * AI_Img_height; // float fOverlap_y = (nSumLen_y - det_height) * 1.0f / (nBlocknum_y - 1); use_MinOverlap_Height = int(fOverlap_y); } // printf("nBlocknum_y %d use_MinOverlap_Height %d \n", nBlocknum_y, use_MinOverlap_Height); int cut_sy = start_y; int cut_ey = start_y + AI_Img_height; for (int iy = 0; iy < nBlocknum_y; iy++) { int nleny = end_y - cut_ey; int cut_sx = start_x; int cut_ex = start_x + AI_Img_width; for (int ix = 0; ix < nBlocknum_x; ix++) { cv::Rect roi; roi.x = cut_sx; roi.y = cut_sy; roi.width = AI_Img_width; roi.height = AI_Img_height; samllRoiList.push_back(roi); // 剩余长度 int nlenx = end_x - cut_ex; if (nlenx > AI_Img_width) { cut_sx = cut_sx + AI_Img_width - use_MinOverlap_Width; cut_ex = cut_sx + AI_Img_width; } else { cut_sx = end_x - AI_Img_width; cut_ex = cut_sx + AI_Img_width; } } if (nleny > AI_Img_height) { cut_sy = cut_sy + AI_Img_height - use_MinOverlap_Height; cut_ey = cut_sy + AI_Img_height; } else { cut_sy = end_y - AI_Img_height; cut_ey = cut_sy + AI_Img_height; } } return 0; } cv::Point2f CheckUtil::transformPoint(const cv::Point2f &point, const cv::Mat &transform_matrix) { // 验证变换矩阵有效性 if (transform_matrix.empty() || transform_matrix.rows != 2 || transform_matrix.cols != 3) { std::cerr << "Error: Invalid transformation matrix! Returning original point." << std::endl; return point; } // 使用矩阵乘法进行点变换 cv::Mat point_mat = (cv::Mat_(3, 1) << point.x, point.y, 1); cv::Mat result_mat = transform_matrix * point_mat; return cv::Point2f(result_mat.at(0), result_mat.at(1)); } int CheckUtil::GetMaskProtrusions(const cv::Mat &binMask, std::vector &protrusions, int nJumpThresh, int nMinRun, int nMinDepth, int nMaxRun) { protrusions.clear(); if (binMask.empty()) { return 1; } // 1、二值化(兼容 0/1 与 0/255 掩膜) cv::Mat gray; if (binMask.channels() != 1) { cv::cvtColor(binMask, gray, cv::COLOR_BGR2GRAY); } else { gray = binMask; } cv::Mat maskFull; cv::threshold(gray, maskFull, 0, 255, cv::THRESH_BINARY); // 1.1 大掩膜先降采样再分析:凸起尺寸远大于降采样误差,可显著降低耗时 int nScale = 1; while (nScale < 4 && std::max(maskFull.cols, maskFull.rows) / nScale > 2000) { nScale *= 2; } cv::Mat mask; if (nScale > 1) { cv::resize(maskFull, mask, cv::Size(maskFull.cols / nScale, maskFull.rows / nScale), 0, 0, cv::INTER_NEAREST); } else { mask = maskFull; } // 阈值按降采样比例换算到工作图尺度 nJumpThresh = std::max(2, nJumpThresh / nScale); nMinRun = std::max(3, nMinRun / nScale); nMinDepth = std::max(2, nMinDepth / nScale); // 2、只保留最大连通域,避免背景杂物参与边界统计 cv::Mat labels, stats, centroids; int nLabels = cv::connectedComponentsWithStats(mask, labels, stats, centroids, 8, CV_32S); if (nLabels <= 1) { return 2; } int nMainIdx = 1; int nMainArea = 0; for (int i = 1; i < nLabels; i++) { int nArea = stats.at(i, cv::CC_STAT_AREA); if (nArea > nMainArea) { nMainArea = nArea; nMainIdx = i; } } // 3、裁剪到主体外接矩形,缩小后续扫描范围 cv::Rect boxMain(stats.at(nMainIdx, cv::CC_STAT_LEFT), stats.at(nMainIdx, cv::CC_STAT_TOP), stats.at(nMainIdx, cv::CC_STAT_WIDTH), stats.at(nMainIdx, cv::CC_STAT_HEIGHT)); boxMain &= cv::Rect(0, 0, mask.cols, mask.rows); if (boxMain.width <= 0 || boxMain.height <= 0) { return 2; } cv::Mat sub = (labels(boxMain) == nMainIdx); // 0/255 const int H = sub.rows; const int W = sub.cols; if (nMaxRun <= 0) { nMaxRun = std::max(100, std::min(H, W) / 3); } // 4、逐边抠凸起: // 主体近似矩形,矩形边上的凸起会让该边的"边界值"先突变到外侧(进入凸起)、再突变回主体(离开凸起); // 区间内的边界值相对"首尾边界值线性插值"得到的基线持续外偏,即判定为一个凸起。 for (int nSide = 0; nSide < 4; nSide++) { const bool bRowScan = (nSide < 2); // 左右边按行扫描,上下边按列扫描 const int nLine = bRowScan ? H : W; // 扫描的行数/列数 const int nDir = (nSide == 0 || nSide == 2) ? 1 : -1; // 凸起内边界值变小(左侧/上侧)取 +1,变大取 -1 // 4.1 从每条 行/列 的外侧向内找第一个前景像素,得到该边的边界值 profile std::vector posList; // 有前景的行号/列号 std::vector valList; // 对应的边界值 posList.reserve(nLine); valList.reserve(nLine); for (int i = 0; i < nLine; i++) { int nV = -1; if (nSide == 0) // 左:自左向右 { const uchar *p = sub.ptr(i); for (int x = 0; x < W; x++) { if (p[x]) { nV = x; break; } } } else if (nSide == 1) // 右:自右向左 { const uchar *p = sub.ptr(i); for (int x = W - 1; x >= 0; x--) { if (p[x]) { nV = x; break; } } } else if (nSide == 2) // 上:自上向下 { for (int y = 0; y < H; y++) { if (sub.ptr(y)[i]) { nV = y; break; } } } else // 下:自下向上 { for (int y = H - 1; y >= 0; y--) { if (sub.ptr(y)[i]) { nV = y; break; } } } if (nV >= 0) { posList.push_back(i); valList.push_back(nV); } } const int n = (int)valList.size(); if (n < nMinRun + 2) { continue; } // 4.2 配对"进入/离开"突变,扣除该边上的凸起 int nUsedTo = -1; // 已统计过的凸起末端,避免同一个凸起重复输出 for (int s = 0; s + 1 < n; s++) { if (s <= nUsedTo) { continue; } // 进入凸起:边界值朝外侧突变 if ((valList[s + 1] - valList[s]) * nDir >= -nJumpThresh) { continue; } // 离开凸起:在其后 nMaxRun 范围内找第一个反向突变 int e = -1; for (int j = s + nMinRun; j + 1 < n && j - s <= nMaxRun; j++) { if ((valList[j + 1] - valList[j]) * nDir > nJumpThresh) { e = j; break; } } if (e < 0) { continue; } // 4.3 区间内的边界值应持续超出"首尾边界值线性插值"得到的基线 const int kk = e - s; int nDevOk = 0; float fMaxDev = 0.0f; int nLow = valList[s + 1]; int nHigh = valList[s + 1]; for (int i = 1; i <= kk; i++) { float fBase = valList[s] + (valList[e + 1] - valList[s]) * (float)i / (float)(kk + 1); float fDev = (fBase - valList[s + i]) * nDir; fMaxDev = std::max(fMaxDev, fDev); if (fDev >= nMinDepth) { nDevOk++; } nLow = std::min(nLow, valList[s + i]); nHigh = std::max(nHigh, valList[s + i]); } if (fMaxDev < nMinDepth) { continue; } // 凸起内大部分采样点都要明显外偏,避免把主体的斜边/圆角当成凸起 if (nDevOk < (int)(kk * 0.8f)) { continue; } // 4.4 计算凸起外接矩形(先算在 sub 图上的位置) const int nLen = posList[e] - posList[s + 1] + 1; // 沿该边的长度 cv::Rect roiSub; if (nSide == 0 || nSide == 2) // 左/上:外侧取小值,主体边缘取大值 { int nOut = nLow; int nInner = std::max(valList[s], valList[e + 1]); if (nSide == 0) { roiSub = cv::Rect(nOut, posList[s + 1], nInner - nOut + 1, nLen); } else { roiSub = cv::Rect(posList[s + 1], nOut, nLen, nInner - nOut + 1); } } else // 右/下:外侧取大值,主体边缘取小值 { int nOut = nHigh; int nInner = std::min(valList[s], valList[e + 1]); if (nSide == 1) { roiSub = cv::Rect(nInner, posList[s + 1], nOut - nInner + 1, nLen); } else { roiSub = cv::Rect(posList[s + 1], nInner, nLen, nOut - nInner + 1); } } roiSub &= cv::Rect(0, 0, W, H); if (roiSub.width <= 0 || roiSub.height <= 0) { continue; } Mask_Protrusion_ protr; protr.nSide = nSide; // 从工作图尺度还原到输入图尺度 protr.roi = cv::Rect((roiSub.x + boxMain.x) * nScale, (roiSub.y + boxMain.y) * nScale, roiSub.width * nScale, roiSub.height * nScale); protr.roi &= cv::Rect(0, 0, binMask.cols, binMask.rows); if (protr.roi.width <= 0 || protr.roi.height <= 0) { continue; } protr.fLen = (float)nLen * nScale; protr.fDepth = fMaxDev * nScale; int nMaskArea = cv::countNonZero(sub(roiSub)); protr.fArea = (float)nMaskArea * nScale * nScale; protr.fFillRatio = (float)nMaskArea / (float)(roiSub.width * roiSub.height); protrusions.push_back(protr); nUsedTo = e; // 该凸起区间不再重复统计 } } return 0; }