【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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...