OpencvSharp 算子学习教案之 - Cv2.Sobel
OpencvSharp 算子学习教案之 - Cv2.Sobel大家好Opencv在很多工程项目中都会用到而OpencvSharp则是以C#开发与实现的Opencv操作库对.NET开发人员友好但很多API的中文资料、应用场景及常见坑点等缺乏系统性归纳因此这系列博客将给大家带来Cv2及Mat对象全系列算子学习教案供大家参考学习。Cv2.Sobel教案版本V1.0面向对象OpenCvSharp 初学者所属模块imgproc源码位置OpenCvSharp/Cv2/Cv2_imgproc.cs:397摘要Sobel 用来计算图像的一阶导数是边缘检测入门最重要的算子之一。本文会先讲清 dx / dy 和 ksize 的关系再用一个带简单几何图形的示例演示 x、y 方向导数和梯度幅值。1. 函数名称带参数签名publicstaticvoidSobel(InputArraysrc,OutputArraydst,MatTypeddepth,intdx,intdy,intksize3,doublescale1,doubledelta0,BorderTypesborderTypeBorderTypes.Default)2. 函数用途Cv2.Sobel用来计算图像的导数响应。它既可以求 x 方向导数也可以求 y 方向导数还可以用于二阶或混合导数。它最常见的用途有提取水平边缘或垂直边缘。作为梯度、角点、边缘检测的前置步骤。和Cv2.Magnitude、Cv2.Phase组合得到梯度幅值和方向。作为SpatialGradient、Scharr、Laplacian的理论基础。如果要给初学者讲“边缘为什么会出现”Sobel 是最适合的入口。3. 函数公式Sobel 可以近似写成dst∂dxdysrc∂xdx∂ydy dst \frac{\partial^{dxdy} src}{\partial x^{dx}\partial y^{dy}}dst∂xdx∂ydy∂dxdysrc当dx1, dy0时表示求 x 方向一阶导数当dx0, dy1时表示求 y 方向一阶导数。在最常见的ksize3情况下它的两个一维核可以写成Gx[−101−202−101] G_x \begin{bmatrix}-1 0 1\\-2 0 2\\-1 0 1\end{bmatrix}Gx−1−2−1000121Gy[−1−2−1000121] G_y \begin{bmatrix}-1 -2 -1\\0 0 0\\1 2 1\end{bmatrix}Gy−101−202−101这些核同时带有平滑和求导的效果所以 Sobel 比“直接做差分”更稳定。4. 函数原理说明Sobel 的核心思想是先在一个方向上做局部平滑再在另一个方向上做微分。dx和dy不是坐标而是求导阶数。ksize越大平滑范围越大导数响应通常越宽。ksize1时不会做额外平滑。ddepth通常要选有符号类型避免负值被截断。borderType决定边缘像素外面怎么补值。在教学里Sobel 常常被拿来说明“导数不只是数学公式它直接对应图像里的边缘强度变化”。5. 参数含义解析参数名类型必填含义srcInputArray是输入图像dstOutputArray是输出图像ddepthMatType是输出深度dxint是x 方向导数阶数dyint是y 方向导数阶数ksizeint否Sobel 核大小默认 3scaledouble否结果缩放系数deltadouble否结果偏移量borderTypeBorderTypes否边界外推方式补充说明ksize只能取 1、3、5、7或者特殊值FILTER_SCHARR。8 位输入做导数时常见做法是把结果存进CV_16S。如果想显示得更明显可以适当调整scale或后处理到 8 位图。BORDER_WRAP不支持。6. 应用场景列表场景名场景说明典型用途场景Ax 方向导数观察左右变化垂直边缘场景By 方向导数观察上下变化水平边缘场景C梯度幅值合成两个方向的强度边缘检测场景D更大 ksize比较平滑强度教学对照7. 函数使用示例下面的 Console 程序演示Cv2.Sobel。示例会生成一张带几何图形的测试图然后分别计算 x / y 一阶导数并把梯度幅值一起打印出来。usingSystem;usingSystem.Text;usingOpenCvSharp;internalstaticclassProgram{privatestaticvoidMain(){// 让控制台可以正确显示中文说明。Console.OutputEncodingEncoding.UTF8;RunDemo();}/// summary/// 运行 Sobel 教学示例。/// /summaryprivatestaticvoidRunDemo(){usingvarsourceCreateDemoImage();usingvargraySourcenewMat();// Sobel 通常先在灰度图上观察这样梯度更容易解释。Cv2.CvtColor(source,graySource,ColorConversionCodes.BGR2GRAY);usingvarsobelXnewMat();usingvarsobelYnewMat();usingvarsobelX5newMat();// 8 位图像做导数时最常见的做法是把结果放到 16 位有符号图像里。Cv2.Sobel(graySource,sobelX,MatType.CV_16S,1,0,3,1.0,0.0,BorderTypes.Default);Cv2.Sobel(graySource,sobelY,MatType.CV_16S,0,1,3,1.0,0.0,BorderTypes.Default);Cv2.Sobel(graySource,sobelX5,MatType.CV_16S,1,0,5,1.0,0.0,BorderTypes.Default);usingvarsobelXFloatnewMat();usingvarsobelYFloatnewMat();usingvargradientMagnitudenewMat();// 计算梯度幅值时需要先把 16 位有符号结果转成浮点。sobelX.ConvertTo(sobelXFloat,MatType.CV_32F);sobelY.ConvertTo(sobelYFloat,MatType.CV_32F);Cv2.Magnitude(sobelXFloat,sobelYFloat,gradientMagnitude);Console.WriteLine(场景ASobel(InputArray src, OutputArray dst, MatType ddepth, int dx, int dy, int ksize 3, double scale 1, double delta 0, BorderTypes borderType BorderTypes.Default));Console.WriteLine(Sobel 可以分别计算 x / y 方向导数也可以把两个方向的导数合成梯度幅值。\n);Console.WriteLine($源图{DescribeMat(source)});Console.WriteLine($灰度图{DescribeMat(graySource)});Console.WriteLine($Sobel X{DescribeMat(sobelX)});Console.WriteLine($Sobel Y{DescribeMat(sobelY)});Console.WriteLine($Sobel X(ksize5){DescribeMat(sobelX5)});Console.WriteLine($Sobel X 与 ksize5 的差异{DescribeDifferenceStatistics(sobelX,sobelX5)});Console.WriteLine($梯度幅值{DescribeMat(gradientMagnitude)});Console.WriteLine();// 把预览保存出来方便在文件浏览器里快速查看。SavePreview(sobel-source.png,graySource);SavePreview(sobel-x.png,sobelX);SavePreview(sobel-y.png,sobelY);SavePreview(sobel-magnitude.png,gradientMagnitude);Console.WriteLine(教学结论Sobel 最适合拿来讲一阶导数、边缘方向和梯度幅值的基本概念。\n);}/// summary/// 创建一张用于教学的测试图。/// /summaryprivatestaticMatCreateDemoImage(){varcanvasnewMat(400,400,MatType.CV_8UC3,newScalar(245,242,236));Cv2.Rectangle(canvas,newRect(48,52,122,120),newScalar(80,175,250),-1,LineTypes.AntiAlias);Cv2.Circle(canvas,newPoint(288,104),50,newScalar(128,214,112),-1,LineTypes.AntiAlias);Cv2.Line(canvas,newPoint(52,260),newPoint(350,330),newScalar(60,70,80),5,LineTypes.AntiAlias);Cv2.PutText(canvas,Sobel,newPoint(48,370),HersheyFonts.HersheySimplex,0.9,newScalar(42,40,36),2,LineTypes.AntiAlias);returncanvas;}/// summary/// 保存一个适合观察的单通道预览图。/// /summaryprivatestaticvoidSavePreview(stringfileName,Matimage){usingvarpreviewnewMat();Cv2.Normalize(image,preview,0,255,NormTypes.MinMax,(int)MatType.CV_8UC1);Cv2.ImWrite(fileName,preview);}/// summary/// 描述一个 Mat 的核心信息。/// /summaryprivatestaticstringDescribeMat(Matmat){return$Size{mat.Width}x{mat.Height}, Channels{mat.Channels()}, Type{mat.Type()}, Depth{mat.Depth()};}/// summary/// 描述两个图像的差异。/// /summaryprivatestaticstringDescribeDifferenceStatistics(Matleft,Matright){usingvardiffnewMat();Cv2.Absdiff(left,right,diff);Cv2.MeanStdDev(diff,outvarmean,outvarstddev);Cv2.MinMaxLoc(diff,outdoubleminVal,outdoublemaxVal);return$均值{mean.Val0:F2}, 标准差{stddev.Val0:F2}, 最小值{minVal:F0}, 最大值{maxVal:F0};}}8. 注意事项8 位输入做导数时不要直接用CV_8U存输出否则负值会丢失。ksize1和ksize3的视觉效果差别很明显初学者最好先从 3x3 开始看。如果你想观察边缘方向最好分别看dx1, dy0和dx0, dy1。BORDER_WRAP不支持。9. 调优建议初学者先把ddepth固定成CV_16S。如果边缘不明显可以给预览图做归一化或ConvertScaleAbs。想更平滑一点可以试试ksize5。想更精细地看方向可以把 x、y 方向分别保存出来对照。10. 运行说明如果你在控制台工程里运行本文示例直接把代码放到Program.cs即可。如果你在本仓库里学习请打开 WPF 控件Cv2.Sobel点击“运行场景A”后查看右侧文本框和预览图。WPF 示例会同时展示 Sobel X、Sobel Y 和梯度幅值方便初学者建立直觉。11. 常见错误排查把dx/dy当成图片坐标。用 8 位输出保存导数导致负值被截断。忘记灰度化直接拿彩色图结果做对比却不理解每个通道的意义。误以为 Sobel 只会做一阶导数其实它也支持更高阶或混合导数。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2606068.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!