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

Posted

技术标签:

【中文标题】如何获取和设置 EmguCV Mat 图像的像素值?【英文标题】:How can I get and set pixel values of an EmguCV Mat image? 【发布时间】:2015-11-22 04:39:12 【问题描述】:

我正在将 EmguCV 3.0.0 包装器用于 OpenCV 3.0 库。我在几个地方使用Mat 类。这是一个由 double 值组成的单通道 8x8 图像示例:

Mat image = new Mat(8, 8, DepthType.Cv64F, 1);

Image<> 类提供reasonable means for getting and setting pixel values,方法与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&lt;&gt;只有2D,所以必须使用掩码来设置每个通道上的像素。

我负担不起以这种方式设置像素的时间或内存。 如何通过单个方法调用设置像素?

【问题讨论】:

图像[0,0]=9;应该这样做 这也是我的预期。但它不起作用。这是错误:“无法使用 [] 将索引应用于 `Emgu.CV.Mat' 类型的表达式” 【参考方案1】:

您可以通过使用 DataPointer 复制非托管内存块并将托管类型转换为非托管类型来从 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 倍。 (请参阅here 和here。)您的扩展方法应该在该频谱的快速端,因为dynamic 接收类型很少会改变。 即便如此,安全返回/接收double 的优化Get/SetValue() 方法应该是可能的,因为从任何支持Emgu 的DepthTypedouble 都有一个safe automatic conversion。 谢谢!我没想到。如果 EmguCV 维护者不能更好地支持这一点,我很快就会包含一个像你这样的扩展方法。 Bartosz,很好的答案。只是一件事而不是(row * mat.Cols + col)* mat ElementSize,我会使用((row * mat.Step)+(col * mat.ElementSize)),因为行步骤可能不等于row * mat .列 起初,很好的答案,对我帮助很大。但我不明白这种方法如何处理多个渠道。在您的示例中,您编写了var mat = new Mat(3, 3, DepthType.Cv64F, 3);,它不应该返回一个大小为 3 的 double[],其中包含每个通道的值吗?【参考方案2】:

基于 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();
        
    

【讨论】:

感谢您的努力,但我认为这不属于这里。 OpenCvSharp 是一个与我所询问的完全不同的库。如果 OpenCvSharp 上没有您可以将其移至的现有问题,请考虑提出并回答您自己的问题。 两者都是 OpenCV 包装器,如您所见,差别不大。当我需要了解某事时,我不仅会寻找 OpenCvSharp 线程。 EMGU、Cpp 甚至 Phton 的例子都给了我很大的帮助。我认为一个新问题会浪费时间。这是同样的问题,并且几乎是相同的代码。任何像我一样搜索的人都可以使用它。你关于动态的讨论也很好读,任何会使用它的人也应该阅读它们。谢谢。 使用 OpenCvSharp,您可以使用 Mat::Set&lt;T&gt;(x,y,val) 方法将 (x,y) 处的元素设置为 val 的值。 Emgu 包装器中没有这样的访问器。【参考方案3】:

此解决方案 https://***.com/a/32559496/15221325 应用户 Quergo 的要求用于三个颜色通道:

起初,很好的答案,对我帮助很大。但我不明白这种方法如何处理多个渠道。在您的示例中,您编写了 var mat = new Mat(3, 3, DepthType.Cv64F, 3);它不应该返回一个包含每个通道值的大小为 3 的 double[] 吗? – Quergo 2019 年 11 月 18 日 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 个颜色通道对原始问题并不重要。 引用另一个答案:“起初,很好的答案,对我帮助很大。但我不明白这种方法如何处理多个通道。在你的例子中,你写了 var mat = new Mat(3 , 3, DepthType.Cv64F, 3); 它不应该返回一个包含每个通道值的大小为 3 的 double[] 吗? – Quergo 2019 年 11 月 18 日 22:30”所以肯定对 3 个颜色通道和最佳答案中有一个三色图像,在这种情况下这是一个错误。 您好 eng3ls,您的回答并不清楚您是在处理其他用户的评论。我建议更新您的答案来解释这一点。注意:对某事进行投票的用户不能在第二天更改他们的投票除非帖子被编辑 我编辑了我的解决方案,提到我正在处理另一个用户的评论。由于我没有足够的声誉来评论最佳解决方案,您可以在那里写评论吗,Quergo 的问题已经得到回答......它已经 2 岁了,所以我认为这并不重要,但你永远不会知道。然而,用户 peter bence 去年也问过同样的问题。【参考方案4】:

一个更好的。

在项目的调试和发布配置中勾选“允许不安全代码”。 代码:
public static class MatExtension

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

        public static ReadOnlySpan<T> Get<T>(this Mat mat, int row, Range cols)
        
            unsafe
            
                var span = new ReadOnlySpan<T>(mat.DataPointer.ToPointer(), mat.Rows * mat.Cols * mat.ElementSize);
                var colOffsets = cols.GetOffsetAndLength(span.Length);
                return span.Slice(row * mat.Cols + colOffsets.Offset, colOffsets.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);
double cxy0 = cxy[0];
//...

【讨论】:

以上是关于如何获取和设置 EmguCV Mat 图像的像素值?的主要内容,如果未能解决你的问题,请参考以下文章

如何将OpenCV Mat的所有像素设置为特定值?

如何访问二进制 Mat 图像中的像素值?

04 图像像素的读写操作

opencv如何计算图像中物体的像素值

opencv::Mat,从原始数据中获取像素值?

OpenCV与EmguCV中的漫水填充