/* * @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 // OpenCV核心功能 #include // 图像处理模块 #include // 向量容器 #include // std::sort #include // 输入输出流 /** * @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 template_pts = {template_p1, template_p2}; std::vector 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(0, 2), transform_matrix_.at(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_(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)); } /** * @brief 批量转换点坐标 * * @param points 输入点集(模板坐标系) * @return std::vector 转换后的点集(检测图像坐标系) */ std::vector transformPoints(const std::vector &points) const { if (!valid_) { std::cerr << "Warning: Using invalid transform! Returning original points." << std::endl; return points; } std::vector 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(0, 0); double b = transform_matrix_.at(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_(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)); } AI_Edge_Algin::AI_Edge_Algin(std::shared_ptr &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 &pCheckResult_Aling) { m_pCheckResult_Aling = std::make_shared(); 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 Big_roi; std::vector Small_roi; std::vector 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> contours; vector 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 &RoiList) { std::shared_ptr 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> contours; std::vector 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(img.cols) / sz.width; const float scale_y = static_cast(img.rows) / sz.height; RoiList.clear(); // 按轮廓面积从大到小排序(记录面积与轮廓索引) std::vector> 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 &a, const std::pair &b) { return a.first > b.first; }); // 按面积从大到小,将每个轮廓点还原到原图坐标系并计算 RotatedRect RoiList.reserve(areaIdx.size()); for (const auto &ai : areaIdx) { const std::vector &contour = contours[ai.second]; if (contour.size() < 3) { continue; } std::vector 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 &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; }