【OpenCV】OpenCV (C++) 與 OpenCvSharp (C#) 之間數據通信

来源:https://www.cnblogs.com/guojin-blogs/p/18098931
-Advertisement-
Play Games

在實際使用中,由於涉及到不同編程語言之間互相調用,導致C++ 中的OpenCV與C#中的OpenCvSharp 圖像數據在不同編程語言之間難以有效傳遞。在本文中我們將結合OpenCvSharp源碼實現原理,探究兩種數據之間的通信方式。 ...


  OpenCV是一個基於Apache2.0許可(開源)發行的跨平臺電腦視覺和機器學習軟體庫,可以運行在Linux、Windows、Android和Mac OS操作系統上。 它輕量級而且高效——由一系列 C 函數和少量 C++ 類構成,同時提供了Python、Ruby、MATLAB等語言的介面,實現了圖像處理和電腦視覺方面的很多通用演算法。OpenCV用C++語言編寫,它具有C ++,Python,Java和MATLAB介面,並支持Windows,Linux,Android和Mac OS,OpenCV主要傾向於實時視覺應用。
  OpenCvSharp 是一個OpenCV的.Net wrapper,應用最新的OpenCV庫開發,使用習慣比EmguCV更接近原始的OpenCV,有詳細的使用樣例供參考。該庫採用LGPL發行,對商業應用友好。使用OpenCvSharp,可用C#,VB.NET等語言實現多種流行的圖像處理(image processing)與電腦視覺(computer vision)演算法。
  但是在實際使用中,由於涉及到不同編程語言之間互相調用,導致C++ 中的OpenCV與C#中的OpenCvSharp 圖像數據在不同編程語言之間難以有效傳遞。在本文中我們將結合OpenCvSharp源碼實現原理,探究兩種數據之間的通信方式

1. 問題分析

  在日常開發中,由於一些庫不支持C#介面,因此在使用時我們需要藉助動態鏈接庫的方式,在C#中調用C++封裝的應用。由於C++與C#底層編譯方式不同,因此動態鏈接庫只可以傳遞基礎的數據類型,無法傳遞像Class這種高級的數據格式。

  在日常開發中,我們在C#中使用OpenCvSharp進行圖像處理,但是我們調用的演算法是通過C++封裝的動態鏈接庫,且需要將圖片數據傳遞到C++封裝的動態鏈接庫中進行處理,因此實現高效的實現圖片數據傳遞是十分有必要的。常見的方式有兩種:

  (1)第一種方式是在C#中將圖片數據轉為基本數據類型byte[]數組,然後將該數據傳遞到C++動態鏈接庫中,在接收到該數據後,由C++再將該數據重新轉為圖片數據進行處理。目前該方式經過測試,是可以實現的,但是這樣有一個弊端,圖片數據需要進行兩次的轉換,這樣會導致嚴重浪費時間和消耗大量記憶體。

  (2)第二種方式是在C#中將數據保存到本地,然後再C++動態鏈接庫中讀取。與上一種方式一樣,這樣會導致嚴重浪費時間和消耗大量記憶體。

2. 解決辦法

  為瞭解決這個問題,我們探究了一下OpenCvSharp 實現方式,通過其源碼可知,OpenCvSharp 在實現時,是通過對C++中的OpenCV進行了進一步封裝,將Mat數據定義成指針類型,然後以指針的方式在C++與C#中進行傳遞;而在C#中,重新定義了Mat數據類型,將C++傳遞來的Mat指針作為成員變數進行初始化,而後續基於Mat的所有操作,其低層都是通過傳遞這個指針進行操作的。

  知道了Mat的這個數據類型的實現原理後,我們可以模仿這種方式,以指針的方式實現將OpenCvSharp的數據傳遞到OpenCV C++中,這樣就可以快速實現數據類型傳遞。實現方式如下圖所示。

  在C#中使用OpenCvSharp獲取一個圖片數據,數據類型為Mat,我們可以先進行處理等操作;接下來我們可以獲取OpenCvSharp的地址CvPtr,然後在C++中使用*Mat指針進行獲取,然後通過*Mat我們便可以獲取到OpenCV C++中的Mat數據。接下來,用戶就可以根據自己的需求進行處理即可。在處理完成後,在將獲得新的用Mat數據轉為用*Mat指針,然後再C#中,使用IntPtr數據類型進行接收,然後使用OpenCvSharp的Mat以獲取的指針數據為初始值初始化Mat數據類型即獲得新的Mat數據。

  通過上述方式,我們便可以很輕鬆的實現C#中的OpenCvSharp與C++中的OpenCv數據轉換。

3. 項目創建

為方便演示,下述所有程式設計與編譯皆是在Windows11環境下,使用Visual Studio 2022編輯器實現。

  • OpenCV: 4.8.0
  • OpenCvSharp: 4.9.0

大家可以根據上述版本進行配置,也可以使用其他版本配置,但要保證OpenCV與OpenCvSharp都是同一個基礎版本的,且版本差別不要太大。

3.1 創建C++項目

  使用Visual Studio 2022創建一個空的C++項目,然後添加兩個文件,分別為:mat_conv.hmat_conv.cpp

  接下來配置項目屬性,首先配置項目輸出類型,如下圖所示,設置圖片輸出類型為動態庫(.dll)

  然後配置OpenCV C++項目依賴,主要是配置C++項目的包含目錄、庫目錄以及附加依賴項三個地方,如下圖所示:

  以下是我的項目設置信息,大家可以根據自己安裝的OpenCV情況進行設置:

包含目錄: C:\3rdpartylib\opencv\build\include
庫目錄: C:\3rdpartylib\opencv\build\x64\vc16\lib
附加依賴項: opencv_world480.lib

3.2 創建C#項目

使用Visual Studio 2022創建一個新的C#控制台項目,然後使用NuGet安裝所需的程式集即可,此處只需要安裝OpenCvSharp即可,如下圖所示:

4. 介面測試

此處主要測試四個介面:

  • 第一個介面測試在OpenCvSharp中讀取一張圖片,然後將圖片數據傳入到OpenCV中,測試傳入是否成功。
  • 第二個測試介面在OpenCV創建一個圖片,繪製一個矩形,然後將創建好的圖片傳出到OpenCvSharp,測試傳出數據是否成功。
  • 第三個測試介面是在OpenCvSharp中讀取一張圖片,然後將圖片數據傳入到OpenCV中,併進行一步處理,該處理結果會將數據保存到另一個新的圖片數據中,將該新的圖片數據傳出,然後在OpenCvSharp查看是否處理成功,測試該過程是否成功。
  • 第四個測試介面是在OpenCvSharp中讀取一張圖片,然後將圖片數據傳入到OpenCV中,併進行一步處理,該處理結果會直接在原有數據上進行修改,然後在OpenCvSharp查看是否處理成功,測試該過程是否成功。

4.1 介面一測試

  在以下文件中分別添加以下代碼:

mat_conv.h

#include "opencv2/opencv.hpp"
extern "C"  __declspec(dllexport) void __stdcall mat_conv1(cv::Mat * mat);

mat_conv.cpp

#include "mat_conv.h"

void mat_conv1(cv::Mat *mat)
{
	cv::imshow("image", *mat);
	cv::waitKey(0);
}

Program.cs

using OpenCvSharp;
using OpenCvSharp.Internal;
using System.Runtime.InteropServices;
namespace opencv_csharp
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");
            string image_path = "image.jpg";
            Mat mat1 = Cv2.ImRead(image_path);
            Methord.mat_conv1(mat1.CvPtr);
        }
    }

    class Methord 
    {
        private const string dll_path = "C:\\Users\\lenovo\\Desktop\\test_opencv\\x64\\Release\\opencv_cpp.dll";
        [DllImport(dll_path, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
        public static extern void mat_conv1(IntPtr mat);
    }
}

  其中,mat_conv.hmat_conv.cpp為C++項目文件,Program.cs文件為C#項目文件。

  在C++項目文件中,extern "C" __declspec(dllexport)表示使用C語言的編譯方式進行編譯,並導出到dll中。 mat_conv1(cv::Mat * mat)方法主要是接受傳入的Mat指針,並使用cv::imshow("image", *mat)將圖片數據展示出來。

  在C#項目中,使用[DllImport]屬性將動態鏈接庫中的mat_conv1讀取出來,同時因為在C#中指針都是被封裝為IntPtr類型的,因此使用IntPtr表示此處傳入的參數為指針類型。在使用該介面時,直接調用該方法,並且傳入指針參數,該指針參數可以通過Mat.CvPtr直接獲得。

  如下圖所示,程式在運行後,成功將傳入的圖片數據繪製出來,如下圖所示,說明該介面測試成功,也證明瞭該方法是可行的。

4.2 介面二測試

  在以下文件中分別添加以下代碼:

mat_conv.h

#include "opencv2/opencv.hpp"
extern "C"  __declspec(dllexport) void __stdcall mat_conv2(cv::Mat **returnValue);

mat_conv.cpp

#include "mat_conv.h"

void  mat_conv2(cv::Mat** returnValue)
{
    // 創建一個空白圖像
    cv::Mat image = cv::Mat::zeros(400, 400, CV_8UC3);
    // 矩形的左上角和右下角坐標
    cv::Point2f rect_start(50, 50);
    cv::Point2f rect_end(350, 350);
    // 矩形顏色 (B, G, R)
    cv::Scalar color(255, 0, 0); // 紅色
    // 矩形線條粗細
    int thickness = 2;
    // 繪製矩形
    cv::rectangle(image, rect_start, rect_end, color, thickness);
    *returnValue = new cv::Mat(image);
}

Program.cs

using OpenCvSharp;
using OpenCvSharp.Internal;
using System.Runtime.InteropServices;
namespace opencv_csharp
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");
            IntPtr ptr2 = IntPtr.Zero;
            Methord.mat_conv2(out ptr2);
            Mat mat2 = new Mat(ptr2);
            Cv2.ImShow("image2", mat2);
            Cv2.WaitKey(0);
        }
    }

    class Methord 
    {
        private const string dll_path = "C:\\Users\\lenovo\\Desktop\\test_opencv\\x64\\Release\\opencv_cpp.dll";
        [DllImport(dll_path, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
        public static extern void mat_conv2(out IntPtr returnValue);
    }
}

  其中,mat_conv.hmat_conv.cpp為C++項目文件,Program.cs文件為C#項目文件。

  在C++項目文件中,mat_conv2(cv::Mat** returnValue)主要是創建一個畫布,並繪製一個矩形,然後將創建好的圖片數據以指針的方式傳遞到C#中。

  在C#項目中,使用[DllImport]屬性將動態鏈接庫中的mat_conv2讀取出來,傳出數據此處使用的時雙重指針,因此使用out IntPtr進行接收。再獲取到該方法後,我們調用new Mat(IntPtr ptr)構造方法初始化為新的Mat數據。。

  如下圖所示,程式在運行後,成功將傳出的圖片數據繪製出來,如下圖所示,說明該介面測試成功,也證明瞭該方法是可行的。

4.3 介面三測試

  在以下文件中分別添加以下代碼:

mat_conv.h

#include "opencv2/opencv.hpp"
extern "C"  __declspec(dllexport) void __stdcall mat_conv3(cv::Mat * mat, cv::Mat **returnValue);

mat_conv.cpp

#include "mat_conv.h"

void  mat_conv3(cv::Mat * mat, cv::Mat **returnValue)
{
	cv::Mat m;
	cv::cvtColor(*mat, m, cv::COLOR_BGR2GRAY);
	*returnValue = new cv::Mat(m);
}

Program.cs

using OpenCvSharp;
using OpenCvSharp.Internal;
using System.Runtime.InteropServices;
namespace opencv_csharp
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");
            string image_path = "image.jpg";
            Mat mat3 = Cv2.ImRead(image_path);
            IntPtr ptr3 = IntPtr.Zero;
            Methord.mat_conv3(mat1.CvPtr, out ptr3);
            Mat mat3 = new Mat(ptr3);
            Cv2.ImShow("image1", mat3);
            Cv2.WaitKey(0);
        }
    }

    class Methord 
    {
        private const string dll_path = "C:\\Users\\lenovo\\Desktop\\test_opencv\\x64\\Release\\opencv_cpp.dll";
        [DllImport(dll_path, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
        public static extern void mat_conv3(IntPtr mat, out IntPtr return_value);
    }
}

  其中,mat_conv.hmat_conv.cpp為C++項目文件,Program.cs文件為C#項目文件。

  在C++項目文件中, mat_conv2(cv::Mat * mat, cv::Mat **returnValue)方法主要是接受傳入的Mat指針,並將傳入的圖片數據轉為灰度圖,同時將轉換好的圖片數據以指針的方式傳出到C#中。

  在C#項目中,使用[DllImport]屬性將動態鏈接庫中的mat_conv3讀取出來,其中傳入數據為指針數據,所以直接使用IntPtr即可;而對於傳出數據此處使用的時雙重指針,因此使用out IntPtr進行接收。再獲取到該方法後,我們調用new Mat(IntPtr ptr)構造方法初始化為新的Mat數據。

  如下圖所示,程式在運行後,成功將傳入的圖片數據進行灰度轉換,並將轉換後的圖片數據成功傳遞出來,說明該介面測試成功,也證明瞭該方法是可行的。同時我們測試了該過程所需時間,僅使用了3.69毫秒。

4.4 介面四測試

  在以下文件中分別添加以下代碼:

mat_conv.h

#include "opencv2/opencv.hpp"
extern "C"  __declspec(dllexport) void __stdcall mat_conv4(cv::Mat * mat);

mat_conv.cpp

#include "mat_conv.h"

void  mat_conv4(cv::Mat* mat)
{
    // 矩形的左上角和右下角坐標
    cv::Point2f rect_start(50, 50);
    cv::Point2f rect_end(350, 350);
    // 矩形顏色 (B, G, R)
    cv::Scalar color(255, 0, 0); // 紅色
    // 矩形線條粗細
    int thickness = 2;
    // 繪製矩形
    cv::rectangle(*mat, rect_start, rect_end, color, thickness);
}

Program.cs

using OpenCvSharp;
using OpenCvSharp.Internal;
using System.Runtime.InteropServices;
namespace opencv_csharp
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");
            string image_path = "image.jpg";
            Mat mat4 = Cv2.ImRead(image_path);
            Methord.mat_conv4(mat1.CvPtr);
            Cv2.ImShow("image2", mat4);
            Cv2.WaitKey(0);
        }
    }

    class Methord 
    {
        private const string dll_path = "C:\\Users\\lenovo\\Desktop\\test_opencv\\x64\\Release\\opencv_cpp.dll";
        [DllImport(dll_path, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
        public static extern void mat_conv4(IntPtr mat);
    }
}

  其中,mat_conv.hmat_conv.cpp為C++項目文件,Program.cs文件為C#項目文件。

  在C++項目文件中, mat_conv4(IntPtr mat)方法主要是接受傳入的Mat指針,併在傳入的圖片數據中繪製一個矩形,因為該操作是在原始數據上進行的操作,沒有殘生新的圖像數據,所以不需要傳出。

  在C#項目中,使用[DllImport]屬性將動態鏈接庫中的mat_conv4讀取出來,其中傳入數據為指針數據,所以直接使用IntPtr即可。然後該方法運行完後,我們直接查看該圖像數據信息,查看是否已經被修改。

  如下圖所示,程式在運行後,結果如下圖所示,說明該介面測試成功,也證明瞭該方法是可行的。

5. 總結

  在項目中,我們結合OpenCvSharp源碼,使用OpenCvSharp數據指針實現了在C#與C++之間傳遞圖像數據。與傳統的數據傳遞方式相比,該方式通過傳遞指針,通過指針的方式實現對同一塊圖像數據進行操作,避免了圖像數據的來迴轉換,極大的節省了程式運行時間以及記憶體消耗。

  最後如果各位開發者在使用中有任何問題,歡迎大家與我聯繫。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • MoneyPrinterTurbo —— 一個利用大模型,一鍵生成短視頻的開源項目。只需輸入視頻主題或關鍵詞,就可以全自動生成視頻文案、視頻素材、視頻字幕、視頻背景音樂,最後合成一個高清的短視頻。 ...
  • 本文介紹基於R語言中的Ternary包,繪製三元圖(Ternary Plot)的詳細方法;其中,我們就以RGB三色分佈圖為例來具體介紹~ ...
  • 本文介紹瞭如何快速搭建一個基於大型語言模型(LLM)的混元聊天應用。強調了開發速度的重要性,並指出了使用Streamlit這一工具的優勢,特別是對於不熟悉前端代碼的開發者來說,Streamlit提供了一種快速構建聊天應用的方法。 ...
  • 前言 aardio中有些經常使用的庫,換個項目總需要複製一下,還不便於修改。雖然可以直接把它放到aardio\lib目錄下,也是不便於共用給其他人使用。 最近偶然翻到編輯器里的工具->開發環境->擴展庫發佈工具,就想著可以像官方一樣,發佈自己的擴展庫,也便於分享給大家使用,最好能像官方擴展庫一樣線上 ...
  • 隨著汽車的普及和使用頻率的增加,車輛的維修保養成為了車主們經常需要面對的問題。為了提供更好的服務,挖數據平臺提供了一個維修保養記錄統計介面,讓用戶可以方便地查詢車輛的保養記錄和維修記錄。本文將對該介面進行詳細解析,並介紹其使用方法和應用場景。 首先,我們來看一下該介面的具體功能。該介面可以查詢車輛的 ...
  • 在使用Django等框架來操作MySQL時,實際上底層還是通過Python來操作的,首先需要安裝一個驅動程式,在Python3中,驅動程式有多種選擇,比如有pymysql以及mysqlclient等。使用pip命令安裝mysqlclient失敗應如何解決? 安裝的python版本說明 機器同時安裝了 ...
  • Csharper中的表達式樹 這節課來瞭解一下表示式樹是什麼? 在C#中,表達式樹是一種數據結構,它可以表示一些代碼塊,如Lambda表達式或查詢表達式。表達式樹使你能夠查看和操作數據,就像你可以查看和操作代碼一樣。它們通常用於創建動態查詢和解析表達式。 一、認識表達式樹 為什麼要這樣說?它和委托有 ...
  • 一、前言 這是一篇搭建許可權管理系統的系列文章。 隨著網路的發展,信息安全對應任何企業來說都越發的重要,而本系列文章將和大家一起一步一步搭建一個全新的許可權管理系統。 說明:由於搭建一個全新的項目過於繁瑣,所有作者將挑選核心代碼和核心思路進行分享。 二、技術選擇 三、開始設計 1、自主搭建vue前端和. ...
一周排行
    -Advertisement-
    Play Games
  • C#.Net的BCL提供了豐富的類型,最基礎的是值類型、引用類型,而他們的共同(隱私)祖先是 System.Object(萬物之源),所以任何類型都可以轉換為Object。 ...
  • 最近有群友咨詢C#如何調用Python?小編嘗試Python.NET過程中遭遇的版本相容性和環境配置難題,小編決定尋找一個更為簡單、穩定且對初學者友好的解決方案。小編搜索一番,除了Python.NET之外,還有其他途徑能夠幫助我們輕鬆地在C#項目調用Python腳本,那就是通過命令行調用,使用 Sy ...
  • .NET中特性+反射 實現數據校驗 在.NET中,我們可以使用特性+反射來實現數據校驗。特性是一種用於為程式中的代碼添加元數據的機制。元數據是與程式中的代碼相關聯的數據,但不直接成為代碼的一部分。通過特性,我們可以為類、方法、屬性等添加額外的信息,這些信息可以在運行時通過反射獲取和使用。 對反射不太 ...
  • Biwen.Settings 是一個簡易的配置項管理模塊,主要的作用就是可以校驗並持久化配置項,比如將自己的配置存儲到資料庫中,JSON文件中等 使用上也是很簡單,只需要在服務中註入配置, 比如我們有一個GithubSetting的配置項,我們只需要定義好對象然後註入到Service中即可: [De ...
  • EDP是一套集組織架構,許可權框架【功能許可權,操作許可權,數據訪問許可權,WebApi許可權】,自動化日誌,動態Interface,WebApi管理等基礎功能於一體的,基於.net的企業應用開發框架。通過友好的編碼方式實現數據行、列許可權的管控。 ...
  • 前言 VB.NET,全名Visual Basic .NET,是Microsoft .NET框架的一部分,是一種面向對象的編程語言。它繼承了Visual Basic的易用性,同時增加了對面向對象編程的支持。VB.NET提供了大量的內置函數,使得開發者可以更容易地處理字元串、數學計算、文件和目錄訪問等任 ...
  • 自定義可移動點二維坐標軸控制項 目錄 路由參數 坐標軸控制項定義 Demo 路由參數 X_YResultCollection為當前X軸對應Y軸值存儲字典 public class ResultCollectionChangedEventArgs(RoutedEvent routedEvent, obje ...
  • 自定義分頁控制項 tip: 該控制項的樣式用的是materialDesign庫,需要下載Nuget包 Code Xaml <UserControl x:Class="TestTool.CustomControls.PagingControl" xmlns="http://schemas.microsof ...
  • 最近群里有個小伙伴把Dapper遷移SqlSugar幾個不能解決的問題進行一個彙總,我正好寫一篇文章來講解一下 一、sql where in傳參問題: SELECT * FROM users where id IN @ids 答: SqlSugar中應該是 var sql="SELECT * FRO ...
  • 安裝nuget包 Wesky.Net.OpenTools 1.0.8或以上版本。支持.net framework 4.6以上版本,以及所有.net core以及以上版本引用。 開發一個簡單的Winform界面,用來測試使用。如需該winform的demo,可以在公眾號【Dotnet Dancer】後 ...