You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

963 lines
24 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*
* 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 <sys/time.h>
#include <stdio.h>
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string>
#include <sstream>
#include <sys/statfs.h>
#include <stdlib.h>
#include <fstream>
#include <thread>
#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<int>(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<double>(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<cv::Point2f> CheckUtil::sort_vertices(cv::RotatedRect rrect) {
cv::Point2f pts[4];
rrect.points(pts);
std::vector<cv::Point2f> 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<cv::Point> CheckUtil::getLargestContourROI(const cv::Mat &binaryImg, bool &found)
{
std::vector<std::vector<cv::Point>> contours;
std::vector<cv::Vec4i> hierarchy;
// 查找轮廓
cv::findContours(binaryImg.clone(), contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
found = false;
if (contours.empty())
{
return std::vector<cv::Point>(); // 返回空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<cv::Point>();
}
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<cv::Rect> &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_<double>(3, 1) << point.x, point.y, 1);
cv::Mat result_mat = transform_matrix * point_mat;
return cv::Point2f(result_mat.at<double>(0), result_mat.at<double>(1));
}
int CheckUtil::GetMaskProtrusions(const cv::Mat &binMask, std::vector<Mask_Protrusion_> &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<int>(i, cv::CC_STAT_AREA);
if (nArea > nMainArea)
{
nMainArea = nArea;
nMainIdx = i;
}
}
// 3、裁剪到主体外接矩形,缩小后续扫描范围
cv::Rect boxMain(stats.at<int>(nMainIdx, cv::CC_STAT_LEFT), stats.at<int>(nMainIdx, cv::CC_STAT_TOP),
stats.at<int>(nMainIdx, cv::CC_STAT_WIDTH), stats.at<int>(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<int> posList; // 有前景的行号/列号
std::vector<int> 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<uchar>(i);
for (int x = 0; x < W; x++)
{
if (p[x])
{
nV = x;
break;
}
}
}
else if (nSide == 1) // 右:自右向左
{
const uchar *p = sub.ptr<uchar>(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<uchar>(y)[i])
{
nV = y;
break;
}
}
}
else // 下:自下向上
{
for (int y = H - 1; y >= 0; y--)
{
if (sub.ptr<uchar>(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;
}