#include #include #include #include #include #include #include #include #include #pragma once using namespace HalconCpp; struct MatchResult { cv::Rect boundingRect; double confidence; }; // 简单的图像相关算法 //融合两张图片白色区域 static cv::Mat mergeBinaryMats(const cv::Mat& mat1, const cv::Mat& mat2) { // 确保两个输入图像具有相同的尺寸 if (mat1.size() != mat2.size()) { std::cerr << "Error: Input images must have the same size." << std::endl; return cv::Mat(); } // 创建一个输出图像,初始化为全黑 cv::Mat merged = cv::Mat::zeros(mat1.size(), CV_8U); // 将两个输入图像中的白色部分合并到输出图像 cv::bitwise_or(mat1, mat2, merged); return merged; } //Rect相加 static cv::Rect rectPlusRect(cv::Rect world, cv::Rect rect) { return cv::Rect(rect.x + world.x, rect.y + world.y, rect.width, rect.height); } static cv::Rect findMaxAreaContour(cv::Mat src) { int maxIndex = 0, maxArea = 0; std::vector>contours; cv::findContours(src, contours, cv::noArray(), cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE); if (contours.size() == 0) return cv::Rect(0, 0, 0, 0); for (int i = 0; i < contours.size(); i++) { int Area = cv::contourArea(contours[i]); if (Area > maxArea) { maxArea = Area; maxIndex = i; } } if (contours.size() != 0) return cv::boundingRect(contours[maxIndex]); return cv::Rect(0, 0, 0, 0); } static cv::Rect findMaxAreaContour(cv::Mat src, int& indexReturn) { int maxIndex = 0, maxArea = 0; std::vector>contours; cv::findContours(src, contours, cv::noArray(), cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE); if (contours.size() == 0) return cv::Rect(0, 0, 0, 0); for (int i = 0; i < contours.size(); i++) { int Area = cv::contourArea(contours[i]); if (Area > maxArea) { maxArea = Area; maxIndex = i; } } if (contours.size() != 0) { indexReturn = maxIndex; return cv::boundingRect(contours[maxIndex]); } } static cv::Mat cutImg2Cls(cv::Rect boundingBox, cv::Mat imgInput, cv::Size targetSize = cv::Size(128, 128)) { if (boundingBox.width <= 0 || boundingBox.height <= 0 || imgInput.empty()) { std::cerr << "Invalid bounding box or empty input image." << std::endl; return cv::Mat(); } // 以目标框中心为正方形中心,边长为 max(max(w,h),128) const cv::Point mid(boundingBox.x + boundingBox.width / 2, boundingBox.y + boundingBox.height / 2); const int sideLength = std::max(std::max(boundingBox.width, boundingBox.height), 128); // 期望在原图上的 ROI(可能越界) cv::Rect expectRect(mid.x - sideLength / 2, mid.y - sideLength / 2, sideLength, sideLength); // 计算需要的边界填充(反射填充) const int left = std::max(0, -expectRect.x); const int top = std::max(0, -expectRect.y); const int right = std::max(0, expectRect.x + expectRect.width - imgInput.cols); const int bottom = std::max(0, expectRect.y + expectRect.height - imgInput.rows); cv::Mat padded; if (left || top || right || bottom) { cv::copyMakeBorder(imgInput, padded, top, bottom, left, right, cv::BORDER_REFLECT_101); } else { padded = imgInput; } // 在填充后图像上的 ROI(保证不越界) cv::Rect shifted(expectRect.x + left, expectRect.y + top, expectRect.width, expectRect.height); // 最终裁剪并缩放 cv::Mat squareImage = padded(shifted).clone(); cv::resize(squareImage, squareImage, targetSize); return squareImage; } static std::vector cutImg2ClsVec(cv::Rect boundingBox, cv::Mat imgInput, cv::Size targetSize = cv::Size(128, 128)) { std::vector result; if (boundingBox.width <= 0 || boundingBox.height <= 0 || imgInput.empty()) { std::cerr << "Invalid bounding box or empty input image." << std::endl; return result; } const int w = boundingBox.width; const int h = boundingBox.height; const int minSide = std::min(w, h); if (minSide <= 0) { std::cerr << "Bounding box has zero width or height." << std::endl; return result; } // 沿长边切分为若干个 size=minSide 的方块,覆盖到末端且避免重复最后一块 const bool splitHoriz = (w > h); // 横向长 -> 沿X方向切 const int longSide = splitHoriz ? w : h; const int step = minSide; const int beginCoord = splitHoriz ? boundingBox.x : boundingBox.y; const int endCoord = beginCoord + longSide; // 计算切块起点列表 std::vector starts; starts.reserve((longSide + step - 1) / step); for (int s = beginCoord; s + step <= endCoord; s += step) { starts.emplace_back(s); } // 确保包含末端(避免遗漏),同时避免与最后一个重复 const int lastStart = endCoord - step; if (starts.empty() || starts.back() != lastStart) { starts.emplace_back(lastStart); } result.reserve(starts.size()); if (splitHoriz) { for (int x : starts) { result.emplace_back(cutImg2Cls(cv::Rect(x, boundingBox.y, minSide, minSide), imgInput, targetSize)); } } else { for (int y : starts) { result.emplace_back(cutImg2Cls(cv::Rect(boundingBox.x, y, minSide, minSide), imgInput, targetSize)); } } return result; } static cv::Mat HObject2Mat(HObject& Hobj) //HObject类型转Mat类型 { HTuple htCh; HString cType; cv::Mat Image; ConvertImageType(Hobj, &Hobj, "byte"); CountChannels(Hobj, &htCh); Hlong wid = 0; Hlong hgt = 0; if (htCh[0].I() == 1) { HImage hImg(Hobj); void* ptr = hImg.GetImagePointer1(&cType, &wid, &hgt);//GetImagePointer1(Hobj, &ptr, &cType, &wid, &hgt); int W = wid; int H = hgt; Image.create(H, W, CV_8UC1); unsigned char* pdata = static_cast(ptr); memcpy(Image.data, pdata, W * H); } else if (htCh[0].I() == 3) { void* Rptr; void* Gptr; void* Bptr; HImage hImg(Hobj); hImg.GetImagePointer3(&Rptr, &Gptr, &Bptr, &cType, &wid, &hgt); int W = wid; int H = hgt; Image.create(H, W, CV_8UC3); std::vector VecM(3); VecM[0].create(H, W, CV_8UC1); VecM[1].create(H, W, CV_8UC1); VecM[2].create(H, W, CV_8UC1); unsigned char* R2 = (unsigned char*)Rptr; unsigned char* G = (unsigned char*)Gptr; unsigned char* B = (unsigned char*)Bptr; memcpy(VecM[2].data, R2, W * H); memcpy(VecM[1].data, G, W * H); memcpy(VecM[0].data, B, W * H); cv::merge(VecM, Image); } return Image; } enum MatsToHObjectEnum { horizontal, vertical }; static HalconCpp::HObject MatsToHObject(cv::Mat* img1, cv::Mat* img2, MatsToHObjectEnum type) { auto start3 = std::chrono::high_resolution_clock::now(); HalconCpp::HObject Hobj; int height = 0; int width = 0; switch (type) { case horizontal: width = img1->cols; height = img1->rows + img2->rows; break; case vertical: width = img1->cols + img2->cols; height = img1->rows; break; } if (img1->type() == CV_8UC1) { unsigned char* imgData = new unsigned char[width * height]; memcpy(imgData, img1->data, img1->rows * img1->cols); memcpy(img1->rows * img1->cols + imgData, img2->data, img1->rows * img1->cols); GenImage1(&Hobj, "byte", width, height, (Hlong)imgData); delete[]imgData; imgData = nullptr; } auto end3 = std::chrono::high_resolution_clock::now(); auto duration3 = std::chrono::duration_cast(end3 - start3); std::cout << "Time taken by functionxxx: " << duration3.count() << " milliseconds" << std::endl; return Hobj; } static HalconCpp::HObject MatsToHObject(cv::Mat& img1, cv::Mat& img2, MatsToHObjectEnum type) { auto start3 = std::chrono::high_resolution_clock::now(); HalconCpp::HObject Hobj; int height = 0; int width = 0; switch (type) { case horizontal: width = img1.cols; height = img1.rows + img2.rows; break; case vertical: width = img1.cols + img2.cols; height = img1.rows; break; } if (img1.type() == CV_8UC1) { unsigned char* imgData = new unsigned char[width * height]; //#pragma omp parallel for //for (int i = 0; i < 2; i++) //{ // switch (i) // { // case 0: // memcpy(imgData, img1.data, img1.rows * img1.cols); // break; // case 1: // memcpy(img1.rows * img1.cols + imgData, img2.data, img1.rows * img1.cols); // break; // } //} memcpy(imgData, img1.data, img1.rows * img1.cols); memcpy(img1.rows * img1.cols + imgData, img2.data, img1.rows * img1.cols); GenImage1(&Hobj, "byte", width, height, (Hlong)imgData); delete[]imgData; imgData = nullptr; } auto end3 = std::chrono::high_resolution_clock::now(); auto duration3 = std::chrono::duration_cast(end3 - start3); std::cout << "Time taken by functionxxx: " << duration3.count() << " milliseconds" << std::endl; return Hobj; } static void MatToHObjectV(cv::Mat& image, HalconCpp::HObject* Hobj) { int hgt = image.rows; int wid = image.cols; int i; if (image.type() == CV_8UC3) { HalconCpp::HImage himage = HalconCpp::HImage(); std::vector imgchannel; split(image, imgchannel); cv::Mat imgB = imgchannel[0]; cv::Mat imgG = imgchannel[1]; cv::Mat imgR = imgchannel[2]; uchar* dataR = new uchar[hgt * wid]; uchar* dataG = new uchar[hgt * wid]; uchar* dataB = new uchar[hgt * wid]; for (i = 0; i < hgt; i++) { memcpy(dataR + wid * i, imgR.data + imgR.step * i, wid); memcpy(dataG + wid * i, imgG.data + imgG.step * i, wid); memcpy(dataB + wid * i, imgB.data + imgB.step * i, wid); } himage.GenImage3("byte", wid, hgt, dataR, dataG, dataB); HalconCpp::HString tmpType; Hlong tmpW, tmpH; void* hImgPtR, * hImgPtG, * hImgPtB; himage.GetImagePointer3(&hImgPtR, &hImgPtG, &hImgPtB, &tmpType, &tmpW, &tmpH); GenImage3(Hobj, "byte", wid, hgt, (Hlong)hImgPtR, (Hlong)hImgPtG, (Hlong)hImgPtB); delete[]dataR; delete[]dataG; delete[]dataB; dataR = NULL; dataG = NULL; dataB = NULL; } else if (image.type() == CV_8UC1) { //HalconCpp::HImage himage = HalconCpp::HImage(); ////uchar* data = new uchar[hgt * wid]; ////for (i = 0; i < hgt; i++) ////{ //// memcpy(data + wid * i, image.data + image.step * i, wid); ////} ////gen_image_const //himage.GenImage1("byte", wid, hgt, image.data); //HalconCpp::HString tmpType; //Hlong tmpW, tmpH; //void* hImgPt; //hImgPt = himage.GetImagePointer1(&tmpType, &tmpW, &tmpH); //GenImage1(&Hobj, "byte", wid, hgt, (Hlong)hImgPt); GenImage1(Hobj, "byte", wid, hgt, (Hlong)image.data); /* delete[] data; data = NULL;*/ } else if (image.type() == CV_16UC1) { HalconCpp::HImage himage = HalconCpp::HImage(); ushort* data = new ushort[hgt * wid * 2]; memcpy(data, image.data, hgt * wid * 2); himage.GenImage1("uint2", wid, hgt, data); HalconCpp::HString tmpType; Hlong tmpW, tmpH; void* hImgPt; hImgPt = himage.GetImagePointer1(&tmpType, &tmpW, &tmpH); GenImage1(Hobj, "uint2", wid, hgt, (Hlong)hImgPt); delete[] data; data = NULL; } else if (image.type() == CV_16SC1) { HalconCpp::HImage himage = HalconCpp::HImage(); short* data = new short[hgt * wid * 2]; memcpy(data, image.data, hgt * wid * 2); himage.GenImage1("int2", wid, hgt, data); HalconCpp::HString tmpType; Hlong tmpW, tmpH; void* hImgPt; hImgPt = himage.GetImagePointer1(&tmpType, &tmpW, &tmpH); GenImage1(Hobj, "int2", wid, hgt, (Hlong)hImgPt); delete[] data; data = NULL; } } static HalconCpp::HObject MatToHObject(cv::Mat& image) { HalconCpp::HObject Hobj; int hgt = image.rows; int wid = image.cols; int i; if (image.type() == CV_8UC3) { HalconCpp::HImage himage = HalconCpp::HImage(); std::vector imgchannel; split(image, imgchannel); cv::Mat imgB = imgchannel[0]; cv::Mat imgG = imgchannel[1]; cv::Mat imgR = imgchannel[2]; uchar* dataR = new uchar[hgt * wid]; uchar* dataG = new uchar[hgt * wid]; uchar* dataB = new uchar[hgt * wid]; for (i = 0; i < hgt; i++) { memcpy(dataR + wid * i, imgR.data + imgR.step * i, wid); memcpy(dataG + wid * i, imgG.data + imgG.step * i, wid); memcpy(dataB + wid * i, imgB.data + imgB.step * i, wid); } himage.GenImage3("byte", wid, hgt, dataR, dataG, dataB); HalconCpp::HString tmpType; Hlong tmpW, tmpH; void* hImgPtR, * hImgPtG, * hImgPtB; himage.GetImagePointer3(&hImgPtR, &hImgPtG, &hImgPtB, &tmpType, &tmpW, &tmpH); GenImage3(&Hobj, "byte", wid, hgt, (Hlong)hImgPtR, (Hlong)hImgPtG, (Hlong)hImgPtB); delete[]dataR; delete[]dataG; delete[]dataB; dataR = NULL; dataG = NULL; dataB = NULL; } else if (image.type() == CV_8UC1) { //HalconCpp::HImage himage = HalconCpp::HImage(); ////uchar* data = new uchar[hgt * wid]; ////for (i = 0; i < hgt; i++) ////{ //// memcpy(data + wid * i, image.data + image.step * i, wid); ////} ////gen_image_const //himage.GenImage1("byte", wid, hgt, image.data); //HalconCpp::HString tmpType; //Hlong tmpW, tmpH; //void* hImgPt; //hImgPt = himage.GetImagePointer1(&tmpType, &tmpW, &tmpH); //GenImage1(&Hobj, "byte", wid, hgt, (Hlong)hImgPt); GenImage1(&Hobj, "byte", wid, hgt, (Hlong)image.data); /* delete[] data; data = NULL;*/ } else if (image.type() == CV_16UC1) { HalconCpp::HImage himage = HalconCpp::HImage(); ushort* data = new ushort[hgt * wid * 2]; memcpy(data, image.data, hgt * wid * 2); himage.GenImage1("uint2", wid, hgt, data); HalconCpp::HString tmpType; Hlong tmpW, tmpH; void* hImgPt; hImgPt = himage.GetImagePointer1(&tmpType, &tmpW, &tmpH); GenImage1(&Hobj, "uint2", wid, hgt, (Hlong)hImgPt); delete[] data; data = NULL; } else if (image.type() == CV_16SC1) { HalconCpp::HImage himage = HalconCpp::HImage(); short* data = new short[hgt * wid * 2]; memcpy(data, image.data, hgt * wid * 2); himage.GenImage1("int2", wid, hgt, data); HalconCpp::HString tmpType; Hlong tmpW, tmpH; void* hImgPt; hImgPt = himage.GetImagePointer1(&tmpType, &tmpW, &tmpH); GenImage1(&Hobj, "int2", wid, hgt, (Hlong)hImgPt); delete[] data; data = NULL; } return Hobj; } //获取两点之间的直线距离 static double getDistance(cv::Point pointO, cv::Point pointA) { double distance; distance = powf((pointO.x - pointA.x), 2) + powf((pointO.y - pointA.y), 2); distance = sqrtf(distance); return distance; } //获取点到线段之间的直线距离 static double pointToLineDistance(const cv::Point& point, const cv::Point& lineStart, const cv::Point& lineEnd) { double numerator = std::abs((lineEnd.y - lineStart.y) * point.x - (lineEnd.x - lineStart.x) * point.y + lineEnd.x * lineStart.y - lineEnd.y * lineStart.x); double denominator = std::sqrt(std::pow(lineEnd.y - lineStart.y, 2) + std::pow(lineEnd.x - lineStart.x, 2)); return numerator / denominator; } //计算角度,pt2为顶点 static double calculateAngle(cv::Point pt1, cv::Point pt2, cv::Point pt3) { // 计算两个向量 cv::Point vector1 = pt1 - pt2; cv::Point vector2 = pt3 - pt2; // 计算两个向量的长度 double length1 = norm(vector1); double length2 = norm(vector2); // 计算两个向量的点积 double dotProduct = vector1.dot(vector2); // 计算夹角的余弦值 double cosAngle = dotProduct / (length1 * length2); // 计算夹角的弧度 double angleRadians = acos(cosAngle); // 将弧度转换为度数 double angleDegrees = angleRadians * 180 / CV_PI; return angleDegrees; } //计算角度偏移量,pt1,pt2为第一条线段,pt3,pt4为第二条线段 static double calculateAngleOffset(cv::Point pt11, cv::Point pt12, cv::Point pt21, cv::Point pt22) { // 计算第一条线段的向量 cv::Point vec1(pt12.x - pt11.x, pt12.y - pt11.y); // 计算第二条线段的向量 cv::Point vec2(pt22.x - pt21.x, pt22.y - pt21.y); // 计算向量的叉乘 double crossProduct = vec1.x * vec2.y - vec1.y * vec2.x; // 计算角度偏移量 double angleOffset = atan2(crossProduct, vec1.dot(vec2)); // 将弧度转换为度数 angleOffset = angleOffset * 180 / CV_PI; return angleOffset; } //通过角度计算圆上点的位置 static cv::Point calculatePointOnCircle(cv::Point center, double radius, double angleDegrees) { // 将角度转换为弧度 double angleRadians = angleDegrees * CV_PI / 180.0; // 计算点的坐标 int x = static_cast(center.x + radius * cos(angleRadians)); int y = static_cast(center.y + radius * sin(angleRadians)); return cv::Point(x, y); } /// /// 模板匹配,以MatchResult返回 /// /// 图像 /// 模板图像 /// static MatchResult templateMatching(const cv::Mat& originalImage, const cv::Mat& templateImage) { MatchResult result; // 创建结果图像 int resultCols = originalImage.cols - templateImage.cols + 1; int resultRows = originalImage.rows - templateImage.rows + 1; cv::Mat resultImg(resultRows, resultCols, CV_32FC1); // 进行模板匹配 cv::matchTemplate(originalImage, templateImage, resultImg, cv::TM_CCOEFF_NORMED); // 归一化结果图像 cv::normalize(resultImg, resultImg, 0, 1, cv::NORM_MINMAX, -1, cv::Mat()); // 寻找最佳匹配位置 double minVal, maxVal; cv::Point minLoc, maxLoc; cv::minMaxLoc(resultImg, &minVal, &maxVal, &minLoc, &maxLoc); // 设置矩形框和可信度 result.boundingRect = cv::Rect(maxLoc.x, maxLoc.y, templateImage.cols, templateImage.rows); result.confidence = maxVal; return result; } /// /// 模板匹配,返回Rect以及分数 /// /// 模板图像 /// 匹配图像 /// 分数 /// static cv::Rect doMatchTemplate(const cv::Mat& templateImg, const cv::Mat& srcImg, double* sorce) { cv::Mat resultImage; matchTemplate(srcImg, templateImg, resultImage, cv::TM_CCOEFF_NORMED); double minVal = 0, maxVal = 0; cv::Point minLoc(0, 0), maxLoc(0, 0); minMaxLoc(resultImage, &minVal, &maxVal, &minLoc, &maxLoc); *sorce = maxVal; cv::Rect rect(maxLoc.x, maxLoc.y, templateImg.cols, templateImg.rows); return rect; } //计算contour是否重叠 static bool isContoursOverlap(const std::vector& contour1, const std::vector& contour2, double thresholdPercent = 0) { cv::Rect rect1 = cv::boundingRect(contour1); cv::Rect rect2 = cv::boundingRect(contour2); cv::Size tempImgSize(std::max(rect1.x + rect1.width, rect2.x + rect2.width), std::max(rect1.y + rect1.height, rect2.y + rect2.height)); cv::Mat binaryImage = cv::Mat::zeros(tempImgSize, CV_8U); // 画出目标轮廓 drawContours(binaryImage, std::vector>{contour2}, 0, cv::Scalar(255), -1); double totalOverlap = 0.0; // 创建一个临时的二值图像 cv::Mat tempBinaryImage = cv::Mat::zeros(tempImgSize, CV_8U); // 画出当前轮廓 drawContours(tempBinaryImage, std::vector>{contour1}, 0, cv::Scalar(255), -1); // 计算重叠的像素数 cv::Mat intersection = binaryImage & tempBinaryImage; int overlapPixelCount = countNonZero(intersection); // 计算当前轮廓的重叠百分比 double overlapPercentage = static_cast(overlapPixelCount) / countNonZero(tempBinaryImage); return overlapPercentage * 100.0 > thresholdPercent; // 将结果转换为百分比 } //计算rect是否重叠 static bool isRectOverlap(const cv::Rect& rect1, const cv::Rect& rect2, double thresholdPercent = 0) { // 计算两个矩形的重叠区域 cv::Rect intersection = rect1 & rect2; if (thresholdPercent != 0)//如果有阈值 { int maxCnotourArea = std::max(rect1.area(), rect2.area()); if (intersection.area() / maxCnotourArea < thresholdPercent) return false; } // 如果重叠区域的面积大于0,则说明两个矩形重叠 return intersection.area() > 0; } //将两个contour的区域合并 static std::vector contourPlusContour(const std::vector& contour1, const std::vector& contour2) { std::vectorres; res.insert(res.end(), contour1.begin(), contour1.end()); res.insert(res.end(), contour2.begin(), contour2.end()); return res; } static cv::Rect BboxToRect(handdleAI::Bbox input) { return cv::Rect(input.x - input.w / 2, input.y - input.h / 2, input.w, input.h); } enum ngTypeEnum { OK, huaHeng, aoKeng, zangWu }; struct rectAndData { cv::Rect rect; double defectHeight; double defectWidth; int badType = 0; bool result; ngTypeEnum ngType; std::vector contour; rectAndData(cv::Rect rectInput, double defectHeightInput, double defectWidthInput, bool resultInput) { rect = rectInput; defectHeight = defectHeightInput; defectWidth = defectWidthInput; result = resultInput; } rectAndData(cv::Rect rectInput, double defectHeightInput, double defectWidthInput, int n, bool resultInput) { rect = rectInput; defectHeight = defectHeightInput; defectWidth = defectWidthInput; badType = n; result = resultInput; } rectAndData(cv::Rect rectInput, double defectHeightInput, double defectWidthInput, int n, bool resultInput, std::vector& contourInput) { rect = rectInput; defectHeight = defectHeightInput; defectWidth = defectWidthInput; badType = n; result = resultInput; contour = contourInput; } rectAndData(cv::Rect rectInput, double defectHeightInput, double defectWidthInput, int n, bool resultInput, std::vector& contourInput, ngTypeEnum inputNgType) { rect = rectInput; defectHeight = defectHeightInput; defectWidth = defectWidthInput; badType = n; result = resultInput; contour = contourInput; ngType = inputNgType; } }; ; //平铺图像 static cv::Mat tiledImages(std::vector& imgs) { if (imgs.size() == 0) return cv::imread("./noimg.png",-1); if (imgs.size() == 1) return imgs[0]; for (auto& img : imgs) { if (img.channels() == 1) cv::cvtColor(img, img, cv::COLOR_GRAY2BGR); } //总图像大小 平铺显示 int matWidth = 0, matHeight = 0; for (auto& mat : imgs) { matHeight = std::max(mat.rows, matHeight); matWidth += mat.cols; } cv::Mat showMat(matHeight, matWidth, CV_8UC3); //平铺图像 int withCnt = 0; for (auto& resultMat : imgs) { resultMat.copyTo(showMat(cv::Rect(withCnt, 0, resultMat.cols, resultMat.rows))); withCnt += resultMat.cols; } return showMat; }; /** * 快速去噪函数 * * 说明: * - 优先使用均值滤波(box filter)实现快速平滑; * - 对明显的椒盐噪声可开启中值滤波(按通道处理,适用于8U/16U); * - 指定较大核或需要可控平滑强度时,使用高斯滤波作为兜底。 * * 参数: * - inputMat 输入图像(支持单通道/多通道); * - outputMat 输出图像; * - kernelSize 卷积核大小,必须为奇数且>=3(传偶数会自动+1); * - sigma 高斯滤波标准差,<=0 表示走OpenCV自动推导; * - useMedian 是否启用中值滤波(更抗椒盐噪声,速度较均值略慢)。 */ static void denoiseImage(const cv::Mat& inputMat, cv::Mat& outputMat, int kernelSize = 3, double sigma = 0.0, bool useMedian = false) { if (inputMat.empty()) { outputMat.release(); return; } int k = kernelSize; if (k < 3) k = 3; // 最小有效核大小 if ((k & 1) == 0) { ++k; // 转换为奇数核 } // 快速路径(椒盐噪声):启用中值滤波;多通道按通道分别处理 if (useMedian && (inputMat.depth() == CV_8U || inputMat.depth() == CV_16U)) { if (inputMat.channels() == 1) { cv::medianBlur(inputMat, outputMat, k); return; } else { std::vector ch; cv::split(inputMat, ch); for (auto& c : ch) { cv::medianBlur(c, c, k); } cv::merge(ch, outputMat); return; } } // 默认快速路径:小核且未指定sigma时使用均值滤波(分离实现,速度快) if (k <= 5 && sigma <= 0.0) { cv::blur(inputMat, outputMat, cv::Size(k, k), cv::Point(-1, -1), cv::BORDER_DEFAULT); return; } // 兜底:使用可分离高斯滤波,适合较大核或需要指定平滑强度的场景 const double sigmaChecked = std::max(0.0, sigma); cv::GaussianBlur(inputMat, outputMat, cv::Size(k, k), sigmaChecked, sigmaChecked, cv::BORDER_DEFAULT); } // 字符编码转换:GBK 编码的 std::string 转为 UTF-8 编码的 std::string(基于 Qt 5.14.x) // 入参:gbkStr - GBK/GB2312/GB18030 字节序列(std::string 仅作为原始字节容器) // 返回:UTF-8 编码的字节序列(std::string) static inline std::string gbkToUtf8(const std::string& gbkStr) { // 使用 Qt 的 QTextCodec 进行编码转换 // 在 Windows 简体中文环境下,本地编码通常是 GBK/GB2312;此处显式指定 "GBK" QTextCodec* codec = QTextCodec::codecForName("GBK"); if (!codec) { // 兜底:如果没有 GBK codec(极少见),退化为直接当作本地 8-bit 解码 QString tmp = QString::fromLocal8Bit(gbkStr.data(), static_cast(gbkStr.size())); QByteArray utf8 = tmp.toUtf8(); return std::string(utf8.constData(), static_cast(utf8.size())); } // 将 GBK 字节解码为 Unicode(QString) QString unicode = codec->toUnicode(gbkStr.data(), static_cast(gbkStr.size())); // 再编码为 UTF-8 QByteArray utf8 = unicode.toUtf8(); return std::string(utf8.constData(), static_cast(utf8.size())); }