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.

731 lines
23 KiB

/*
* @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 <algorithm> // std::sort
#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;
std::vector<cv::RotatedRect> Big_roi;
std::vector<cv::RotatedRect> Small_roi;
std::vector<cv::RotatedRect> Tag_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;
}
re = Get_Edge(2, img, pDetConfig, m_pDetConfig->strChannel, Tag_roi);
if (re != 0)
{
m_pdetlog->AddCheckstr(PrintLevel_2, DET_LOG_LEVEL_3, "AI_Edge_Algin ", "AICheck_Tag----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;
m_pCheckResult_Aling->tagroi = Tag_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, std::vector<cv::RotatedRect> &RoiList)
{
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;
case 2:
pBackPlate_Align = AI_Factory->Tag_Loc;
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);
}
// 检查模型输入尺寸
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;
}
// 找到所有轮廓
std::vector<std::vector<cv::Point>> contours;
std::vector<cv::Vec4i> hierarchy;
cv::findContours(mask.clone(), contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
if (contours.empty())
{
m_pdetlog->AddCheckstr(PrintLevel_3, DET_LOG_LEVEL_3, "AI_Edge_Algin ", "No contours found!----error ");
return 4;
}
// 检测小图 到 原图 的缩放比例
const float scale_x = static_cast<float>(img.cols) / sz.width;
const float scale_y = static_cast<float>(img.rows) / sz.height;
RoiList.clear();
// 按轮廓面积从大到小排序(记录面积与轮廓索引)
std::vector<std::pair<double, size_t>> areaIdx;
areaIdx.reserve(contours.size());
for (size_t i = 0; i < contours.size(); i++)
{
double area = cv::contourArea(contours[i]);
if (area > 0)
{
areaIdx.emplace_back(area, i);
}
}
if (areaIdx.empty())
{
m_pdetlog->AddCheckstr(PrintLevel_3, DET_LOG_LEVEL_3, "AI_Edge_Algin ", "No valid contours found!----error ");
return 4;
}
std::sort(areaIdx.begin(), areaIdx.end(),
[](const std::pair<double, size_t> &a, const std::pair<double, size_t> &b)
{ return a.first > b.first; });
// 按面积从大到小,将每个轮廓点还原到原图坐标系并计算 RotatedRect
RoiList.reserve(areaIdx.size());
for (const auto &ai : areaIdx)
{
const std::vector<cv::Point> &contour = contours[ai.second];
if (contour.size() < 3)
{
continue;
}
std::vector<cv::Point2f> srcContour;
srcContour.reserve(contour.size());
for (const auto &pt : contour)
{
srcContour.emplace_back(pt.x * scale_x, pt.y * scale_y);
}
RoiList.emplace_back(cv::minAreaRect(srcContour));
}
if (RoiList.empty())
{
m_pdetlog->AddCheckstr(PrintLevel_3, DET_LOG_LEVEL_3, "AI_Edge_Algin ", "contour points too few!----error ");
return 6;
}
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;
}