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