OpenCVSharp和NumSharp的交互


友情提示,想直接看实现就跳到后面,但如果是跟我一样的初学者,可以把过程看完,有助于对 OpenCVSharpNumSharp 以及相关东西有更多的了解。

初学OpenCV,虽然从语法上Python更人性化,但考虑到学习成本,而且总是想用自己较为熟悉的语言,所以选择了 OpenCVSharpNumSharp。其实 SciSharp 的库上已经有了融合了 OpenCV 和 NumPy 的 SharpCV,但由于这个库对OpenCV API的实现相对不完全,所以暂不考虑。

因为市面的教材大部分都基于Python,少部分基于C++,没有办法,只能选择基于Python的教材。刚上手就碰见了Cv2.ImShow(string winName, Mat mat)无法直接使用NDArray作为参数生成图像,网上还找不到解决方案,还能怎样,自己不想学Python,那就看看怎么解决吧。

看到Mat有一个构造函数Mat(int rows, int cols, MatType type, Array data, long step = 0),在 NdArray.Implicit.Array.cs 中找到了NDArrayArray显性转换的实现,而NDArray有一个构造函数NDArray(Array values, Shape shape = default, char order = 'C'),Mat又有一个函数bool GetArray<T>(out T[] data)可以提取Array,有戏!

NDArray支持的类型在 NPTypeCodeMat支持的类型在 MatType,然后我鬼知道这些奇奇怪怪的类型是什么东西,更不知道他们之间是什么对应关系。

参考 SharpCV 中的 Mat,其中有一个函数

public unsafe NDArray GetData()
{
    // we pass donothing as it keeps reference to src preventing its disposal by GC
    switch (MatType)
    {
        case MatType.CV_32SC1:
        case MatType.CV_32SC2:
            {
                cv2_native_api.core_Mat_data(_handle, out int* dataPtr);
                dtype = TF_DataType.TF_INT32;
                return new NDArray(new IntPtr(dataPtr), shape, dtype);
            }
        case MatType.CV_32FC1:
            {
                cv2_native_api.core_Mat_data(_handle, out float* dataPtr);
                dtype = TF_DataType.TF_FLOAT;
                return new NDArray(new IntPtr(dataPtr), shape, dtype);
            }
        case MatType.CV_8UC1:
        case MatType.CV_8UC3:
            {
                cv2_native_api.core_Mat_data(_handle, out byte* dataPtr);
                dtype = TF_DataType.TF_UINT8;
                return new NDArray(new IntPtr(dataPtr), shape, dtype);
            }
        default:
            throw new NotImplementedException($"Can't find type: {_matType}");
    }
}

所以基本确定MatNDArray之间的转换支持5种类型,分别是CV_32SC1、CV_32SC2、CV_32FC1、CV_8UC1、CV_8UC3,然后查了一下,在 LIST OF MAT TYPE IN OPENCV 这里可以知道这些字母分别代表什么数据类型,综合看两者对应关系应该是
NPTypeCode.Int32(1通道) - MatType.CV_32SC1
NPTypeCode.Int32(2通道) - MatType.CV_32SC2
NPTypeCode.Float(1通道) - MatType.CV_32FC1
NPTypeCode.Byte(1通道) - MatType.CV_8UC1
NPTypeCode.Byte(3通道) - MatType.CV_8UC3

OK,写个扩展函数就搞定。然而,写完测试发现 bool GetArray<T>(out T[] data) 这个函数导出的数据只有通道数是1的类型才能被NDArray(Array values, Shape shape = default, char order = 'C')支持,Mat 里的acceptableTypesMap这个字典列出了对应类型。

至于为什么不直接用上面 SharpCVGetData()的代码,原因很简单,因为 OpenCVSharp 只引入了public static extern unsafe ExceptionStatus core_Mat_data(IntPtr self, out byte* returnValue)这个API (NativeMethods_core_Mat.cs),也就是只支持byte,不支持int32float,😠😠😠

那只能从NDArray其他的构造函数入手了,没错,就是这个 public NDArray(IArraySlice values, Shape shape = default, char order = 'C')。看着看着NDArray的源码,然后试出了可以使用的方法。接下来就直接上菜吧。

后来在学习 OpenCV 的过程中发现上面提到的几种转换是不够的,至少要把4通道的考虑进去,不然没法操作透明度。

Mat 转 NDArray (safe)

public static NDArray ToNDArray(this Mat mat)
{
    var matType = mat.Type();
    var channels = mat.Channels();
    var size = mat.Rows * mat.Cols * channels;
    var shape = channels == 1 ? new Shape(mat.Rows, mat.Cols) : new Shape(mat.Rows, mat.Cols, channels);
    if (matType == MatType.CV_32SC1 || matType == MatType.CV_32SC2)
    {
        var managedArray = new int[size];
        Marshal.Copy(mat.Data, managedArray, 0, size);
        var aslice = ArraySlice.FromArray(managedArray);
        return new NDArray(aslice, shape);
    }
    if (matType == MatType.CV_32FC1)
    {
        var managedArray = new float[size];
        Marshal.Copy(mat.Data, managedArray, 0, size);
        var aslice = ArraySlice.FromArray(managedArray);
        return new NDArray(aslice, shape);
    }
    if (matType == MatType.CV_64FC1)
    {
        var managedArray = new double[size];
        Marshal.Copy(mat.Data, managedArray, 0, size);
        var aslice = ArraySlice.FromArray(managedArray);
        return new NDArray(aslice, shape);
    }
    if (matType == MatType.CV_8UC1 || matType == MatType.CV_8UC3 || matType == MatType.CV_8UC4)
    {
        var managedArray = new byte[size];
        Marshal.Copy(mat.Data, managedArray, 0, size);
        var aslice = ArraySlice.FromArray(managedArray);
        return new NDArray(aslice, shape);
    }

    throw new Exception($"mat data type = {matType} is not supported");
}

NDArray 转 Mat (safe)

public static Mat ToMat(this NDArray nDArray) => 
    new Mat(nDArray.shape[0], nDArray.shape[1], nDArray.GetMatType(), (Array)nDArray);

public static MatType GetMatType(this NDArray nDArray)
{
    int channels = nDArray.ndim == 3 ? nDArray.shape[2] : 1;
    return nDArray.typecode switch
    {
            NPTypeCode.Int32 => channels == 1 ? MatType.CV_32SC1 : 
            channels == 2 ? MatType.CV_32SC2 :
            throw new ArgumentException($"nDArray data type = {nDArray.typecode} & channels = {channels} is not supported"),

            NPTypeCode.Float => channels == 1 ? MatType.CV_32FC1 :
            throw new ArgumentException($"nDArray data type = {nDArray.typecode} & channels = {channels} is not supported"),

            NPTypeCode.Double => channels == 1 ? MatType.CV_64FC1 :
            throw new ArgumentException($"nDArray data type = {nDArray.typecode} & channels = {channels} is not supported"),
            
            NPTypeCode.Byte => channels == 1 ? MatType.CV_8UC1 :
            channels == 3 ? MatType.CV_8UC3 :
            channels == 4 ? MatType.CV_8UC4 :
            throw new ArgumentException($"nDArray data type = {nDArray.typecode} & channels = {channels} is not supported"),

            _ => throw new ArgumentException($"nDArray data type = {nDArray.typecode} is not supported")
    };
}

其实中途还找到了应该是 SharpCVMat 旧版的实现,里面的GetData()是这样的

public unsafe NDArray GetData()
{
    // we pass donothing as it keeps reference to src preventing its disposal by GC
    switch (MatType)
    {
        case MatType.CV_32FC1:
            {
                cv2_native_api.core_Mat_data(_handle, out float* dataPtr);
                var block = new UnmanagedMemoryBlock<float>(dataPtr, shape.Size, () => DoNothing(_handle));
                var storage = new UnmanagedStorage(new ArraySlice<float>(block), shape);
                return new NDArray(storage);
            }
        case MatType.CV_8UC1:
        case MatType.CV_8UC3:
            {
                cv2_native_api.core_Mat_data(_handle, out byte* dataPtr);
                var block = new UnmanagedMemoryBlock<byte>(dataPtr, size, () => DoNothing(_handle));
                var storage = new UnmanagedStorage(new ArraySlice<byte>(block), shape);
                return new NDArray(storage);
            }
        default:
            throw new NotImplementedException($"Can't find type: {_matType}");
    }
}

[MethodImpl(MethodImplOptions.NoOptimization)]
private void DoNothing(IntPtr ptr)
{
    var p = ptr;
}

有人(应该是)根据上面的代码实现了转换 issues17

public static unsafe NDArray OpenCvSharpToNDArray(Mat input, int channels = 3)
{
    Shape shape = new Shape(new int[] { input.Height, input.Width, channels });
    var i = IntPtr.Zero;
    if (input.Type() == MatType.CV_32SC1 || input.Type() == MatType.CV_32SC2)
    {
        int* ptr = (int*)input.DataPointer;
        var block = new UnmanagedMemoryBlock<int>(ptr, shape.Size, () => DoNothing(i));
        var storage = new UnmanagedStorage(new ArraySlice<int>(block), shape);
        return new NDArray(storage);
    }
    else if (input.Type() == MatType.CV_32FC1)
    {
        float* ptr = (float*)input.DataPointer;
        var block = new UnmanagedMemoryBlock<float>(ptr, shape.Size, () => DoNothing(i));
        var storage = new UnmanagedStorage(new ArraySlice<float>(block), shape);
        return new NDArray(storage);
    }
    else if (input.Type() == MatType.CV_8UC1 || input.Type() == MatType.CV_8UC3)
    {
        byte* ptr = (byte*)input.DataPointer;
        var block = new UnmanagedMemoryBlock<byte>(ptr, shape.Size, () => DoNothing(i));
        var storage = new UnmanagedStorage(new ArraySlice<byte>(block), shape);
        return new NDArray(storage);
    }
    else
        throw new NotImplementedException($"Can't find type: {input.Type()}");

}
[MethodImpl(MethodImplOptions.NoOptimization)]
private static void DoNothing(IntPtr ptr)
{
    var p = ptr;
}

这里的 new Shape没有考虑 channels数,但改一改其实就能用了(没有亲自测试过)。另一点是触碰到指针,SharpCV 库修改了GetData()的实现可能也是基于安全的考虑,我是觉得无问题不大,只是想看看还有没有别的实现方法。反正问题已经解决又可以不碰指针,完事。

20211119

在实践中发现上面的 ToNDArray()方法效率太低,一张 (500, 500, 3),np.uint8 的画布转换一次(6700HQ)竟然需要40-50ms,相比之下 ToMat()仅需不到1ms。高分辨率画布的转换会更明显,而通过指针进行转化几乎不受分辨率的影响。虽然目前还不知道有什么应用场景需要高性能的转化,但推荐还是通过指针转换,这样可以适应更多的应用场景。

Mat 转 NDArray (unsafe, hi-perf)

public static unsafe NDArray ToNDArrayUnsafe(this Mat mat)
{
    var matType = mat.Type();
    var channels = mat.Channels();
    var size = mat.Rows * mat.Cols * channels;
    var shape = channels == 1 ? new Shape(mat.Rows, mat.Cols) : new Shape(mat.Rows, mat.Cols, channels);
    if (matType == MatType.CV_32SC1 || matType == MatType.CV_32SC2)
    {
        int* ptr = (int*)mat.DataPointer;
        var block = new UnmanagedMemoryBlock<int>(ptr, shape.Size, () => DoNothing(IntPtr.Zero));
        var storage = new UnmanagedStorage(new ArraySlice<int>(block), shape);
        return new NDArray(storage);
    }
    if (matType == MatType.CV_32FC1)
    {
        float* ptr = (float*)mat.DataPointer;
        var block = new UnmanagedMemoryBlock<float>(ptr, shape.Size, () => DoNothing(IntPtr.Zero));
        var storage = new UnmanagedStorage(new ArraySlice<float>(block), shape);
        return new NDArray(storage);
    }
    if (matType == MatType.CV_64FC1)
    {
        double* ptr = (double*)mat.DataPointer;
        var block = new UnmanagedMemoryBlock<double>(ptr, shape.Size, () => DoNothing(IntPtr.Zero));
        var storage = new UnmanagedStorage(new ArraySlice<double>(block), shape);
        return new NDArray(storage);
    }
    if (matType == MatType.CV_8UC1 || matType == MatType.CV_8UC3 || matType == MatType.CV_8UC4)
    {
        byte* ptr = (byte*)mat.DataPointer;
        var block = new UnmanagedMemoryBlock<byte>(ptr, shape.Size, () => DoNothing(IntPtr.Zero));
        var storage = new UnmanagedStorage(new ArraySlice<byte>(block), shape);
        return new NDArray(storage);
    }

    throw new Exception($"mat data type = {matType} is not supported");
}

[MethodImpl(MethodImplOptions.NoOptimization)]
private static void DoNothing(IntPtr ptr)
{
    var p = ptr;
}

NDArray 转 Mat (unsafe, hi-perf)

public unsafe static Mat ToMatUnsafe(this NDArray nDArray) =>
    new Mat(nDArray.shape[0], nDArray.shape[1], nDArray.GetMatType(), new IntPtr(nDArray.Unsafe.Address));

public static MatType GetMatType(this NDArray nDArray)
{
    int channels = nDArray.ndim == 3 ? nDArray.shape[2] : 1;
    return nDArray.typecode switch
    {
            NPTypeCode.Int32 => channels == 1 ? MatType.CV_32SC1 : 
            channels == 2 ? MatType.CV_32SC2 :
            throw new ArgumentException($"nDArray data type = {nDArray.typecode} & channels = {channels} is not supported"),

            NPTypeCode.Float => channels == 1 ? MatType.CV_32FC1 :
            throw new ArgumentException($"nDArray data type = {nDArray.typecode} & channels = {channels} is not supported"),

            NPTypeCode.Double => channels == 1 ? MatType.CV_64FC1 :
            throw new ArgumentException($"nDArray data type = {nDArray.typecode} & channels = {channels} is not supported"),
            
            NPTypeCode.Byte => channels == 1 ? MatType.CV_8UC1 :
            channels == 3 ? MatType.CV_8UC3 :
            channels == 4 ? MatType.CV_8UC4 :
            throw new ArgumentException($"nDArray data type = {nDArray.typecode} & channels = {channels} is not supported"),

            _ => throw new ArgumentException($"nDArray data type = {nDArray.typecode} is not supported")
    };
}


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM