【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
  • 前言 在我們開發過程中基本上不可或缺的用到一些敏感機密數據,比如SQL伺服器的連接串或者是OAuth2的Secret等,這些敏感數據在代碼中是不太安全的,我們不應該在源代碼中存儲密碼和其他的敏感數據,一種推薦的方式是通過Asp.Net Core的機密管理器。 機密管理器 在 ASP.NET Core ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 順序棧的介面程式 目錄順序棧的介面程式頭文件創建順序棧入棧出棧利用棧將10進位轉16進位數驗證 頭文件 #include <stdio.h> #include <stdbool.h> #include <stdlib.h> 創建順序棧 // 指的是順序棧中的元素的數據類型,用戶可以根據需要進行修改 ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • C總結與剖析:關鍵字篇 -- <<C語言深度解剖>> 目錄C總結與剖析:關鍵字篇 -- <<C語言深度解剖>>程式的本質:二進位文件變數1.變數:記憶體上的某個位置開闢的空間2.變數的初始化3.為什麼要有變數4.局部變數與全局變數5.變數的大小由類型決定6.任何一個變數,記憶體賦值都是從低地址開始往高地 ...
  • 如果讓你來做一個有狀態流式應用的故障恢復,你會如何來做呢? 單機和多機會遇到什麼不同的問題? Flink Checkpoint 是做什麼用的?原理是什麼? ...
  • C++ 多級繼承 多級繼承是一種面向對象編程(OOP)特性,允許一個類從多個基類繼承屬性和方法。它使代碼更易於組織和維護,並促進代碼重用。 多級繼承的語法 在 C++ 中,使用 : 符號來指定繼承關係。多級繼承的語法如下: class DerivedClass : public BaseClass1 ...
  • 前言 什麼是SpringCloud? Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的開發便利性簡化了分散式系統的開發,比如服務註冊、服務發現、網關、路由、鏈路追蹤等。Spring Cloud 並不是重覆造輪子,而是將市面上開發得比較好的模塊集成進去,進行封裝,從 ...
  • class_template 類模板和函數模板的定義和使用類似,我們已經進行了介紹。有時,有兩個或多個類,其功能是相同的,僅僅是數據類型不同。類模板用於實現類所需數據的類型參數化 template<class NameType, class AgeType> class Person { publi ...
  • 目錄system v IPC簡介共用記憶體需要用到的函數介面shmget函數--獲取對象IDshmat函數--獲得映射空間shmctl函數--釋放資源共用記憶體實現思路註意 system v IPC簡介 消息隊列、共用記憶體和信號量統稱為system v IPC(進程間通信機制),V是羅馬數字5,是UNI ...