EMGU CV 3.3中Image<Gray,Byte>骨架像素点获取异常求助
问题分析与解决方案
看起来你遇到的核心问题是对EMGU CV中Image.Data的索引顺序理解有误,这直接导致了像素读取/修改失效以及骨架点无法正确获取的问题。
核心错误:EMGU Data数组的索引顺序搞反了
EMGU CV的Image<TColor, TDepth>.Data是一个三维数组,它的索引顺序是**[y, x, channel]**,而不是你代码里的[x, y, 0]。原因是OpenCV底层采用行优先存储:
- 第一个维度
y对应图像的行(高度方向,范围是0到src.Height-1) - 第二个维度
x对应图像的列(宽度方向,范围是0到src.Width-1) - 第三个维度是通道数(灰度图就是0)
你的代码里把x和y的位置写反了,所以:
- 修改
src.Data[0,0,0] = 255其实是修改了第0行第0列的像素,但你循环里用x遍历高度、y遍历宽度,完全错位,导致读取不到正确的骨架像素; - 当你把条件改成
==0时,遍历的是所有错位的像素,自然会获取到所有点。
修正后的代码
下面是调整了索引顺序后的正确实现:
public static List<Point> GetSkeletonPoints(Image<Gray, Byte> src, Byte color = 255) { var SkeletonPoints = new List<Point>(); int width = src.Width; // 列数,x的范围是0到width-1 int height = src.Height; // 行数,y的范围是0到height-1 byte[,,] data = src.Data; for(int y = 0; y < height; y++) // 先遍历行(y) { for(int x = 0; x < width; x++) // 再遍历列(x) { if(data[y, x, 0] == color) // 注意索引顺序是[y, x, 0] { SkeletonPoints.Add(new Point(x, y)); } } } return SkeletonPoints; }
另外,你之前修改像素的代码也需要调整为:
src.Data[0, 0, 0] = 255; // 修改第0行第0列的像素为白色 CvInvoke.Imshow("test", src); CvInvoke.WaitKey(0); // 记得加WaitKey,否则窗口可能一闪而过
性能优化建议
虽然用Data数组比GetPixel/SetPixel快,但还有更高效的方式:
1. 直接使用CvInvoke.FindNonZero
既然你已经知道用CvInvoke.FindNonZero能检测到非零点,那可以直接用这个方法来获取所有骨架点,省去手动遍历的麻烦,性能也更优:
public static List<Point> GetSkeletonPointsFast(Image<Gray, Byte> src) { using (var mat = src.Mat) { VectorOfPoint vector = new VectorOfPoint(); CvInvoke.FindNonZero(mat, vector); return vector.ToList(); } }
这个方法直接利用OpenCV的底层优化,速度远快于手动遍历数组。
2. 使用LockBits访问像素(适合复杂遍历场景)
如果需要自定义遍历逻辑,LockBits是比Data数组更高效的方式,因为它直接锁定内存缓冲区,避免了数组的索引开销:
public static List<Point> GetSkeletonPointsLockBits(Image<Gray, byte> src, byte color = 255) { var points = new List<Point>(); Bitmap bitmap = src.ToBitmap(); Rectangle rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height); System.Drawing.Imaging.BitmapData bmpData = bitmap.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly, bitmap.PixelFormat); IntPtr ptr = bmpData.Scan0; int bytes = Math.Abs(bmpData.Stride) * bitmap.Height; byte[] rgbValues = new byte[bytes]; System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes); for (int y = 0; y < bitmap.Height; y++) { for (int x = 0; x < bitmap.Width; x++) { int index = y * bmpData.Stride + x; if (rgbValues[index] == color) { points.Add(new Point(x, y)); } } } bitmap.UnlockBits(bmpData); return points; }
额外提示
- 调试时可以打印几个关键位置的像素值,比如
src.Data[y, x, 0],确认是否和预期一致; - 使用
Imshow后一定要加WaitKey,否则窗口无法正常显示内容。
内容的提问来源于stack exchange,提问作者Kajij
相关产品推荐
相关产品推荐

