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.

684 lines
22 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.

/*
* @Author: xiewenji 527774126@qq.com
* @Date: 2025-09-11 15:32:52
* @LastEditors: xiewenji 527774126@qq.com
* @LastEditTime: 2025-09-22 16:56:41
* @FilePath: /BOE_ZB_PLATE_Detect/AlgorithmModule/src/AI_Edge_Algin.cpp
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
#include "AI_Edge_Algin.h"
#include "CheckErrorCodeDefine.hpp"
#define EDGE_GPU 0
#include <opencv2/opencv.hpp> // OpenCV核心功能
#include <opencv2/imgproc.hpp> // 图像处理模块
#include <vector> // 向量容器
#include <iostream> // 输入输出流
/**
* @class SimilarityTransform
* @brief 用于计算和存储相似变换(旋转、缩放、平移)
*
* 通过两个点对计算模板图像到检测图像的相似变换
*/
class SimilarityTransform
{
public:
/**
* @brief 构造函数,使用两个点对初始化变换矩阵
*
* @param template_p1 模板点1
* @param template_p2 模板点2
* @param detected_d1 检测点1
* @param detected_d2 检测点2
*/
SimilarityTransform(const cv::Point2f &template_p1,
const cv::Point2f &template_p2,
const cv::Point2f &detected_d1,
const cv::Point2f &detected_d2)
{
std::vector<cv::Point2f> template_pts = {template_p1, template_p2};
std::vector<cv::Point2f> detected_pts = {detected_d1, detected_d2};
// 计算相似变换矩阵
transform_matrix_ = cv::estimateAffinePartial2D(template_pts, detected_pts);
if (transform_matrix_.empty())
{
valid_ = false;
std::cerr << "Error: Failed to calculate transformation matrix." << std::endl;
}
else
{
valid_ = true;
extractParameters();
}
}
/**
* @brief 检查变换矩阵是否有效
* @return bool 变换是否有效
*/
bool isValid() const { return valid_; }
/**
* @brief 获取旋转角度(度)
* @return double 旋转角度(度)
*/
double getRotationAngle() const { return rotation_angle_; }
/**
* @brief 获取缩放比例
* @return double 缩放比例
*/
double getScaleFactor() const { return scale_factor_; }
/**
* @brief 获取平移分量
* @return cv::Point2f 平移向量(tx, ty)
*/
cv::Point2f getTranslation() const
{
return cv::Point2f(transform_matrix_.at<double>(0, 2),
transform_matrix_.at<double>(1, 2));
}
/**
* @brief 转换点坐标
*
* @param point 输入点(模板坐标系)
* @return cv::Point2f 转换后的点(检测图像坐标系)
*/
cv::Point2f transformPoint(const cv::Point2f &point) const
{
if (!valid_)
{
std::cerr << "Warning: Using invalid transform! 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));
}
/**
* @brief 批量转换点坐标
*
* @param points 输入点集(模板坐标系)
* @return std::vector<cv::Point2f> 转换后的点集(检测图像坐标系)
*/
std::vector<cv::Point2f> transformPoints(const std::vector<cv::Point2f> &points) const
{
if (!valid_)
{
std::cerr << "Warning: Using invalid transform! Returning original points." << std::endl;
return points;
}
std::vector<cv::Point2f> result;
cv::transform(points, result, transform_matrix_);
return result;
}
/**
* @brief 获取变换矩阵
* @return cv::Mat 2x3变换矩阵
*/
cv::Mat getTransformMatrix() const { return transform_matrix_; }
private:
/**
* @brief 从变换矩阵中提取旋转角度和缩放比例
*/
void extractParameters()
{
double a = transform_matrix_.at<double>(0, 0);
double b = transform_matrix_.at<double>(0, 1);
// 计算旋转角度(弧度转角度)
rotation_angle_ = std::atan2(b, a) * 180.0 / CV_PI;
// 计算缩放比例
scale_factor_ = std::sqrt(a * a + b * b);
}
cv::Mat transform_matrix_; // 2x3 变换矩阵
bool valid_ = false; // 变换是否有效
double rotation_angle_ = 0; // 旋转角度(度)
double scale_factor_ = 1; // 缩放比例
};
/**
* @brief 独立转换函数(使用变换矩阵)
*
* @param point 输入点(模板坐标系)
* @param transform_matrix 2x3变换矩阵
* @return cv::Point2f 转换后的点(检测图像坐标系)
*/
cv::Point2f 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));
}
AI_Edge_Algin::AI_Edge_Algin(std::shared_ptr<DetLog> &log_ref)
: m_pdetlog(log_ref)
{
m_bInitialized = false;
m_bModelSucc = false;
m_bModel_Mark_Succ = false;
m_bshowimg = false;
m_strRootPath_Big = "/home/aidlux/BOE/Algin/";
m_strRootPath_Mark = "/home/aidlux/BOE/MarkLine/";
creatsavedir();
std::string m_strSavePath;
m_pImageStorage = ImageStorage::getInstance();
}
AI_Edge_Algin::~AI_Edge_Algin()
{
}
int AI_Edge_Algin::Init(OtherDet_Config *pOtherDet_Config)
{
m_pOtherDet_Config = pOtherDet_Config;
AI_Factory = AIFactory::GetInstance();
m_bInitialized = true;
return 0;
}
int AI_Edge_Algin::Detect(const cv::Mat &img, DetConfig *pDetConfig, std::shared_ptr<Edge_AI_Result> &pCheckResult_Aling)
{
m_pCheckResult_Aling = std::make_shared<Edge_AI_Result>();
pCheckResult_Aling = m_pCheckResult_Aling;
m_pdetlog->AddCheckstr(PrintLevel_2, DET_LOG_LEVEL_3, "AI_Edge_Algin ", "start");
m_pDetConfig = pDetConfig;
static int erridx = 0;
std::string str_error = "";
// 保存过程图片
if (m_pDetConfig->IsSaveProcessImg())
{
erridx++;
if (erridx > 9999999)
{
erridx = 0;
}
str_error = "/home/aidlux/BOE/Edge/Error/" + std::to_string(erridx) + "_src.png";
}
if (img.empty())
{
return 1;
}
// 1、初步定位 找到产品大致区域
int re = 0;
cv::RotatedRect Big_roi;
cv::RotatedRect Small_roi;
re = Get_Edge(0, img, pDetConfig, m_pDetConfig->strChannel, Big_roi);
if (re != 0)
{
m_pdetlog->AddCheckstr(PrintLevel_2, DET_LOG_LEVEL_3, "AI_Edge_Algin ", "AICheck_Edge_Big----error %d ", re);
if (m_pDetConfig->IsSaveProcessImg())
{
cv::imwrite(str_error, img);
}
return re;
}
re = Get_Edge(1, img, pDetConfig, m_pDetConfig->strChannel, Small_roi);
if (re != 0)
{
m_pdetlog->AddCheckstr(PrintLevel_2, DET_LOG_LEVEL_3, "AI_Edge_Algin ", "AICheck_Edge_Small----error %d ", re);
if (m_pDetConfig->IsSaveProcessImg())
{
cv::imwrite(str_error, img);
}
return re;
}
m_pCheckResult_Aling->bigroi = Big_roi;
m_pCheckResult_Aling->smallroi = Small_roi;
pCheckResult_Aling = m_pCheckResult_Aling;
// return 1;
return 0;
}
int AI_Edge_Algin::SaveSmallImg(const cv::Mat &img, const cv::Mat &mask, cv::Rect roi)
{
// 是否要保存中间过程的小图
if (m_pDetConfig->IsSaveProcessImg())
{
static int svsmallidx = 0;
svsmallidx++;
if (svsmallidx > 9999999)
{
svsmallidx = 0;
/* code */
}
bool bssss = false;
if (m_pDetConfig->saveProcessImg == Save_Filter)
{
// 4. 查找轮廓
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(mask.clone(), contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
if (contours.size() > 1)
{
bssss = true;
}
else if (contours.size() == 1)
{
int pointNum = 0;
for (int i = 0; i < contours.size(); i++)
{
pointNum += contours[i].size();
}
// printf("pointNum============== %d\n", pointNum);
if (pointNum > 30)
{
bssss = true;
}
}
}
else if (m_pDetConfig->saveProcessImg == Save_ALL)
{
bssss = true;
}
if (bssss)
{
std::string str1 = "/home/aidlux/BOE/Edge/Smasll/" + std::to_string(svsmallidx) + "_in.png";
std::string str2 = "/home/aidlux/BOE/Edge/Smasll/" + std::to_string(svsmallidx) + "_in_mask.png";
// std::string st3 = "/home/aidlux/BOE/Edge/Smasll/" + std::to_string(svsmallidx) + "_in_show.png";
cv::imwrite(str1, img);
cv::imwrite(str2, mask);
// cv::Mat showsss = temDet + smask * 0.4;
// cv::imwrite(st3, showsss);
}
}
return 0;
}
int AI_Edge_Algin::InitModel_ALL()
{
m_bModelSucc = true;
return 0;
}
int AI_Edge_Algin::Get_Edge(int AIModel_type, const cv::Mat &img, DetConfig *pDetConfig, std::string strChannel, cv::RotatedRect &Roi)
{
std::shared_ptr<AIModel_Base> pBackPlate_Align;
switch (AIModel_type)
{
case 0:
pBackPlate_Align = AI_Factory->Align_Outer;
break;
case 1:
pBackPlate_Align = AI_Factory->Align_Inner;
break;
default:
pBackPlate_Align = AI_Factory->Align_Outer;
break;
}
cv::Size sz;
sz.width = pBackPlate_Align->input_0.width;
sz.height = pBackPlate_Align->input_0.height;
cv::Mat detImg;
cout<< pDetConfig->strChannel << ": " << "---Get_Edge-resize-" << to_string(AIModel_type) <<"-- ";
cout << "imgSize: " << img.size() << ", " << "detImgSize: " << detImg.size() << ", " << "szSize: " << sz << endl;
cv::resize(img, detImg, sz);
int re = 0;
cv::Mat mask;
if (detImg.channels() != 1)
{
cv::cvtColor(detImg, detImg, cv::COLOR_RGB2GRAY);
}
re = pBackPlate_Align->AIDet(detImg, mask);
if (re != 0)
{
m_pdetlog->AddCheckstr(PrintLevel_3, DET_LOG_LEVEL_3, "AI_Edge_Algin ", "AICheck_Edge_%d----error %d ", AIModel_type, re);
int re123 = 100 + re;
return re123;
}
if (pDetConfig->pBaseCheckFunction->saveImg.bSaveAlginImg)
{
creatsavedir();
static int sdk = 0;
std::string str = m_strSavePath_Big + std::to_string(sdk) + "_" + pDetConfig->strChannel + "_Img.png";
int sr = m_pImageStorage->addImage(str, detImg);
if (sr == 0)
{
str = m_strSavePath_Big + std::to_string(sdk) + "_" + pDetConfig->strChannel + "_Img_mask.png";
int sr = m_pImageStorage->addImage(str, mask, true);
sdk++;
if (sdk > 99999)
{
sdk = 0;
/* code */
}
}
}
// if (m_pDetConfig->bSaveResultImg)
{
cv::imwrite(strChannel +"_edge_"+ to_string(AIModel_type) +"_in.png", detImg);
cv::imwrite(strChannel +"_edge_"+ to_string(AIModel_type) +"_out_mask.png", mask);
}
// 找到最大轮廓
bool found;
std::vector<cv::Point> boundingBox = CheckUtil::getLargestContourROI(mask, found);
if (!found)
{
m_pdetlog->AddCheckstr(PrintLevel_3, DET_LOG_LEVEL_3, "AI_Edge_Algin ", "No contours found!----error ");
return 4;
}
// 边缘轮廓点集还原为原图坐标并计算得到RotateROI
if (sz.width <= 0 || sz.height <= 0)
{
m_pdetlog->AddCheckstr(PrintLevel_3, DET_LOG_LEVEL_3, "AI_Edge_Algin ", "invalid model input size!----error ");
return 5;
}
// 检测小图 到 原图 的缩放比例
const float scale_x = static_cast<float>(img.cols) / sz.width;
const float scale_y = static_cast<float>(img.rows) / sz.height;
// 轮廓点还原到原图坐标系
std::vector<cv::Point2f> srcContour;
srcContour.reserve(boundingBox.size());
for (const auto &pt : boundingBox)
{
srcContour.emplace_back(pt.x * scale_x, pt.y * scale_y);
}
if (srcContour.size() < 3)
{
m_pdetlog->AddCheckstr(PrintLevel_3, DET_LOG_LEVEL_3, "AI_Edge_Algin ", "contour points too few!----error ");
return 6;
}
Roi = cv::minAreaRect(srcContour);
return 0;
}
int AI_Edge_Algin::creatsavedir()
{
std::string curDate = CheckUtil::getCurrentDate();
if (curDate == m_strLastDate)
{
return 0;
}
m_strLastDate = curDate;
m_strSavePath_Big = m_strRootPath_Big + curDate + "/";
m_strSavePath_Mark = m_strRootPath_Mark + curDate + "/";
CheckUtil::CreateDir(m_strSavePath_Big);
CheckUtil::CreateDir(m_strSavePath_Mark);
return 0;
return 0;
}
Image_Feature_Algin::Image_Feature_Algin()
{
}
Image_Feature_Algin::~Image_Feature_Algin()
{
}
int Image_Feature_Algin::Detect(DetConfig *pDetConfig, Align_Result *pResult, std::vector<std::string> &LogList)
{
// 检测目标:找到 参数模版图到 检测图的 映射关系。包括 缩放和移动。
// 先缩放 在 移动
std::string strlog = "";
if (!pDetConfig)
{
return 1;
}
if (pDetConfig->TemplateImg.empty())
{
strlog = m_PrintLog.printstr(Print_Level_Error, "Image_Align", "TemplateImg is empty ");
LogList.push_back(strlog);
return 1;
}
// 裁切位置;
pResult->Crop_Roi_DetImg = pDetConfig->DetImg_CropROi;
// 1、缩放尺度
float fx = 1;
float fy = 1;
// 裁切尺寸 存在 并合理
if (pDetConfig->param_CropRoi.width > 0 && pDetConfig->param_CropRoi.height > 0 &&
pDetConfig->DetImg_CropROi.width > 0 && pDetConfig->DetImg_CropROi.height > 0)
{
fx = pDetConfig->DetImg_CropROi.width * 1.0f / pDetConfig->param_CropRoi.width;
fy = pDetConfig->DetImg_CropROi.height * 1.0f / pDetConfig->param_CropRoi.height;
if (fx > 0.5 && fx < 2 && fy > 0.5 && fy < 2)
{
pResult->fCropROI_Scale_ParmToDet_X = fx;
pResult->fCropROI_Scale_ParmToDet_Y = fy;
}
else
{
strlog = m_PrintLog.printstr(Print_Level_Error, "Image_Align", "Scale out 0.5--2");
LogList.push_back(strlog);
return 1;
}
}
else
{
strlog = m_PrintLog.printstr(Print_Level_Error, "Image_Align", "crop ROI Error");
LogList.push_back(strlog);
return 1;
}
// strlog = m_PrintLog.printstr(Print_Level_Info, "Image_Align", "Scale x %f y %f\n", fx, fy);
// LogList.push_back(strlog);
// 2、定位
// 1)、模版特征图片的 缩放。
cv::Mat TemplateFeature;
cv::Size sz;
// fx = 1;
// fy = 1;
sz.width = int(pDetConfig->TemplateImg.cols * fx);
sz.height = int(pDetConfig->TemplateImg.rows * fy);
cv::resize(pDetConfig->TemplateImg, TemplateFeature, sz);
if (!CheckUtil::RoiInImg(pDetConfig->Search_Roi, pDetConfig->DetImg))
{
strlog = m_PrintLog.printstr(Print_Level_Error, "Image_Align", "Search_Roi ROI Error Not In img");
LogList.push_back(strlog);
return 1;
}
cv::Mat DetFeature = pDetConfig->DetImg(pDetConfig->Search_Roi).clone();
double confidence = 0;
int kernel_size = 128;
int search_size = 1024;
int det_search_min_size = DetFeature.cols;
if (DetFeature.rows < det_search_min_size)
{
det_search_min_size = DetFeature.rows;
}
int template_kernel_min_size = TemplateFeature.cols;
if (TemplateFeature.rows < template_kernel_min_size)
{
template_kernel_min_size = TemplateFeature.rows;
}
float f_search = search_size * 1.0f / det_search_min_size;
float f_Kernel = kernel_size * 1.0f / template_kernel_min_size;
float falign = f_search;
if (f_Kernel > falign)
{
falign = f_Kernel;
}
cv::Size Search_sz;
Search_sz.width = int(DetFeature.cols * falign);
Search_sz.height = int(DetFeature.rows * falign);
cv::Mat Search_img;
cv::resize(DetFeature, Search_img, Search_sz);
cv::Size Kernel_sz;
Kernel_sz.width = int(TemplateFeature.cols * falign);
Kernel_sz.height = int(TemplateFeature.rows * falign);
cv::Mat Kernel_img;
cv::resize(TemplateFeature, Kernel_img, Kernel_sz);
auto bestMatch = findBestTemplateMatch(Search_img, Kernel_img, confidence);
bestMatch.x /= falign;
bestMatch.y /= falign;
pResult->bestMatch = bestMatch;
if (pDetConfig->bSaveImg)
{
cv::imwrite("Align_template.png", TemplateFeature);
cv::imwrite("Align_Det.png", DetFeature);
}
if (confidence != -1)
{
std::cout << "最佳匹配位置: (" << bestMatch.x << ", " << bestMatch.y
<< "), 得分: " << confidence << std::endl;
if (confidence > pDetConfig->fscore)
{
/* code */
int m_x = pDetConfig->feature_Roi.x * fx - pDetConfig->Search_Roi.x;
int m_y = pDetConfig->feature_Roi.y * fy - pDetConfig->Search_Roi.y;
pResult->offt_x = bestMatch.x - m_x;
pResult->offt_y = bestMatch.y - m_y;
// printf("m_x %d bestMatch.x %d offt_x %d\n", m_x, bestMatch.x, pResult->offt_x);
// printf("m_y %d bestMatch.y %d offt_y %d\n", m_y, bestMatch.y, pResult->offt_y);
pResult->bDet = true;
pResult->Crop_Roi_ParmImg = pResult->Det_srcToParm_src_Rect(pResult->Crop_Roi_DetImg);
strlog = m_PrintLog.printstr(Print_Level_Info, "Image_Align", " -- Succ :Align score %f > %f offt x %d y %d Scale x %f y %f",
confidence, pDetConfig->fscore, pResult->offt_x, pResult->offt_y, pResult->fCropROI_Scale_ParmToDet_X, pResult->fCropROI_Scale_ParmToDet_Y);
LogList.push_back(strlog);
}
else
{
strlog = m_PrintLog.printstr(Print_Level_Error, "Image_Align", " error :Align score %f< 0.9", confidence);
pResult->fCropROI_Scale_ParmToDet_X = 1;
pResult->fCropROI_Scale_ParmToDet_Y = 1;
LogList.push_back(strlog);
}
}
else
{
pResult->fCropROI_Scale_ParmToDet_X = 1;
pResult->fCropROI_Scale_ParmToDet_Y = 1;
std::cout << "未找到有效匹配" << std::endl;
}
return 0;
}
cv::Point Image_Feature_Algin::findBestTemplateMatch(
const cv::Mat &detectionImage,
const cv::Mat &templateImage,
double &bestScore,
int method)
{
// 输入验证
if (detectionImage.empty() || templateImage.empty())
{
throw std::invalid_argument("输入图像不能为空");
}
if (detectionImage.channels() != 1 || templateImage.channels() != 1)
{
throw std::invalid_argument("必须输入灰度图像");
}
if (templateImage.rows > detectionImage.rows ||
templateImage.cols > detectionImage.cols)
{
throw std::invalid_argument("模板尺寸不能大于被检测图像");
}
// cv::imwrite("detectionImage.png", detectionImage);
// cv::imwrite("templateImage.png", templateImage);
// 执行模板匹配
cv::Mat resultMatrix;
cv::matchTemplate(detectionImage, templateImage, resultMatrix, method);
// 确定极值搜索方式
const bool findMinima = (method == cv::TM_SQDIFF || method == cv::TM_SQDIFF_NORMED);
// 查找极值位置
cv::Point extremaLoc = cv::Point(0, 0);
double extremaVal;
cv::minMaxLoc(resultMatrix,
findMinima ? &extremaVal : nullptr,
findMinima ? nullptr : &extremaVal,
findMinima ? &extremaLoc : nullptr,
findMinima ? nullptr : &extremaLoc);
// 设置有效性检查阈值(可根据方法动态调整)
double threshold = 0.0;
switch (method)
{
case cv::TM_CCOEFF_NORMED:
threshold = 0.6;
break; // [-1, 1]
case cv::TM_CCORR_NORMED:
threshold = 0.7;
break; // [0, 1]
case cv::TM_SQDIFF_NORMED:
threshold = 0.2;
break; // [0, 1]
default:
threshold = 0.0;
}
// 验证匹配有效性
const bool isValid = findMinima ? (extremaVal <= threshold) : (extremaVal >= threshold);
if (isValid)
{
bestScore = extremaVal;
return extremaLoc;
}
bestScore = -1; // 无效时的默认值
return extremaLoc;
}