|
|
/*
|
|
|
* 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;
|
|
|
} |