OpencvSharp 算子学习教案之 - Cv2.MorphologyEx
OpencvSharp 算子学习教案之 - Cv2.MorphologyEx大家好Opencv在很多工程项目中都会用到而OpencvSharp则是以C#开发与实现的Opencv操作库对.NET开发人员友好但很多API的中文资料、应用场景及常见坑点等缺乏系统性归纳因此这系列博客将给大家带来Cv2及Mat对象全系列算子学习教案供大家参考学习。Cv2.MorphologyEx教案版本V1.0面向对象OpenCvSharp 初学者所属模块imgproc源码位置OpenCvSharp/Cv2/Cv2_imgproc.cs摘要Cv2.MorphologyEx 是复合形态学操作的统一入口Open、Close、Gradient 等常见操作都从这里进入。本文通过同一张二值图对比三种运算结果帮助初学者理解这些复合操作其实都是膨胀和腐蚀的组合。1. 函数名称带参数签名publicstaticvoidMorphologyEx(InputArraysrc,OutputArraydst,MorphTypesop,InputArray?element,Point?anchornull,intiterations1,BorderTypesborderTypeBorderTypes.Constant,Scalar?borderValuenull)2. 函数用途Cv2.MorphologyEx的作用是执行更高级的形态学组合操作。它常见的用途有开运算去噪。闭运算补洞。形态学梯度提取轮廓。把常见形态学处理统一到一个入口里。和单独调用Dilate或Erode相比MorphologyEx更适合表达“组合运算”的意图。3. 函数公式几种最常见的复合形态学操作可以写成Open(A)(A⊖B)⊕B Open(A) (A \ominus B) \oplus BOpen(A)(A⊖B)⊕BClose(A)(A⊕B)⊖B Close(A) (A \oplus B) \ominus BClose(A)(A⊕B)⊖BGradient(A)(A⊕B)−(A⊖B) Gradient(A) (A \oplus B) - (A \ominus B)Gradient(A)(A⊕B)−(A⊖B)其中AAA是输入图像BBB是结构元素。4. 函数原理说明MorphologyEx 本质上不是一种全新的基础操作而是把常见的膨胀和腐蚀组合起来。Open 通常先腐蚀再膨胀适合去除小白噪点。Close 通常先膨胀再腐蚀适合填补小黑洞。Gradient 会把边界提出来结果通常更像轮廓带。结构元素和迭代次数依旧会影响结果强弱。当你理解了Dilate和ErodeMorphologyEx就会变得很容易。5. 参数含义解析参数名类型必填含义srcInputArray是输入图像通常是二值图或灰度图dstOutputArray是输出图像opMorphTypes是复合形态学操作类型elementInputArray?否结构元素anchorPoint?否锚点位置默认在中心iterationsint否迭代次数borderTypeBorderTypes否边界外推方式borderValueScalar?否常量边界模式下的边界值补充说明MorphTypes.Open、MorphTypes.Close、MorphTypes.Gradient是最常见的教学入口。如果你已经知道自己只是要腐蚀或膨胀也可以直接用专门函数。MorphologyEx的优势是把“意图”说得更清楚。教学时适合用同一张图对比不同op的效果。6. 应用场景列表场景名场景说明典型用途场景AOpen 去噪去掉孤立白点教学入门场景BClose 补洞填补小黑洞图像修复场景CGradient 看边界提取轮廓带轮廓教学场景D统一入口一次讲完多种形态学理论学习7. 函数使用示例下面的 Console 程序演示Cv2.MorphologyEx。示例会对同一张二值图分别执行Open、Close和Gradient三种操作帮助你建立整体直觉。usingSystem;usingSystem.Globalization;usingSystem.Linq;usingSystem.Text;usingOpenCvSharp;internalstaticclassProgram{/// summary/// 程序入口。/// /summaryprivatestaticvoidMain(){// 先让控制台可以正确显示中文说明。Console.OutputEncodingEncoding.UTF8;usingvarsourceCreateMorphologySourceImage();usingvarkernelCv2.GetStructuringElement(MorphShapes.Ellipse,newSize(5,5));usingvaropenednewMat();usingvarclosednewMat();usingvargradientnewMat();// Open 先腐蚀再膨胀通常用于去噪。Cv2.MorphologyEx(source,opened,MorphTypes.Open,kernel,iterations:1,borderType:BorderTypes.Constant,borderValue:Cv2.MorphologyDefaultBorderValue());// Close 先膨胀再腐蚀通常用于补洞。Cv2.MorphologyEx(source,closed,MorphTypes.Close,kernel,iterations:1,borderType:BorderTypes.Constant,borderValue:Cv2.MorphologyDefaultBorderValue());// Gradient 会突出边界。Cv2.MorphologyEx(source,gradient,MorphTypes.Gradient,kernel,iterations:1,borderType:BorderTypes.Constant,borderValue:Cv2.MorphologyDefaultBorderValue());Cv2.ImWrite(morphologyex-source.png,source);Cv2.ImWrite(morphologyex-open.png,opened);Cv2.ImWrite(morphologyex-close.png,closed);Cv2.ImWrite(morphologyex-gradient.png,gradient);Cv2.ImWrite(morphologyex-kernel.png,CreateKernelPreview(kernel));Console.WriteLine(场景AMorphologyEx(InputArray src, OutputArray dst, MorphTypes op, InputArray? element, Point? anchor null, int iterations 1, BorderTypes borderType BorderTypes.Constant, Scalar? borderValue null));Console.WriteLine(MorphologyEx 是复合形态学的统一入口。\n);Console.WriteLine($源图{DescribeBinaryMat(source)});Console.WriteLine($5x5 Ellipse 核{DescribeKernel(kernel)});Console.WriteLine($结构元素矩阵\n{FormatKernelMatrix(kernel)});Console.WriteLine();AppendCaseReport(结果AOpen,source,opened,开运算通常用来去除孤立白噪点并保留较大的主体结构。);Console.WriteLine();AppendCaseReport(结果BClose,source,closed,闭运算通常用来填补小黑洞并让断裂的前景更容易连起来。);Console.WriteLine();AppendCaseReport(结果CGradient,source,gradient,形态学梯度会把边界提出来结果通常更像一条轮廓带。);}/// 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/// 把结构元素矩阵逐行格式化成文本。/// /summaryprivatestaticstringFormatKernelMatrix(Matkernel){varvaluesReadMatAsDoubleMatrix(kernel);varsbnewStringBuilder();for(varrow0;rowvalues.GetLength(0);row){sb.Append([);for(varcol0;colvalues.GetLength(1);col){sb.Append(values[row,col].ToString(F0,CultureInfo.InvariantCulture));if(colvalues.GetLength(1)-1){sb.Append(, );}}sb.AppendLine(]);}returnsb.ToString();}/// 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. 注意事项MorphologyEx不是一种全新的基础运算而是膨胀和腐蚀的组合。Open、Close、Gradient分别对应不同的教学目标。结构元素和迭代次数依旧会影响最终结果。如果你理解了Dilate和Erode再看MorphologyEx会轻松很多。9. 调优建议教学时先固定结构元素只观察op的变化。如果你在做去噪先看Open。如果你在做补洞先看Close。如果你想看轮廓先看Gradient。10. 运行说明如果你在控制台工程里运行本文示例直接把代码放到Program.cs即可。如果你在本仓库里学习请直接打开 WPF 控件Cv2.MorphologyEx点击“运行场景A”。WPF 示例会同时展示 Open、Close 和 Gradient 的结果便于你整体理解复合形态学。11. 常见错误排查把MorphologyEx当成独立于膨胀和腐蚀的新东西。不区分 Open、Close 和 Gradient 的用途。忘记结构元素对结果的影响。只看结果图不看操作背后的组合逻辑。