ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

C#与OpenCV实现工业级圆形检测:超越HoughCircles的鲁棒算法

C#与OpenCV实现工业级圆形检测:超越HoughCircles的鲁棒算法 简介本资源是一套基于C#与OpenCvSharp实现的高实用性圆形检测算法工程面向.NET开发者、机器视觉初学者及工业检测应用工程师解决复杂背景下精准定位圆形目标的核心问题。压缩包共14个文件7个C#源码文件含主窗体Form1.cs、核心算法FindCircles.cs、自定义图像控件ProPictureBox.cs2个配置文件app.config与packages.config2个资源文件.resx1个解决方案.sln、1个项目文件.csproj、1个设置文件.settings总大小仅16KB结构精炼、模块职责清晰便于快速理解霍夫圆检测全流程——涵盖灰度化、高斯滤波、Canny边缘提取及HoughCircles参数调优等关键环节。已有141人学习下载配套完整可运行WinForm界面开箱即用无需额外环境配置适合用于教学演示、产线缺陷识别原型开发或OpenCV.NET集成实践。1. 项目概述从“找圆”到工业视觉的精准定位在工业自动化、机器视觉乃至一些创意图像处理项目中“找圆”是一个看似简单却充满挑战的基础任务。你可能需要从一张产品照片中定位螺丝孔从医学影像中识别细胞核或者在一张星空图中标记行星。这个名为“C#和OpenCv实现功能强大的找圆算法”的项目其核心目标就是构建一个鲁棒、高效且功能丰富的圆形检测工具。它不仅仅是调用一个现成的函数而是深入理解图像处理流程并针对复杂场景如光照不均、噪声干扰、部分遮挡、椭圆变形进行算法组合与优化。我之所以对这个话题有深入的实践是因为在早期的视觉项目里我天真地以为OpenCv的HoughCircles函数是万能的直到它在产线上因为反光、油污而频频误检或漏检导致整个系统不稳定。从那以后我花了大量时间研究如何构建一个真正“强大”的找圆流程。这个流程通常不是单一算法而是一个从图像预处理、边缘/轮廓提取到候选圆筛选与验证的完整流水线。使用C#作为开发语言结合OpenCvSharp这样的.NET封装库能够让我们在拥有强大图像处理能力的同时享受C#在Windows桌面应用、工业上位机开发中便捷的UI构建和系统集成优势。这个项目适合有一定C#基础并希望踏入机器视觉领域的开发者。无论你是想为自家的小型自动化设备增加视觉定位功能还是处理特定的科研图像分析任务理解并实现一套自己的找圆算法都将为你打下坚实的基础。接下来我将拆解整个实现过程分享从原理到代码再到调试优化的全链路经验。2. 核心思路与算法选型为什么不用HoughCircles一用到底当我们拿到一个找圆需求第一反应往往是OpenCv中的霍夫圆变换cv2.HoughCircles在C#中是Cv2.HoughCircles。这没错它是一个经典且直接的圆检测方法。但在工业级或高精度应用中直接使用它往往不够。我们需要理解其局限性并设计更强大的方案。2.1 霍夫圆变换的原理与局限霍夫圆变换的基本思想是“投票”。对于一个边缘点它可能位于无数个可能的圆上每个圆由圆心(a,b)和半径r定义。算法在三维参数空间(a, b, r)中为每个边缘点可能对应的所有圆进行累加投票最终票数高的位置就对应一个可能的圆。它的优点是概念直观对于清晰、完整的圆形效果不错。但缺点也很明显参数敏感dp累加器分辨率、minDist圆间最小距离、param1Canny边缘检测高阈值、param2累加器阈值等参数需要精心调节且一个参数集很难适应所有图像。计算量大三维参数空间搜索对内存和计算资源要求较高特别是图像大、边缘点多时。抗干扰差对噪声、非圆边缘如直线、纹理敏感容易产生误检。对部分圆/椭圆支持弱对于被遮挡的圆或变成椭圆的圆检测能力下降。因此一个“功能强大”的找圆算法通常会将霍夫圆变换作为候选生成器之一而不是唯一判决器。更通用的思路是“边缘/轮廓提取 几何形状拟合与筛选”。2.2 更鲁棒的算法流程设计基于我的项目经验一个强大的找圆流程可以概括为以下步骤它更像一个处理管道Pipeline图像预处理目的是增强圆特征抑制噪声。包括灰度化、高斯模糊去噪、对比度增强如直方图均衡化EqualizeHist、二值化阈值分割或自适应阈值等。边缘/轮廓提取从预处理后的图像中找出可能的圆形边界。可以使用Canny边缘检测或者直接使用FindContours查找所有轮廓。候选圆生成方法A轮廓法对每个找到的轮廓用MinEnclosingCircle求最小外接圆或使用FitEllipse拟合椭圆。然后根据圆度、面积、周长等几何特征进行初步筛选。方法B霍夫法对边缘图像使用HoughCircles得到一组初始的圆参数(x, y, radius)。候选圆验证与筛选这是提升“强大”性的关键。对每个候选圆计算其可靠性指标例如轮廓支持度计算圆周上实际有多少比例的像素是边缘点。灰度梯度一致性检查圆边界内外的灰度梯度方向是否与径向垂直符合圆形边缘特性。几何约束根据应用场景过滤掉半径过大/过小、圆心位置超出ROI感兴趣区域的圆。结果优化与输出对最终筛选出的圆进行非极大值抑制避免重叠圆并按置信度或半径排序输出。这个流程的优势在于模块化和可解释性。每个步骤都可以独立调整和优化并且我们可以清楚地知道一个圆为什么被选中或淘汰。下面我们将进入具体的实现环节。3. 环境搭建与核心工具链配置工欲善其事必先利其器。在C#中调用OpenCv我们主要使用OpenCvSharp这个优秀的开源库。它几乎提供了与原生OpenCv (C) 一一对应的API学习成本低。3.1 开发环境与NuGet包管理我强烈建议使用Visual Studio 2022进行开发社区版免费且功能强大。创建一个新的C#控制台应用或WPF/WinForms项目。通过NuGet包管理器安装以下核心库OpenCvSharp4 主库包含核心图像处理功能。OpenCvSharp4.runtime.win 对应平台的运行时库包含必要的本地DLL如opencv_world4xx.dll。根据你的系统选择例如OpenCvSharp4.runtime.win适用于大多数Windows环境。安装命令包管理器控制台Install-Package OpenCvSharp4 Install-Package OpenCvSharp4.runtime.win注意如果你在部署到其他机器时遇到“无法加载DLL ‘opencv_world4xx’…”的错误就是因为目标机器缺少这些本地运行时库。解决方案有两种一是确保目标机器也安装了对应的OpenCvSharp4.runtime包例如通过安装程序打包二是将相关DLL通常位于你的项目输出目录bin\Debug下随你的应用程序一起发布。3.2 基础图像操作与第一个找圆程序安装完成后让我们写一个最简单的、基于HoughCircles的找圆程序来验证环境并理解基础API。using OpenCvSharp; using System; using System.Linq; namespace PowerfulCircleFinder { class Program { static void Main(string[] args) { // 1. 读取图像 using Mat src new Mat(path_to_your_image.jpg, ImreadModes.Color); using Mat gray new Mat(); using Mat display src.Clone(); if (src.Empty()) { Console.WriteLine(Could not open or find the image!); return; } // 2. 转换为灰度图 Cv2.CvtColor(src, gray, ColorConversionCodes.BGR2GRAY); // 3. 应用中值模糊去噪对椒盐噪声效果好 Cv2.MedianBlur(gray, gray, 5); // 4. 霍夫圆变换 CircleSegment[] circles Cv2.HoughCircles( image: gray, method: HoughMethods.Gradient, // 常用方法 dp: 1, // 累加器分辨率与图像分辨率之比1表示相同 minDist: gray.Rows / 8, // 检测到的圆之间的最小距离避免重复 param1: 100, // Canny边缘检测的高阈值 param2: 30, // 累加器阈值越小检测到的圆越多但误检也可能增多 minRadius: 20, // 最小半径 maxRadius: 100 // 最大半径 ); // 5. 绘制并输出结果 Console.WriteLine($Found {circles.Length} circles.); foreach (var circle in circles) { // 绘制圆心 Cv2.Circle(display, (int)circle.Center.X, (int)circle.Center.Y, 3, Scalar.Red, -1); // 绘制圆轮廓 Cv2.Circle(display, (int)circle.Center.X, (int)circle.Center.Y, (int)circle.Radius, Scalar.Green, 2); Console.WriteLine($Center: ({circle.Center.X:F1}, {circle.Center.Y:F1}), Radius: {circle.Radius:F1}); } // 6. 显示结果 Cv2.ImShow(Detected Circles, display); Cv2.WaitKey(0); Cv2.DestroyAllWindows(); } } }这个程序是一个起点。你会发现调整param1,param2,minRadius,maxRadius对结果影响巨大。在复杂图像上它可能表现不佳。接下来我们就来构建更强大的流程。4. 构建功能强大的找圆算法流水线我们将实现一个名为RobustCircleDetector的类它封装了之前提到的多步骤流水线。这里我采用“轮廓提取几何拟合”为主“霍夫变换”为辅的双路验证策略。4.1 图像预处理模块的实现预处理的目标是得到一个清晰的、高对比度的二值图像其中圆的边缘尽可能完整、连续。public class RobustCircleDetector { public Mat PreprocessImage(Mat srcImage) { using Mat gray new Mat(); using Mat blurred new Mat(); using Mat binary new Mat(); // 1. 灰度化 Cv2.CvtColor(srcImage, gray, ColorConversionCodes.BGR2GRAY); // 2. 高斯模糊有效抑制高斯噪声内核大小需为奇数 // 内核大小(5,5)和SigmaX1.5是常用起始值。噪声大时可增大内核。 Cv2.GaussianBlur(gray, blurred, new Size(5, 5), 1.5); // 3. 对比度增强使用CLAHE限制对比度自适应直方图均衡化 // 比普通的EqualizeHist更能避免局部过曝。 using var clahe Cv2.CreateCLAHE(clipLimit: 2.0, tileGridSize: new Size(8, 8)); clahe.Apply(blurred, blurred); // 4. 自适应阈值二值化应对光照不均的关键步骤 // 与全局阈值不同自适应阈值为每个像素点邻域计算阈值。 // Cv2.AdaptiveThreshold(源, 目标, 最大值, 自适应方法, 阈值类型, 块大小, 常数C) // 块大小必须是奇数通常取比目标圆直径稍大的值。 Cv2.AdaptiveThreshold(blurred, binary, 255, AdaptiveThresholdTypes.GaussianC, ThresholdTypes.Binary, 11, 2); // 5. 形态学操作闭合小孔洞断开细连接 // 先闭运算先膨胀后腐蚀填充轮廓内的小孔 // 再开运算先腐蚀后膨胀去除小的白色噪点 var kernel Cv2.GetStructuringElement(MorphShapes.Ellipse, new Size(3, 3)); Cv2.MorphologyEx(binary, binary, MorphTypes.Close, kernel); Cv2.MorphologyEx(binary, binary, MorphTypes.Open, kernel); return binary.Clone(); // 返回处理后的二值图像 } }实操心得AdaptiveThreshold的blockSize参数至关重要。它定义了计算阈值的邻域大小。如果目标圆大小已知可以将其设置为略小于圆直径的奇数。如果圆大小变化大可能需要采用多尺度策略或使用其他方法。C常数是从计算出的平均值或加权平均值中减去的值用于微调通常设为较小的正数。4.2 基于轮廓的候选圆生成与筛选这是算法的核心。我们从二值图像中提取轮廓然后对每个轮廓进行分析。public ListDetectedCircle FindCirclesByContour(Mat binaryImage, Mat originalImage) { var detectedCircles new ListDetectedCircle(); // 1. 查找轮廓 Point[][] contours; HierarchyIndex[] hierarchy; // RetrievalModes.External 只检测最外层轮廓对于实心圆很好用。 // 如果是环形可能需要用 RetrievalModes.List 或 RetrievalModes.CComp Cv2.FindContours(binaryImage, out contours, out hierarchy, RetrievalModes.External, ContourApproximationModes.ApproxSimple); foreach (var contour in contours) { // 2. 面积过滤太小的轮廓可能是噪声 double area Cv2.ContourArea(contour); if (area 100) continue; // 阈值根据图像分辨率调整 // 3. 拟合最小外接圆 Cv2.MinEnclosingCircle(contour, out Point2f center, out float radius); // 4. 计算圆度Circularity: 衡量轮廓接近圆形的程度 // 圆度 (4 * PI * 面积) / (周长^2)。完美圆的值为1。 double perimeter Cv2.ArcLength(contour, true); double circularity 4 * Math.PI * area / (perimeter * perimeter); // 5. 计算轮廓对拟合圆的支持度 // 在拟合圆的圆周上采样若干点检查这些点在二值图中是否为前景边缘 int supportPoints 0; int totalSamplePoints (int)(2 * Math.PI * radius); // 采样点数约等于周长 for (int i 0; i totalSamplePoints; i) { double angle 2 * Math.PI * i / totalSamplePoints; int x (int)(center.X radius * Math.Cos(angle)); int y (int)(center.Y radius * Math.Sin(angle)); // 确保坐标在图像范围内 if (x 0 x binaryImage.Width y 0 y binaryImage.Height) { if (binaryImage.Getbyte(y, x) 0) // 如果是白色前景 { supportPoints; } } } double supportRatio (double)supportPoints / totalSamplePoints; // 6. 综合筛选条件 if (circularity 0.7 supportRatio 0.6) // 阈值可调 { // 7. 可选使用灰度梯度信息进一步验证 // 在原始灰度图的圆边界上计算梯度方向是否指向圆心或背离圆心。 bool gradientCheckPassed VerifyByGradient(originalImage, center, radius); if (gradientCheckPassed) { detectedCircles.Add(new DetectedCircle { Center center, Radius radius, Confidence (circularity supportRatio) / 2.0 // 简单的置信度计算 }); } } } // 8. 非极大值抑制移除位置和半径过于接近的重复圆 return NonMaximumSuppression(detectedCircles, overlapThreshold: 0.8); } private bool VerifyByGradient(Mat grayImage, Point2f center, float radius) { // 简化版的梯度验证在几个方向上检查边界像素的梯度 // 实际实现会更复杂可能需要计算整个圆周的梯度方向一致性 // 这里仅作示意 int sampleCount 8; int passCount 0; for (int i 0; i sampleCount; i) { double angle 2 * Math.PI * i / sampleCount; int x1 (int)(center.X (radius - 2) * Math.Cos(angle)); int y1 (int)(center.Y (radius - 2) * Math.Sin(angle)); int x2 (int)(center.X (radius 2) * Math.Cos(angle)); int y2 (int)(center.Y (radius 2) * Math.Sin(angle)); if (IsInsideImage(grayImage, x1, y1) IsInsideImage(grayImage, x2, y2)) { byte val1 grayImage.Getbyte(y1, x1); byte val2 grayImage.Getbyte(y2, x2); // 简单判断圆外到圆内应该是从亮到暗或暗到亮边缘 if (Math.Abs(val1 - val2) 30) // 梯度强度阈值 { passCount; } } } return passCount sampleCount * 0.7; // 70%的采样点通过 }这个DetectedCircle是一个简单的数据类public class DetectedCircle { public Point2f Center { get; set; } public float Radius { get; set; } public double Confidence { get; set; } // 0~1置信度 }4.3 与霍夫变换结果融合的策略轮廓法对于完整、闭合的轮廓效果好但对于边缘断裂或不连续的圆霍夫变换可能更有优势。我们可以将两种方法的结果进行融合。public ListDetectedCircle FindCirclesFusion(Mat srcImage) { var finalCircles new ListDetectedCircle(); // 路径1轮廓法 Mat binary PreprocessImage(srcImage); var contourCircles FindCirclesByContour(binary, srcImage); finalCircles.AddRange(contourCircles); // 路径2霍夫变换法在预处理后的灰度图上进行 using Mat grayForHough new Mat(); Cv2.CvtColor(srcImage, grayForHough, ColorConversionCodes.BGR2GRAY); Cv2.GaussianBlur(grayForHough, grayForHough, new Size(5,5), 1.5); var houghCircles Cv2.HoughCircles(grayForHough, HoughMethods.Gradient, dp: 1.2, // 稍大的dp可以加速但精度略降 minDist: grayForHough.Rows / 10, param1: 80, // 可以比单独使用霍夫时低一些 param2: 25, minRadius: 10, maxRadius: 200); foreach (var c in houghCircles) { // 将霍夫结果转换为我们的DetectedCircle格式 var houghCircle new DetectedCircle { Center c.Center, Radius c.Radius, Confidence 0.8 }; // 赋予一个基础置信度 // 检查是否与轮廓法检测到的圆重叠过多避免重复添加 if (!IsOverlapping(houghCircle, finalCircles, threshold: 0.7)) { // 可以对霍夫检测的圆也进行一次支持度验证提升置信度 houghCircle.Confidence CalculateSupportRatio(binary, houghCircle.Center, houghCircle.Radius); if (houghCircle.Confidence 0.4) // 设置一个较低的接受阈值 { finalCircles.Add(houghCircle); } } } // 最终进行一次全局的非极大值抑制和按置信度排序 finalCircles NonMaximumSuppression(finalCircles, 0.6); finalCircles.Sort((a, b) b.Confidence.CompareTo(a.Confidence)); return finalCircles; }融合策略的关键在于去重IsOverlapping函数和置信度统一。通过计算霍夫圆在二值图上的轮廓支持度我们可以将两种方法的结果放在同一个置信度体系下比较。5. 性能优化与高级技巧当处理高分辨率图像或需要实时检测时性能至关重要。5.1 多尺度检测与ROI感兴趣区域聚焦如果图像中圆的大小差异巨大或者图像很大但圆只出现在特定区域我们可以优化多尺度如果不知道圆的尺度可以对图像进行金字塔下采样Cv2.PyrDown在不同尺度空间进行检测然后再将结果映射回原图坐标。ROI聚焦如果知道圆可能出现的大致区域先用一个粗略的方法如斑点检测SimpleBlobDetector或颜色分割确定候选区域然后只在每个ROI内进行精细的找圆计算这能极大减少计算量。public ListDetectedCircle FindCirclesWithROI(Mat srcImage) { // 步骤1粗略定位潜在区域例如通过颜色阈值或亮度 using Mat hsv new Mat(); Cv2.CvtColor(srcImage, hsv, ColorConversionCodes.BGR2HSV); // 假设我们要找亮色区域 Mat mask new Mat(); Cv2.InRange(hsv, new Scalar(0, 0, 200), new Scalar(180, 30, 255), mask); // 步骤2查找粗略区域的轮廓 Point[][] roughContours; HierarchyIndex[] roughHierarchy; Cv2.FindContours(mask, out roughContours, out roughHierarchy, RetrievalModes.External, ContourApproximationModes.ApproxSimple); var allCircles new ListDetectedCircle(); foreach (var roughContour in roughContours) { Rect roiRect Cv2.BoundingRect(roughContour); // 扩大ROI区域确保圆边界完整 roiRect roiRect.Inflate(20, 20); roiRect roiRect.Intersect(new Rect(0, 0, srcImage.Width, srcImage.Height)); // 确保不越界 if (roiRect.Width 50 || roiRect.Height 50) continue; // 忽略太小的区域 // 步骤3在ROI内进行精细找圆 Mat roi new Mat(srcImage, roiRect); var circlesInROI FindCirclesFusion(roi); // 使用我们之前融合的方法 // 步骤4将ROI内的坐标转换回原图坐标 foreach (var circle in circlesInROI) { circle.Center.X roiRect.X; circle.Center.Y roiRect.Y; allCircles.Add(circle); } roi.Dispose(); } mask.Dispose(); hsv.Dispose(); return NonMaximumSuppression(allCircles, 0.6); }5.2 利用GPU加速如果可用对于极其耗时的操作如在大图上进行霍夫变换如果系统有NVIDIA GPU且安装了CUDA和OpenCv的GPU模块OpenCvSharp.Gpu但注意此模块维护状态可能不如主库可以尝试使用。不过对于大多数应用CPU优化和多线程处理已经足够。更实用的CPU端优化是使用Parallel.ForEach对多个ROI或不同的预处理参数进行并行处理。// 示例并行处理多个预处理参数例如不同的高斯模糊核大小以找到最佳结果 var blurSizes new[] { 3, 5, 7 }; var results new ConcurrentBagListDetectedCircle(); Parallel.ForEach(blurSizes, kernelSize { using Mat processed PreprocessImageWithCustomBlur(srcImage, kernelSize); var circles FindCirclesByContour(processed, srcImage); if (circles.Any()) results.Add(circles); }); // 合并并筛选所有并行任务的结果6. 实战调试与常见问题排查理论再完美也要经过实战调试。以下是我在项目中遇到的一些典型问题及解决方法。6.1 问题速查表问题现象可能原因排查与解决思路完全检测不到圆1. 预处理过度边缘被抹掉。2. 二值化阈值不当圆区域未突出。3. 轮廓面积/圆度阈值设得过高。1. 逐步显示预处理每一步的结果图Cv2.ImShow检查边缘是否保留。2. 尝试不同的二值化方法全局阈值、自适应阈值、大津法Cv2.ThresholdTypes.Otsu。3. 逐步调低筛选阈值观察中间结果。误检太多把方块、三角形当圆1. 圆度(circularity)阈值过低。2. 轮廓支持度(supportRatio)检查不严。3. 未使用梯度验证。1. 将圆度阈值提高到0.75甚至0.8。2. 提高支持度阈值或增加圆周采样点密度。3. 启用并严格化梯度验证逻辑。检测到的圆半径不准1.MinEnclosingCircle对不规则轮廓拟合有误差。2. 图像存在透视畸变圆变椭圆。1. 尝试使用FitEllipse拟合椭圆然后取长短轴平均值作为半径估计。2. 如果场景固定可先进行相机标定和图像畸变校正。部分圆如半圆检测不到1. 轮廓提取模式RetrievalModes.External只找外层闭合轮廓。2. 圆度计算对不闭合轮廓无效。1. 尝试使用RetrievalModes.List获取所有轮廓并尝试连接边缘断点形态学闭运算。2. 对于弧段可考虑使用RANSAC拟合圆算法。算法速度太慢1. 在全图高分辨率下直接处理。2. 霍夫变换参数dp设置太小计算量大。3. 轮廓数量太多。1. 先缩放图像到合适尺寸或使用ROI。2. 适当增大dp如从1.0改为1.5或2.0。3. 在轮廓分析前用面积阈值过滤掉大量小轮廓。6.2 调试技巧可视化中间结果在开发过程中将关键步骤的结果可视化是最高效的调试手段。我为算法类添加了一个调试模式public ListDetectedCircle FindCircles(Mat srcImage, bool debugMode false) { Mat binary PreprocessImage(srcImage); if (debugMode) Cv2.ImShow(1. Binary Image, binary); Point[][] contours; HierarchyIndex[] hierarchy; Cv2.FindContours(binary, out contours, out hierarchy, RetrievalModes.External, ContourApproximationModes.ApproxSimple); Mat contourImage Mat.Zeros(srcImage.Size(), MatType.CV_8UC3); Cv2.DrawContours(contourImage, contours, -1, Scalar.Green, 1); if (debugMode) Cv2.ImShow(2. All Contours, contourImage); // ... 后续处理逻辑每一步都可以绘制中间状态 // 例如绘制所有拟合的圆 Mat circleImage srcImage.Clone(); foreach (var contour in contours) { Cv2.MinEnclosingCircle(contour, out Point2f c, out float r); Cv2.Circle(circleImage, (int)c.X, (int)c.Y, (int)r, Scalar.Blue, 1); } if (debugMode) Cv2.ImShow(3. All Fitted Circles, circleImage); // ... 最终筛选和绘制 return finalCircles; }通过依次显示二值图、所有轮廓、所有拟合圆你可以清晰地看到算法在哪一步出现了偏差从而有针对性地调整参数或逻辑。6.3 处理特别挑战性的场景重叠圆如果两个圆部分重叠轮廓提取可能会得到一个“8”字形的轮廓。简单的MinEnclosingCircle会拟合出一个大圆。解决方法是在轮廓分析阶段检查轮廓的凸性缺陷(ConvexityDefects)或者尝试使用分水岭算法(Watershed)先分割粘连区域。明暗不均的环一个圆环如果内外亮度差异不大二值化可能无法形成闭合轮廓。可以尝试使用Canny边缘检测代替二值化或者使用基于梯度的边缘检测方法。大量噪声背景在强噪声下自适应阈值可能失效。可以尝试使用非局部均值去噪(Cv2.FastNlMeansDenoising)或在频域进行滤波。构建一个“功能强大”的找圆算法本质上是构建一个针对特定场景的特征提取与决策管道。没有放之四海而皆准的参数最好的方法就是深入理解你的图像数据通过系统的调试和迭代让算法与你的应用场景完美契合。从简单的HoughCircles调用开始逐步增加预处理、后验证和融合策略你会发现解决实际视觉问题的能力在稳步提升。本文还有配套的精品资源点击获取
返回列表