OpencvSharp 算子学习教案之 - Cv2.Erode
OpencvSharp 算子学习教案之 - Cv2.Erode大家好Opencv在很多工程项目中都会用到而OpencvSharp则是以C#开发与实现的Opencv操作库对.NET开发人员友好但很多API的中文资料、应用场景及常见坑点等缺乏系统性归纳因此这系列博客将给大家带来Cv2及Mat对象全系列算子学习教案供大家参考学习。Cv2.Erode教案版本V1.0面向对象OpenCvSharp 初学者所属模块imgproc源码位置OpenCvSharp/Cv2/Cv2_imgproc.cs摘要Cv2.Erode 会让二值图或灰度图中的前景向内收缩是理解形态学“腐蚀”概念的起点。本文用两组结构元素和迭代次数对比帮助初学者观察白色区域如何变小以及它和 Dilate 的相反关系。1. 函数名称带参数签名publicstaticvoidErode(InputArraysrc,OutputArraydst,InputArray?element,Point?anchornull,intiterations1,BorderTypesborderTypeBorderTypes.Constant,Scalar?borderValuenull)2. 函数用途Cv2.Erode的作用是让前景区域向内收缩。它常见的用途有去掉孤立白点。收紧目标轮廓。断开非常细的连接。和Dilate配合组成更复杂的形态学流程。如果你想先建立“白色会变小”的直觉先看 Erode 最合适。3. 函数公式对二值或灰度图来说腐蚀可以理解成局部最小值操作dst(x,y)min(i,j)∈Bsrc(xi,yj) dst(x, y) \min_{(i, j) \in B} src(x i, y j)dst(x,y)(i,j)∈Bminsrc(xi,yj)其中BBB是结构元素。直观地说只要结构元素覆盖范围里有黑色背景结果就更容易变黑。4. 函数原理说明Erode 的教学理解可以分成四步选择一个结构元素。把结构元素放到图像上的每个位置。只要覆盖范围里有背景结果就可能被“压暗”。重复iterations次后收缩效果会更明显。它的视觉效果通常是白色区域变小细小突起会被削掉细线也更容易断开。5. 参数含义解析参数名类型必填含义srcInputArray是输入图像通常是二值图或灰度图dstOutputArray是输出图像大小和类型与输入一致elementInputArray?否结构元素决定腐蚀的形状和范围anchorPoint?否锚点位置默认是结构元素中心iterationsint否腐蚀次数borderTypeBorderTypes否边界外推方式borderValueScalar?否常量边界模式下的边界值补充说明element越大腐蚀越明显。iterations越多结果越“瘦”。教学时常用MorphShapes.Rect、MorphShapes.Ellipse、MorphShapes.Cross。常量边界模式下通常配合Cv2.MorphologyDefaultBorderValue()使用。6. 应用场景列表场景名场景说明典型用途场景A前景收缩观察白色区域如何变小教学入门场景B噪点清理去掉孤立白点二值修复场景C轮廓收紧让目标边界更干净预处理场景D与膨胀对照对比Dilate的相反效果理论学习7. 函数使用示例下面的 Console 程序演示Cv2.Erode。示例会对同一张二值图使用两组不同的结构元素和迭代次数让你直接看到前景收缩的差异。usingSystem;usingSystem.Globalization;usingSystem.Linq;usingSystem.Text;usingOpenCvSharp;internalstaticclassProgram{/// summary/// 程序入口。/// /summaryprivatestaticvoidMain(){// 先让控制台可以正确显示中文说明。Console.OutputEncodingEncoding.UTF8;usingvarsourceCreateMorphologySourceImage();usingvarrectKernelCv2.GetStructuringElement(MorphShapes.Rect,newSize(3,3));usingvarellipseKernelCv2.GetStructuringElement(MorphShapes.Ellipse,newSize(7,7));usingvarrectErodednewMat();usingvarellipseErodednewMat();// 腐蚀会把白色前景往里缩所以我们用两组参数做对比。Cv2.Erode(source,rectEroded,rectKernel,iterations:1,borderType:BorderTypes.Constant,borderValue:Cv2.MorphologyDefaultBorderValue());Cv2.Erode(source,ellipseEroded,ellipseKernel,iterations:2,borderType:BorderTypes.Constant,borderValue:Cv2.MorphologyDefaultBorderValue());Cv2.ImWrite(erode-source.png,source);Cv2.ImWrite(erode-rect.png,rectEroded);Cv2.ImWrite(erode-ellipse.png,ellipseEroded);Cv2.ImWrite(erode-ellipse-kernel.png,CreateKernelPreview(ellipseKernel));Console.WriteLine(场景AErode(InputArray src, OutputArray dst, InputArray? element, Point? anchor null, int iterations 1, BorderTypes borderType BorderTypes.Constant, Scalar? borderValue null));Console.WriteLine(Erode 会让白色前景向内收缩。\n);Console.WriteLine($源图{DescribeBinaryMat(source)});Console.WriteLine($3x3 Rect 核{DescribeKernel(rectKernel)});Console.WriteLine($7x7 Ellipse 核{DescribeKernel(ellipseKernel)});Console.WriteLine();AppendCaseReport(结果A3x3 Rect 1 次,source,rectEroded,这组参数会轻微收缩前景适合先观察边缘如何往内退。);Console.WriteLine();AppendCaseReport(结果B7x7 Ellipse 2 次,source,ellipseEroded,这组参数更强细线条和小白块更容易被吞掉。);}/// summary/// 创建一张适合形态学演示的二值图。/// /summaryprivatestaticMatCreateMorphologySourceImage(){varcanvasnewMat(480,640,MatType.CV_8UC1,newScalar(0));// 这张图里包含大块前景、孔洞、细线和孤立区域适合观察腐蚀的变化。Cv2.Rectangle(canvas,newRect(44,44,184,132),newScalar(255),-1,LineTypes.Link8);Cv2.Rectangle(canvas,newRect(84,82,34,34),newScalar(0),-1,LineTypes.Link8);Cv2.Circle(canvas,newPoint(178,132),14,newScalar(0),-1,LineTypes.Link8);Cv2.Circle(canvas,newPoint(348,118),58,newScalar(255),-1,LineTypes.Link8);Cv2.Circle(canvas,newPoint(348,118),18,newScalar(0),-1,LineTypes.Link8);Cv2.Line(canvas,newPoint(62,282),newPoint(568,282),newScalar(255),6,LineTypes.Link8);Cv2.Line(canvas,newPoint(390,194),newPoint(514,92),newScalar(255),4,LineTypes.Link8);Cv2.Rectangle(canvas,newRect(68,248,124,58),newScalar(255),-1,LineTypes.Link8);Cv2.Rectangle(canvas,newRect(102,264,28,18),newScalar(0),-1,LineTypes.Link8);Cv2.Ellipse(canvas,newPoint(218,320),newSize(92,60),18,0,360,newScalar(255),-1,LineTypes.Link8);Cv2.Circle(canvas,newPoint(488,314),44,newScalar(255),-1,LineTypes.Link8);Cv2.Circle(canvas,newPoint(488,314),10,newScalar(0),-1,LineTypes.Link8);returnAddSaltAndPepperNoise(canvas,0.008,2026);}/// summary/// 给二值图叠加可重复的椒盐噪声。/// /summaryprivatestaticMatAddSaltAndPepperNoise(Matsource,doublenoiseRatio,intseed){varrngnewRandom(seed);varnoisysource.Clone();varflipCountMath.Max(1,(int)Math.Round(noisy.Rows*noisy.Cols*noiseRatio));// 随机翻转像素值会同时产生白点和黑洞。for(varindex0;indexflipCount;index){varrowrng.Next(noisy.Rows);varcolrng.Next(noisy.Cols);noisy.Atbyte(row,col)noisy.Atbyte(row,col)0?(byte)255:(byte)0;}returnnoisy;}/// summary/// 把结构元素缩放成便于观察的预览图。/// /summaryprivatestaticMatCreateKernelPreview(Matkernel,intscale28){usingvarkernel64newMat();usingvarnormalizednewMat();usingvarenlargednewMat();kernel.ConvertTo(kernel64,MatType.CV_64FC1);Cv2.Normalize(kernel64,normalized,0,255,NormTypes.MinMax,(int)MatType.CV_8UC1);Cv2.Resize(normalized,enlarged,newSize(kernel.Cols*scale,kernel.Rows*scale),0,0,InterpolationFlags.Nearest);returnenlarged;}/// summary/// 创建带标签的预览图。/// /summaryprivatestaticMatCreateLabeledPreview(Matimage,stringlabel){varpreviewnewMat();if(image.Channels()1){Cv2.CvtColor(image,preview,ColorConversionCodes.GRAY2BGR);}else{previewimage.Clone();}Cv2.PutText(preview,label,newPoint(18,34),HersheyFonts.HersheySimplex,0.9,newScalar(255,255,255),3,LineTypes.AntiAlias);Cv2.PutText(preview,label,newPoint(18,34),HersheyFonts.HersheySimplex,0.9,newScalar(35,35,35),1,LineTypes.AntiAlias);returnpreview;}/// summary/// 把二值图格式化成便于教学阅读的摘要。/// /summaryprivatestaticstringDescribeBinaryMat(Matimage){Cv2.MinMaxLoc(image,outvarminVal,outvarmaxVal);varactivePixelsCv2.CountNonZero(image);varratioactivePixels*100.0/(image.Rows*image.Cols);return$Size{image.Width}x{image.Height}, ForegroundPixels{activePixels}, ForegroundRatio{ratio.ToString(F2,CultureInfo.InvariantCulture)}%, Min{minVal.ToString(F0,CultureInfo.InvariantCulture)}, Max{maxVal.ToString(F0,CultureInfo.InvariantCulture)};}/// summary/// 描述结构元素的核心信息。/// /summaryprivatestaticstringDescribeKernel(Matkernel){Cv2.MinMaxLoc(kernel,outvarminVal,outvarmaxVal);varactiveCellsCv2.CountNonZero(kernel);return$Size{kernel.Width}x{kernel.Height}, ActiveCells{activeCells}, Min{minVal.ToString(F0,CultureInfo.InvariantCulture)}, Max{maxVal.ToString(F0,CultureInfo.InvariantCulture)};}/// summary/// 追加一个完整的案例报告块。/// /summaryprivatestaticvoidAppendCaseReport(stringtitle,Matsource,Matresult,stringcomment){Console.WriteLine(title);Console.WriteLine(comment);Console.WriteLine($源图摘要{DescribeBinaryMat(source)});Console.WriteLine($结果摘要{DescribeBinaryMat(result)});Console.WriteLine($前景像素变化{Cv2.CountNonZero(result)-Cv2.CountNonZero(source):0;-0;0});usingvardiffnewMat();Cv2.Absdiff(source,result,diff);Console.WriteLine($实际被改动的像素数{Cv2.CountNonZero(diff)});}}8. 注意事项腐蚀会让前景变小不要把它和膨胀的效果混淆。element的形状会直接影响腐蚀后的轮廓。iterations越多结果越容易被“吃掉”。如果你不理解边界行为先默认使用Cv2.MorphologyDefaultBorderValue()。9. 调优建议教学时先从小结构元素开始比如3x3 Rect。如果只是想轻微收缩不要把iterations设得太大。如果希望保留更多圆角可以试试Ellipse。如果前景已经很细先观察腐蚀后是否还保留了你真正需要的结构。10. 运行说明如果你在控制台工程里运行本文示例直接把代码放到Program.cs即可。如果你在本仓库里学习请直接打开 WPF 控件Cv2.Erode点击“运行场景A”。WPF 示例会同时展示源图、结果图和结构元素预览方便你直观看到腐蚀效果。11. 常见错误排查输入图不是二值图却期待它像二值图那样腐蚀。结构元素太大导致目标细节直接被吞掉。只盯着结果大小变化却忘了观察轮廓形状变化。没有把腐蚀和膨胀成对理解。