How can I get and set pixel values of an EmguCV Mat image?

A better one.

  • Tick "Allow unsafe code" in the project's Debug and Release configurations.
  • Code:
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);
            }
        }
}

Usage:

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];
//...

Based on the Bartosz Rachwal's great answer, I've tried to write it for 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();
        }
    }

You can get elements from Mat by copying unmanaged memory blocks using DataPointer and converting managed to unmanaged types. Setting values is marshaling in the opposite direction.

For an example you can use such an extension class

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];
    }
}

Then getting and setting value is possible by single method call

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);

Tests with 200000000 operations shows that dynamic type version can be up to ~2.5x slower than static.

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);
}

This solution https://stackoverflow.com/a/32559496/15221325 for three color channels as requested by the user Quergo:

At first, nice answer, helped me a lot. But I do not understand how this method deals with multiple channels. In your example you wrote var mat = new Mat(3, 3, DepthType.Cv64F, 3); shouldn't it return a double[] of size 3 containing a value for each channel? – 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];
    }
}