【4opencv】CLR基本原理和如何運用於GOCW

   GOCW的重點和難點就在於Csharp調用OpenCV,其中的橋樑就是CLR,固然咱們也有其餘方法,可是CLR是一個比較新的、比較可靠的、關鍵是能用的橋樑。這裏關於CLR的基本原理知識、如何用於GOCW項目的相關內容加以整理思考,以圖深刻:
1、什麼是CLR;

一、什麼是CLRc++

CLR(Common Language Runtime)是「公共語言運行時」的縮寫,簡單來講它是和Java虛擬機同樣的一個運行時環境。它負責資源管理(內存分配和垃圾收集),並保證應用和底層操做系統之間必要的分離。編程

通用語言運行時是.NET 框架應用程序的執行引摯。它提供了許多服務,其中包括:代碼管理(裝入和執行)、類型安全性驗證、元數據(高級類型信息)訪問、爲管理對象管理內存、管理代碼,COM對象和預生成的DLLs(非管理代碼和數據)的交互操做性、對開發人員服務的支持等等。c#

咱們GOCW項目中爲了可以使用Csharp調用OpenCV,採用了託管C++;數組

二、什麼是託管C++?安全

       託管是.NET的一個專門概念,它是融於通用語言運行時(CLR)中的一種新的編程理念,使用託管C++意味着,咱們的代碼能夠被CLR所管理,並能開發出具備最新特性如垃圾自動收集、程序間相互訪問等的.NET框架應用程序。框架

  由託管概念所引起的C++應用程序包括託管代碼、託管數據和託管類三個組成部分。  ide

  (1) 託管代碼:. Net環境提供了許多核心的運行(RUNTIME)服務,好比異常處理和安全策略。爲了能使用這些服務,必需要給運行環境提供一些信息代碼(元數據),這種代碼就是託管代碼。全部的C#、VB.NET、JScript.NET默認時都是託管的,但Visual C++默認時不是託管的,必須在編譯器中使用命令行選項(/CLR)才能產生託管代碼。函數

  (2) 託管數據:與託管代碼密切相關的是託管數據。託管數據是由公共語言運行的垃圾回收器進行分配和釋放的數據。默認狀況下,C#、Visual Basic 和 JScript.NET 數據是託管數據。不過,經過使用特殊的關鍵字,C# 數據能夠被標記爲非託管數據。Visual C++數據在默認狀況下是非託管數據,即便在使用 /CLR 開關時也不是託管的。編碼

  (3) 託管類: 儘管Visual C++數據在默認狀況下是非託管數據,可是在使用C++的託管擴展時,可使用"__gc"關鍵字將類標記爲託管類。就像該名稱所顯示的那樣,它表示類實例的內存由垃圾回收器管理。另外,一個託管類也徹底能夠成爲 .NET 框架的成員,由此能夠帶來的好處是,它能夠與其餘語言編寫的類正確地進行相互操做,如託管的C++類能夠從Visual Basic類繼承等。但同時也有一些限制,如託管類只能從一個基類繼承等。須要說明的是,在託管C++應用程序中既可以使用託管類也可使用非託管類。這裏的非託管類不是指標準C++類,而是使用託管C++語言中的__nogc關鍵字的類。spa

三、託管C++與標準C++的主要區別

    儘管託管C++是從標準C++創建而來的,但它與標準C++有着本質上的區別,這主要體如今如下幾個方面:
  (1) 普遍採用"名稱空間"(namespace)
  名稱空間是類型的一種邏輯命名方案,.NET使用該命名方案用於將類型按相關功能的邏輯類別進行分組,利用名稱空間可使開發人員更容易在代碼中瀏覽和引用類型。固然,咱們也可將名稱空間理解成是一個"類庫名"。

       (2) 基本數據類型的變化

  咱們知道,標準C++語言的數據類型是很是豐富的。而託管C++的數據類型更加豐富,不只包含了標準C++中的數據類型,並且新增了__int64 (64位整型)、Decimal(96位十進制數)、String*(字符串類型)和Object*(對象類型)等類型,表1-1列出它們各自數據類型。

      (3) 新增三個託管C++類型:__gc class、__value class和__gc interface

  一個__gc類或結構意味着該類或結構的生命週期是由.NET開發平臺自動管理及垃圾自動收集,用戶沒必要自已去調用delete來刪除。定義一個__gc類或結構和標準C++基本類似,所不一樣的是在class或struct前加上__gc。


2、CLR爲何能用於Csharp和C++互相調用

      基本的思路是將C++代碼封裝成爲託管代碼,而CSharp代碼原本就能夠翻譯成CLR語句。在這種狀況下,C++實現的效果,可以直接被CSharp調用,從而達到聯合的目的。
      其中的難點,其實並非引用,而是參數的傳遞:如何將「圖像」這種本質上較爲巨大的數據在兩種系統裏面傳遞,因此必然就須要有內存的操做;此外CLR語言的編碼方法和普通CSharp差距較大,也是須要注意。
      通常來講:
      有C#及C++背景的人使用C++/CLI的必備知識:
  一、C++/CLI裏的new等於C++裏的new, gcnew等於C#裏的new
  二、原生指針用*表示,託管引用使用^表示
  如: Stream^ stream = gcnew Stream();
  三、array<unsigned char>^ 等於 System.Byte[]
  四、pin_ptr關鍵字能把託管引用轉換爲原生指針:
  如: pin_ptr<BYTE> pBytes = & byteArray[0];
  而後pBytes就能夠看成原生的BYTE* 使用了. 
  等代碼執行完pBytes的有效範圍,byteArray就會恢復可被GC處理的狀態

3、經過CLR傳遞Mat和Bitmap:

      這裏應該算是核心代碼的解析,完整代碼能夠本身看,主要講接口
Csharp(BitMap)->Mat->Csharp(BitMap) ,幾乎所有的內容都在CLR形式的C++代碼中,其它地方只是實現接口。

     ////////將輸入cli::array<unsigned char>轉換爲cv::Mat//////////////////
    pin_ptr <System : :Byte > p1  =  &pCBuf1[ 0];
     unsigned  char * pby1  = p1;
    cv : :Mat img_data1(pCBuf1 - >Length, 1,CV_8U,pby1);
    cv : :Mat img_object  = cv : :imdecode(img_data1,IMREAD_UNCHANGED);
     if ( !img_object.data)
         return nullptr;
這裏注意,內存操做實際上是在imdecode中實現的,咱們相信OpenCV已經作了比較好封裝。

System : :Drawing : :Bitmap ^ MatToBitmap( const cv : :Mat & img)
{
     if (img.type()  != CV_8UC3)
    {
         throw gcnew NotSupportedException( "Only images of type CV_8UC3 are supported for conversion to Bitmap");
    }
     //create the bitmap and get the pointer to the data
    PixelFormat fmt(PixelFormat : :Format24bppRgb);
    Bitmap  ^bmpimg  = gcnew Bitmap(img.cols, img.rows, fmt);
    BitmapData  ^data  = bmpimg - >LockBits(System : :Drawing : :Rectangle( 00, img.cols, img.rows), ImageLockMode : :WriteOnly, fmt);
    Byte  *dstData  =  reinterpret_cast <Byte * >(data - >Scan0.ToPointer());
     unsigned  char  *srcData  = img.data;
     for ( int row  =  0; row  < data - >Height;  ++row)
    {
        memcpy( reinterpret_cast < void * >( &dstData[row *data - >Stride]),  reinterpret_cast < void * >( &srcData[row *img.step]), img.cols *img.channels());
    }
    bmpimg - >UnlockBits(data);
     return bmpimg;
}
bmp是有LocKBits操做的,就是在這裏將Bitmap處理的結果固定在內存中的。
Bitmap類使用LockBits和UnLockBits方法來將位圖的數據矩陣保存在內存中、直接對它進行操做,最後用修改後的數據代替位圖中的原始數據。 LockBits返回以各BitmapData的類用已描述數據在已鎖定的矩陣中的位置和分佈。
      BitmapData類包括如下幾個重要的屬性:
  • Scan0:數據矩陣在內存中的地址。
  • Stride:數據矩陣中的行寬,以byte爲單位。可能會擴展幾個Byte,後面會介紹。
  • PixelFormat:像素格式,這對矩陣中字節的定位很重要。
  • Width:位圖的寬度。
  • Height:位圖的高度。

  具體關係見下圖:


 
   如上圖所示,stride屬性表示位圖數據矩陣的行寬,以byte爲單位。出於效率考慮,矩陣的行寬並不是恰好是每行像素數的整數倍,系統每每會將其封裝成4的整數倍。舉例來講,對於一幅24位深17像素寬的圖像,其stride屬性爲52;每行的數據量爲17*3=51,系統將其自動封裝一個字節,因此它的stride爲52byte(或13*4byte)。對於一幅17像素寬的4位索引圖,其stride爲12,其中9byte(準確地說是8.5個byte)用來記錄數據信息,每行再自動添加3(3.5)個byte保證其爲4的整數倍。
   具體數據的分佈因其pixelformat而異。24位深的圖像每隔3個byte包含一組RGB信息;32位深的圖像每隔4個byte包含一組RGBA信息。那些每一個字節包含多個像素的pixelformat,好比4位索引圖像或1位索引圖像,必須通過仔細處理,從而保證同一字節中的相鄰byte不會混淆。
指針的準肯定位
  • 32位RGB:假設X、Y爲位圖中像素的座標,則其在內存中的地址爲scan0+Y*stride+X*4。此時指針指向藍色,其後分別是綠色、紅色,alpha份量。
  • 24位RGB:scan0+Y*stride+X*3。此時指針指向藍色,其後分別是綠色和紅色。
  • 8位索引:scan0+Y*stride+X。當前指針指向圖像的調色盤。
  • 4位索引:scan0+Y*stride+(X/2)。當前指針所指的字節包括兩個像素,經過高位和低位索引16色調色盤,其中高位表示左邊的像素,低位表示右邊的像素。
  • 1位索引:scan0+Y*stride+X/8。當前指針所指的字節中的每一位都表示一個像素的索引顏色,調色盤爲兩色,最左邊的像素爲8,最右邊的像素爲0 (TODO EMGUCV ISSUE)
     
4、CLR傳遞int和string

這樣的圖像處理程序,確定不是隻傳遞圖像就夠了的:在處理圖像的過程當中,可能遇到錯誤和異常,最好是以errorCode也就是一個int的樣式反饋回來;處理的結果多是一段比較長的數據,這個最好是string類型反饋回來。通過一些實驗,目前獲得如下的解決方案。

a、傳遞int:
因爲int是一個系統默認結構,也就是不管c++仍是csharp仍是clr中,都有這樣的基本的結構,因此基本上不須要轉化,一方面,咱們能夠直接將int做爲參數傳遞進入函數,也能夠做爲返回值進行反饋。可是通常來講,咱們確定但願是可以傳遞參數地址,這樣能夠獲得」修改參數「目的:
這樣操做,clr中這樣定義
//2.引用傳遞int
         int GOClrClass : :allTest( int a, int b,  int * c);
並實現
int  GOClrClass : :allTest( int a, int b,  int * c)
{
     *c =a +b; 
     return  *c;
}
那麼在Csharp調用過程當中,最重要的一步,就是須要給這個int c一個固定的內存地址,因此須要這樣操做
unsafe
        {
         int * value  = stackalloc  int[ 1];
        value[ 0=  0;
         int iret  = client.allTest( 23, value);
        }
須要注意, 因爲有個int*類型參數,在C#裏指針屬於不安全代碼,所以使用unsafe關鍵字將涉及到指針的代碼包括起來,在工程屬性裏設置容許使用不安全代碼。定義int指針須要使用stackalloc關鍵字,建立一個int數組,對數組賦值後,將指針傳遞給類函數。
其實,這裏就是直接強制地將int的地址傳遞過去,由於它簡單嘛。

b、傳遞string :

string類型複雜許多,由於它不只僅是char[]的集合,確定還包括許多其它的東西。複雜度關係,這裏我沒有實現」引用傳值「,可是可以獲得一種解決方法。
基本上來講,c#中的std::string 和clr中的system::String^之間存在直接轉換關係,估計在clr翻譯的過程當中就是直接翻譯的,因此能夠這樣來作:

clr處程序編寫

System : :String ^ GOClrClass : :allTestStr(System : :String ^ inputStr)
{
    System : :String ^ retstr  =   "fsdfsdf";
     return retstr;
}

c#處程序編寫

    string   s  =  client . allTestStr ( "abcdefg" );

可以直接將string傳遞到clr中(好比直接傳遞圖片地址),而且返回string結果。應該仍是有必定做用的。

感謝閱讀至此,但願有所幫助。




相關文章
相關標籤/搜索