OpencvSharp 算子学习教案之 - Cv2.Moments 重载2
OpencvSharp 算子学习教案之 - Cv2.Moments 重载2大家好Opencv在很多工程项目中都会用到而OpencvSharp则是以C#开发与实现的Opencv操作库对.NET开发人员友好但很多API的中文资料、应用场景及常见坑点等缺乏系统性归纳因此这系列博客将给大家带来Cv2及Mat对象全系列算子学习教案供大家参考学习。Cv2.Moments教案版本V1.0面向对象OpenCvSharp 初学者所属模块imgproc源码位置OpenCvSharp/Cv2/Cv2_imgproc.cs:2569摘要本页演示Cv2.Moments(byte[,], bool)如何直接读取二维 byte 数据并说明它适合从原生数组、传感器矩阵或手工构造的灰度网格中计算矩。1. 函数名称带参数签名publicstaticMomentsMoments(byte[,]array,boolbinaryImagefalse)2. 函数用途Cv2.Moments(byte[,], bool)用来对二维byte数组直接计算矩。它最常见的用途有直接对手写数组、离线矩阵或网格数据做统计。对二值化后的前景数组计算面积和质心。对灰度权重数组计算加权中心。在不先构造Mat的情况下快速试验矩公式。这个重载最适合“数据已经在 C# 二维数组里”的场景。3. 函数公式当二维数组被当作图像时空间矩可以写成m p q ∑ x ∑ y I ( x , y ) x p y q m_{pq} \sum_x \sum_y I(x, y) x^p y^qmpqx∑y∑I(x,y)xpyq其中I(x, y)就是array[y, x]对应的值。binaryImagefalse时数组值本身就是权重。binaryImagetrue时非零值统一按1处理。质心仍然由下面的公式给出x ˉ m 10 m 00 , y ˉ m 01 m 00 \bar{x} \frac{m_{10}}{m_{00}},\qquad \bar{y} \frac{m_{01}}{m_{00}}xˉm00m10,yˉm00m014. 函数原理说明二维数组版本和Mat版本在语义上非常接近只是入口从“图像对象”变成了“原生数组”。对初学者来说可以这样理解你先准备一个二维byte矩阵。OpenCvSharp 再把它当作一张单通道灰度图去处理。binaryImage决定“按数值加权”还是“按是否为零计数”。byte[,]的好处是简单直观尤其适合以下情况你已经从别的算法拿到了一个二维字节网格。你正在做教学演示不想一开始就引入更多 OpenCV 图像对象。你希望快速验证矩公式对原始数据的影响。5. 参数含义解析参数名类型必填含义arraybyte[,]是输入二维字节数组binaryImagebool否是否把非零元素统一按 1 处理补充说明byte[,]默认取值范围是0~255。如果数组只想表达“有没有前景”可以直接把前景像素设为非零值。如果数组想表达“强度”就可以让不同区域保存不同灰度值。binaryImagetrue时数值大小不会继续影响结果。6. 应用场景列表场景名场景说明典型用途场景A手写灰度网格直接把二维byte矩阵送入矩函数教学演示场景B二值前景矩阵前景设为非零背景设为 0面积统计场景C算法中间结果从别的步骤拿到的字节网格预处理分析场景D快速原型验证先写数组再验证质心和面积算法调试7. 函数使用示例与 WPF 场景一一对应说明下面示例对应 WPF 场景 B。代码直接构造一个byte[,]再分别用binaryImagefalse和binaryImagetrue计算矩便于理解数组数据是怎样影响结果的。usingSystem;usingOpenCvSharp;internalstaticclassProgram{privatestaticvoidMain(){// 直接构造一个 byte[,]这样更接近“把原始矩阵交给 Moments”的教学目标。vardataCreateDemoByteArray();// binaryImagefalse按数组里的具体数值来加权。MomentsweightedCv2.Moments(data,false);// binaryImagetrue只要数组元素不是 0就统一按 1 来统计。MomentsbinaryCv2.Moments(data,true);PrintMoments(binaryImagefalse,weighted);PrintMoments(binaryImagetrue,binary);}privatestaticbyte[,]CreateDemoByteArray(){constintwidth360;constintheight260;// 先创建一个全 0 的背景矩阵后面只在前景区域里写入非零值。vardatanewbyte[height,width];for(vary0;yheight;y){for(varx0;xwidth;x){// 先默认背景为 0表示这个位置不参与统计。bytevalue0;// 用一个简单的多边形条件模拟“基础前景区域”。if(InsidePolygon(x,y)){value92;}// 右上角再叠加一个更亮的圆制造质心偏移。if(InsideCircle(x,y,258,92,46)){value212;}// 中下部放一个矩形表示区域里还存在另一块不同强度的前景。if(InsideRect(x,y,94,166,90,48)){value148;}// 让中央再有一个圆形亮区增加权重分布的层次感。if(InsideCircle(x,y,208,194,30)){value180;}data[y,x]value;}}returndata;}privatestaticboolInsideRect(intx,inty,intleft,inttop,intwidth,intheight){// 这里用最简单的矩形判断便于初学者把重点放在矩公式本身。returnxleftxleftwidthytopytopheight;}privatestaticboolInsideCircle(intx,inty,intcenterX,intcenterY,intradius){// 圆形条件只是在演示里制造一个亮斑不需要特别复杂的数学处理。vardxx-centerX;vardyy-centerY;returndx*dxdy*dyradius*radius;}privatestaticboolInsidePolygon(intx,inty){// 为了让示例尽量简洁这里直接用一个粗略的多边形边界判断。// 在真实项目里你也可以换成更精确的点在多边形内判断。returnx40x320y50y248;}privatestaticvoidPrintMoments(stringname,Momentsmoments){// 先判断 M00 是否为 0避免后面计算质心时出现除零。varcentroidXmoments.M000?double.NaN:moments.M10/moments.M00;varcentroidYmoments.M000?double.NaN:moments.M01/moments.M00;Console.WriteLine(${name});Console.WriteLine($M00 {moments.M00:F3});Console.WriteLine($Centroid ({centroidX:F2},{centroidY:F2}));Console.WriteLine($Mu20 {moments.Mu20:F3});Console.WriteLine($Mu11 {moments.Mu11:F3});Console.WriteLine($Mu02 {moments.Mu02:F3});Console.WriteLine();}}8. 常见错误与避坑把数组的行列顺序写反导致前景位置和预期不一致。以为byte[,]只能存二值图其实它也能存不同灰度值。忘记binaryImagetrue会忽略数组数值大小。数组里全是 0 时M00也会是 0不能直接求质心。9. 进阶扩展可以把byte[,]先转换成Mat再显示成热力图。可以把矩计算和阈值分割结合起来做目标中心定位。可以把数组矩和轮廓矩一起对比理解“栅格”和“边界”的差别。可以把多个byte[,]分别做矩统计做批量分析。10. 小结Cv2.Moments(byte[,], bool)适合已经在内存里准备好二维字节网格的场景。只要记住下面三点就够了它是对数组直接求矩不一定非要先建Mat。binaryImagetrue会把非零值统一看成1。M00和质心仍然是最先要关注的两个结果。11. 相关链接WPF 教学控件Cv2MomentsControl.xaml.cs样例实现MomentsByteArraySample.cs官方文档源码位置OpenCvSharp/Cv2/Cv2_imgproc.cs:2569