OpencvSharp 算子学习教案之 - Cv2.SpatialGradient大家好Opencv在很多工程项目中都会用到而OpencvSharp则是以C#开发与实现的Opencv操作库对.NET开发人员友好但很多API的中文资料、应用场景及常见坑点等缺乏系统性归纳因此这系列博客将给大家带来Cv2及Mat对象全系列算子学习教案供大家参考学习。Cv2.SpatialGradient教案版本V1.0面向对象OpenCvSharp 初学者所属模块imgproc源码位置OpenCvSharp/Cv2/Cv2_imgproc.cs:426摘要SpatialGradient 的作用是一次性返回图像在 x 和 y 两个方向上的一阶导数。它最适合用来讲“两个方向的 Sobel 可以一起算出来”这个概念也很适合和 Canny 的前置梯度步骤连接起来理解。1. 函数名称带参数签名publicstaticvoidSpatialGradient(InputArraysrc,OutputArraydx,OutputArraydy,intksize3,BorderTypesborderTypeBorderTypes.Default)2. 函数用途Cv2.SpatialGradient用来同时计算图像在 x 和 y 两个方向上的一阶导数。它最常见的用途有同时得到水平和垂直边缘响应。作为梯度计算的前置步骤。用在需要同时处理 dx / dy 的算法里。作为 Sobel 的教学简化接口。如果你已经知道 Sobel那可以把 SpatialGradient 理解为“打包版的双方向 Sobel”。3. 函数公式官方文档说明SpatialGradient等价于下面两次 Sobel 调用Sobel(src,dx,CV_16SC1,1,0,3) Sobel(src, dx, CV\_16SC1, 1, 0, 3)Sobel(src,dx,CV_16SC1,1,0,3)Sobel(src,dy,CV_16SC1,0,1,3) Sobel(src, dy, CV\_16SC1, 0, 1, 3)Sobel(src,dy,CV_16SC1,0,1,3)也就是说它本质上就是一次性得到两个方向的一阶导数。4. 函数原理说明这个函数专门为“同时看 x 和 y 导数”设计。dx输出 x 方向导数。dy输出 y 方向导数。ksize只能取 3。borderType在这个接口里只允许默认边界模式。输出类型固定是 16 位有符号单通道图像。如果你的后续处理还要继续做 Canny、角点或梯度方向分析这个接口会很方便。5. 参数含义解析参数名类型必填含义srcInputArray是输入图像dxOutputArray是x 方向导数dyOutputArray是y 方向导数ksizeint否Sobel 核大小必须是 3borderTypeBorderTypes否边界外推方式必须是默认模式补充说明dx/dy都是 16 位有符号单通道图像。输出和输入尺寸保持一致。它特别适合教学因为一眼就能看到两个方向的结果。如果你要继续做边缘检测通常会把它和Cv2.Canny组合起来。6. 应用场景列表场景名场景说明典型用途场景A双方向导数同时看 dx 和 dy梯度分析场景BSobel 对照和两次 Sobel 做对比教学讲解场景C边缘前置提供给 Canny 使用边缘检测场景D梯度合成与 Magnitude 组合方向与强度分析7. 函数使用示例下面的 Console 程序演示Cv2.SpatialGradient。示例会生成一张测试图然后把 SpatialGradient 的结果和两次 Sobel 的结果放在一起对照。usingSystem;usingSystem.Text;usingOpenCvSharp;internalstaticclassProgram{privatestaticvoidMain(){// 让控制台输出中文说明。Console.OutputEncodingEncoding.UTF8;RunDemo();}/// summary/// 运行 SpatialGradient 教学示例。/// /summaryprivatestaticvoidRunDemo(){usingvarsourceCreateDemoImage();usingvargraySourcenewMat();// SpatialGradient 只适合在灰度图上教学这样更容易观察导数。Cv2.CvtColor(source,graySource,ColorConversionCodes.BGR2GRAY);usingvarspatialDxnewMat();usingvarspatialDynewMat();usingvarsobelDxnewMat();usingvarsobelDynewMat();// SpatialGradient 一次性返回 x 和 y 两个方向的一阶导数。Cv2.SpatialGradient(graySource,spatialDx,spatialDy,3,BorderTypes.Default);// 为了验证它的语义再单独调用两次 Sobel 做对照。Cv2.Sobel(graySource,sobelDx,MatType.CV_16S,1,0,3,1.0,0.0,BorderTypes.Default);Cv2.Sobel(graySource,sobelDy,MatType.CV_16S,0,1,3,1.0,0.0,BorderTypes.Default);usingvarmagnitudenewMat();usingvardxFloatnewMat();usingvardyFloatnewMat();// 计算梯度幅值时需要浮点输入。spatialDx.ConvertTo(dxFloat,MatType.CV_32F);spatialDy.ConvertTo(dyFloat,MatType.CV_32F);Cv2.Magnitude(dxFloat,dyFloat,magnitude);Console.WriteLine(场景ASpatialGradient(InputArray src, OutputArray dx, OutputArray dy, int ksize 3, BorderTypes borderType BorderTypes.Default));Console.WriteLine(SpatialGradient 只支持 ksize3它本质上就是一次性返回两个方向的一阶导数。\n);Console.WriteLine($源图{DescribeMat(source)});Console.WriteLine($灰度图{DescribeMat(graySource)});Console.WriteLine($SpatialGradient dx{DescribeMat(spatialDx)});Console.WriteLine($SpatialGradient dy{DescribeMat(spatialDy)});Console.WriteLine($dx 与 Sobel 的差异{DescribeDifferenceStatistics(spatialDx,sobelDx)});Console.WriteLine($dy 与 Sobel 的差异{DescribeDifferenceStatistics(spatialDy,sobelDy)});Console.WriteLine($梯度幅值{DescribeMat(magnitude)});Console.WriteLine();SavePreview(spatialgradient-src.png,graySource);SavePreview(spatialgradient-dx.png,spatialDx);SavePreview(spatialgradient-dy.png,spatialDy);SavePreview(spatialgradient-magnitude.png,magnitude);Console.WriteLine(教学结论如果你需要同时拿到两个方向的导数SpatialGradient 比手动写两次 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,SpatialGradient,newPoint(34,370),HersheyFonts.HersheySimplex,0.8,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. 注意事项ksize在这个接口里必须是 3。borderType只允许默认模式。输出类型固定是 16 位有符号单通道。如果后面要计算梯度幅值记得先把dx和dy转成浮点。9. 调优建议先把它当作 Sobel 的简化接口来理解。如果要和Cv2.Canny配合先保留dx/dy。如果要讲梯度方向记得再接Cv2.Phase。如果要讲边缘强度记得再接Cv2.Magnitude。10. 运行说明如果你在控制台工程里运行本文示例直接把代码放到Program.cs即可。如果你在本仓库里学习请打开 WPF 控件Cv2.SpatialGradient点击“运行场景A”后查看右侧文本框和预览图。WPF 示例会同时展示 dx、dy 和梯度幅值便于理解它和 Sobel 的关系。11. 常见错误排查以为ksize可以随便改。把dx和dy当作两个不同尺寸的图像来处理。忘记它输出的是 16 位有符号结果。没有先转成灰度就开始对比导数结果。