如何获取和设置EmguCV Mat图像的像素值?

17
我将使用EmguCV 3.0.0包装器与OpenCV 3.0库进行编程。在几个地方我将使用Mat类。以下是一个由 double 值制成的单通道8x8图像的示例:
Mat image = new Mat(8, 8, DepthType.Cv64F, 1);

Image<>类提供了合理的方法来获取和设置像素值,并且这种方法对于Matrix<>类是相同的,但对于Mat类似乎不太明显。我唯一想出来的设置单个像素的方法是使用掩码:

// set two pixel values, (0,0) to 9.0, (2, 3) to 42.0

Matrix<byte> mask = new Matrix<byte>(8,8);
mask.Data[0, 0] = 1;
image.SetTo(new MCvScalar(9.0), mask);

mask = new Matrix<byte>(8,8);
mask.Data[2, 3] = 1;
image.SetTo(new MCvScalar(42.0), mask);

这段文字涉及编程相关内容,作者感到有些困惑。当Mat超过一个通道时,情况变得更加复杂,因为Matrix<>只有2D,所以必须使用掩模来在每个通道上设置像素点。作者无法承担像这样设置像素点所需的时间和内存消耗。请问有什么方法可以通过单一的方法调用来设置像素点吗?

image[0,0]=9; 应该可以实现。 - Miki
3
这也是我预期的结果。但它不起作用。这是错误:"无法将[]索引应用于类型为`Emgu.CV.Mat'的表达式"。 - kdbanman
5个回答

20

通过使用DataPointer从Mat中复制非托管内存块并转换托管到非托管类型,可以获取Mat中的元素。 设置值是在相反方向进行的封送处理。

例如,您可以使用这样的扩展类

public static class MatExtension
{
    public static dynamic GetValue(this Mat mat, int row, int col)
    {
        var value = CreateElement(mat.Depth);
        Marshal.Copy(mat.DataPointer + (row * mat.Cols + col) * mat.ElementSize, value, 0, 1);
        return value[0];
    }

    public static void SetValue(this Mat mat, int row, int col, dynamic value)
    {
        var target = CreateElement(mat.Depth, value);
        Marshal.Copy(target, 0, mat.DataPointer + (row * mat.Cols + col) * mat.ElementSize, 1);
    }
    private static dynamic CreateElement(DepthType depthType, dynamic value)
    {
        var element = CreateElement(depthType);
        element[0] = value;
        return element;
    }

    private static dynamic CreateElement(DepthType depthType)
    {
        if (depthType == DepthType.Cv8S)
        {
            return new sbyte[1];
        }
        if (depthType == DepthType.Cv8U)
        {
            return new byte[1];
        }
        if (depthType == DepthType.Cv16S)
        {
            return new short[1];
        }
        if (depthType == DepthType.Cv16U)
        {
            return new ushort[1];
        }
        if (depthType == DepthType.Cv32S)
        {
            return new int[1];
        }
        if (depthType == DepthType.Cv32F)
        {
            return new float[1];
        }
        if (depthType == DepthType.Cv64F)
        {
            return new double[1];
        }
        return new float[1];
    }
}

然后,通过单个方法调用可以实现获取和设置值。

var row = 2;
var col = 1;
var mat = new Mat(3, 3, DepthType.Cv64F, 3);
mat.SetValue(row, col, 3.14);
var value = mat.GetValue(row, col);

经过200000000次操作的测试表明,动态类型版本的速度可能比静态类型版本慢了约2.5倍。

public static double GetDoubleValue(this Mat mat, int row, int col)
{
    var value = new double[1];
    Marshal.Copy(mat.DataPointer + (row * mat.Cols + col) * mat.ElementSize, value, 0, 1);
    return value[0];
}

public static void SetDoubleValue(this Mat mat, int row, int col, double value)
{
    var target = new[] { value };
    Marshal.Copy(target, 0, mat.DataPointer + (row * mat.Cols + col) * mat.ElementSize, 1);
}

有趣的使用dynamic,看起来非常好用! 我唯一的问题是,与静态类型的替代方案相比,dynamic可能会慢10倍到100倍不等。 (请参见此处此处。)您的扩展方法应该在该光谱的快速端,因为dynamic接收类型很少更改。 - kdbanman
即便如此,优化的Get/SetValue()方法可以安全地返回/接收double,因为从任何Emgu支持的DepthTypedouble都存在安全自动转换 - kdbanman
谢谢!我没想到会这样。如果EmguCV的维护者没有更好地支持它,我很快就会像你一样包含一个扩展方法。 - kdbanman
Bartosz,非常好的回答。只有一件事情,我会使用((row * mat.Step) + (col * mat.ElementSize))而不是(row * mat.Cols + col) * mat ElementSize,因为行步长可能不等于row * mat.Columns。 - AeroClassics
1
起初,非常好的答案,帮了我很多忙。但是我不明白这种方法如何处理多个通道。在你的例子中,你写了 var mat = new Mat(3, 3, DepthType.Cv64F, 3); 这不应该返回一个大小为3的double[],其中包含每个通道的值吗? - Quergo
显示剩余3条评论

5

基于Bartosz Rachwal的出色答案,我尝试用OpenCvSharp编写它:

    public static dynamic GetValue(this Mat mat, int row, int col)
    {
        var value = CreateElement(mat.Type());
        Marshal.Copy(mat.Data + (row * mat.Cols + col) * mat.ElemSize(), value, 0, 1);
        return value[0];
    }
    public static void SetValue(this Mat mat, int row, int col, dynamic value)
    {
        var target = CreateElement(mat.Type(), value);
        Marshal.Copy(target, 0, mat.Data + (row * mat.Cols + col) * mat.ElemSize(), 1);
    }
    private static dynamic CreateElement(MatType depthType, dynamic value)
    {
        var element = CreateElement(depthType);
        element[0] = value;
        return element;
    }
    private static dynamic CreateElement(MatType depthType)
    {
        switch (depthType)
        {
            case MatType.CV_8S:
                return new sbyte[1];
            case MatType.CV_8U:
                return new byte[1];
            case MatType.CV_16S:
                return new short[1];
            case MatType.CV_16U:
                return new ushort[1];
            case MatType.CV_32S:
                return new int[1];
            case MatType.CV_32F:
                return new float[1];
            case MatType.CV_64F:
                return new double[1];
            default:
                throw new NotImplementedException();
        }
    }

1
感谢您的努力,但我认为这不属于这里。OpenCvSharp是一个完全不同的库,与我所询问的那个库不同。如果没有现有的关于OpenCvSharp的问题可以将其移动到,考虑提出并回答自己的问题。 - kdbanman
4
两者都是OpenCV包装器,如你所见并没有很大的区别。当我需要了解某个东西时,我不仅会查看OpenCvSharp的帖子,EMGU、Cpp甚至Python的示例也会给我带来很大的帮助。我认为提出一个新问题会浪费时间。这是同样的问题,几乎相同的代码。像我一样搜索的任何人都可以利用它。此外,关于动态的讨论也值得一读,任何使用此代码的人也应该阅读它们。谢谢。 - Koray
1
使用OpenCvSharp,您可以使用Mat::Set<T>(x,y,val)方法将(x,y)处的元素设置为val的值。在Emgu封装中没有这样的访问器。 - antoine

4

更好的选择。

  • 在项目的调试和发布配置中勾选“允许不安全代码”。
  • 代码:
public static class MatExtension
{
    public static void Set<T>(this Mat mat, int row, int col, T value) where T : struct 
       => unsafe {  _ = new Span<T>(mat.DataPointer.ToPointer(), mat.Rows * mat.Cols * mat.ElementSize)
            [row * mat.Cols + col] = value; }

    public static T Get<T>(this Mat mat, int row, int col) where T : struct 
       => unsafe { return new ReadOnlySpan<T>(mat.DataPointer.ToPointer(), mat.Rows * mat.Cols * mat.ElementSize)
            [row * mat.Cols + col]; }

    public static ReadOnlySpan<T> Get<T>(this Mat mat, int row, System.Range cols) where T : struct
    {
        unsafe
        {
           var span = new ReadOnlySpan<T>(mat.DataPointer.ToPointer(), mat.Rows * mat.Cols * mat.ElementSize);
           var (offset, length) = cols.GetOffsetAndLength(span.Length);
           return span.Slice(row * mat.Cols + offset, length);
        }
    }
}

使用方法:

using var stats = new Mat();
using var centroids = new Mat();

var x = stats.Get<int>(i,(int)ConnectedComponentsTypes.Left);
var cxy = centroids.Get<double>(i, 0..1);
var cxy0 = cxy[0];

.Set<struct> 变体怎么样? - n0099
有个人搞乱了我的答案(编辑并且被接受了)。所以大家要小心这段代码,它不再是我的“原创”了。 - Softlion

2
这个解决方案https://dev59.com/ZVwY5IYBdhLWcg3wwqA5#32559496是针对用户Quergo所要求的三个颜色通道的。

首先,很好的答案,帮了我很多忙。但我不明白这种方法如何处理多个通道。在你的示例中,你写了var mat = new Mat(3, 3, DepthType.Cv64F, 3);难道它不应该返回一个大小为3的double[],其中包含每个通道的值吗?- Quergo Nov 18 '19 at 22:30

public static class MatExtension
{
    public static dynamic GetValues(this Mat mat, int row, int col)
    {
        var value = CreateElement3Channels(mat.Depth);
        Marshal.Copy(mat.DataPointer + (row * mat.Cols + col) * mat.ElementSize, value, 0, 3);
        return value;
    }

    public static dynamic GetValue(this Mat mat, int channel, int row, int col)
    {
        var value = CreateElement3Channels(mat.Depth);
        Marshal.Copy(mat.DataPointer + (row * mat.Cols + col) * mat.ElementSize, value, 0, 3);
        return value[channel];
    }

    public static dynamic GetValue(this Mat mat, int row, int col)
    {
        var value = CreateElement(mat.Depth);
        Marshal.Copy(mat.DataPointer + (row * mat.Cols + col) * mat.ElementSize, value, 0, 1);
        return value[0];
    }

    public static void SetValues(this Mat mat, int row, int col, dynamic value)
    {
        Marshal.Copy(value, 0, mat.DataPointer + (row * mat.Cols + col) * mat.ElementSize, 3);
    }

    public static void SetValue(this Mat mat, int channel, int row, int col, dynamic value)
    {
        var element = GetValues(mat, row, col);
        var target = CreateElement(element, value, channel);
        Marshal.Copy(target, 0, mat.DataPointer + (row * mat.Cols + col) * mat.ElementSize, 3);
    }

    public static void SetValue(this Mat mat, int row, int col, dynamic value)
    {
        var target = CreateElement(mat.Depth, value);
        Marshal.Copy(target, 0, mat.DataPointer + (row * mat.Cols + col) * mat.ElementSize, 1);
    }

    private static dynamic CreateElement(dynamic element, dynamic value, int channel)
    {
        element[channel] = value;
        return element;
    }

    private static dynamic CreateElement(DepthType depthType, dynamic value)
    {
        var element = CreateElement(depthType);
        element[0] = value;
        return element;
    }

    private static dynamic CreateElement3Channels(DepthType depthType)
    {
        if (depthType == DepthType.Cv8S)
        {
            return new sbyte[3];
        }

        if (depthType == DepthType.Cv8U)
        {
            return new byte[3];
        }

        if (depthType == DepthType.Cv16S)
        {
            return new short[3];
        }

        if (depthType == DepthType.Cv16U)
        {
            return new ushort[3];
        }

        if (depthType == DepthType.Cv32S)
        {
            return new int[3];
        }

        if (depthType == DepthType.Cv32F)
        {
            return new float[3];
        }

        if (depthType == DepthType.Cv64F)
        {
            return new double[3];
        }

        return new float[3];
    }

    private static dynamic CreateElement(DepthType depthType)
    {
        if (depthType == DepthType.Cv8S)
        {
            return new sbyte[1];
        }

        if (depthType == DepthType.Cv8U)
        {
            return new byte[1];
        }

        if (depthType == DepthType.Cv16S)
        {
            return new short[1];
        }

        if (depthType == DepthType.Cv16U)
        {
            return new ushort[1];
        }

        if (depthType == DepthType.Cv32S)
        {
            return new int[1];
        }

        if (depthType == DepthType.Cv32F)
        {
            return new float[1];
        }

        if (depthType == DepthType.Cv64F)
        {
            return new double[1];
        }

        return new float[1];
    }
}

你刚刚为同一个问题链接了另一个答案吗?我们感谢您提供更全面的解决方案,但是在原始问题中,拥有3个颜色通道并不重要。 - Connor Low
引用另一个答案: “首先,很好的答案,帮了我很多忙。但是我不明白这种方法如何处理多个通道。在你的例子中,你写了var mat = new Mat(3, 3, DepthType.Cv64F, 3);难道它不应该返回一个大小为3的double[],其中包含每个通道的值吗?- Quergo Nov 18 '19 at 22:30”因此,对于3个颜色通道确实存在兴趣,并且顶部答案中有一张三色图像,在这种情况下将是一个错误。 - eng3ls
嗨eng3ls,从你的回答中并不清楚你是在回应另一个用户的评论。我建议更新你的回答来解释这一点。注意:投票的用户除非帖子被编辑,否则第二天无法更改他们的投票。 - Connor Low
我编辑了我的解决方案,并提到我正在回复另一个用户的评论。由于我没有足够的声望在顶部的解决方案上发表评论,你能否在那里写一条评论,说明Quergo的问题已经得到解答...它已经两年了,所以我不认为这真的很重要,但你永远不知道。然而,用户peter bence去年也问了同样的问题。 - eng3ls

2
您可以通过两种方式轻松获取和设置Mat图像的像素值:
1- 将Mat图像转换为Image 格式 2- 直接从Mat类中使用SetValue和GetValue 1- 将Mat图像转换为Image<>格式:
int row = 0;
int col = 1;
int channel = 6;
Mat Image = CvInvoke.Imread("path");
Image<Gray, Byte> ImageFormat = Image.ToImage<Gray, Byte>();
int pixVal = ImageFormat.Data[row, col, channel];

// for set value 

int numValue = 165;
ImageFormat.Data[row, col, channel] = numValue;

您可以使用以下方式访问图像的Mat格式:

<>

ImageFormat.Mat;

2- 你可以使用SetValue和GetValue方法直接设置或获取像素值(这些方法返回一个对象,你需要将对象转换为数字):
object pixVal = Image.Data.GetValue(row, col, channel);
float pixValue = Convert.ToInt32(pixVal);

// for set value:

float setPixVal = 159;
Image.Data.SetValue(setPixVal, row, col, channel);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接