2019年6月23日 星期日

C#呼叫C++進行影像處理

當使用C#進行影像處理的時候,如果遇上了需要大量運算的部分,C#可能不太夠力(C#的指標運算受到了限制而且在運算過程中做了很多邊界檢查),這時可以考慮呼叫C++的函式處理這些影像。

在開始之前可以先參考這篇文章:
https://corettainformation.blogspot.com/2019/06/cc.html



我們以C# Window Forms App作為範例。




我們拉一個Panel(裝PictureBox用的容器)進表單中,然後讓它填滿整個表單。

然後拉一個PictureBox(圖片),讓它填滿表單,樣式設定為Zoom(這樣就可以等比例縮放影像)

接著拉一個MenuStrip(選單)進表單中,設定兩組選項("載入影像"和"處理")


最後拉一個openFileDialog(開啟檔案用的對話方塊)進表單中。


這樣子需要的元件已經都佈置好了。


然後在表單的程式碼中新增一個Bitmap(點陣圖)


回到表單設計頁面,將"載入影像"點兩下,再次進入程式碼編輯器中。

鍵入以下程式碼


private void 載入影像ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            openFileDialog1.Filter = "圖片檔 (*.png;*.jpg;*.bmp;*.gif;*.tif)|*.png;*.jpg;*.bmp;*.gif;*.tif";
            if (openFileDialog1.ShowDialog() == DialogResult.OK)
            {
                bitmap = new Bitmap(openFileDialog1.FileName);//將選擇的影像載入至bitmap中
                pictureBox1.Image = bitmap;//將bitmap顯示在pictureBox中
            }
        }

解說:
openFileDialog1.Filter可限制檔案的類型,我們選png、jpg、bmp、gif、tif這五種。
使用openFileDialog1.ShowDialog()可開啟瀏覽檔案的對話方塊,當DialogResult.OK(開啟成功)時會將檔案路徑(openFileDialog1.FileName)傳給Bitmap產生新的點陣物件,我們將這個點陣影像指派給bitmap,最後設定PictureBox的影像為這個點陣圖。



接著測試程式是否能正常執行,執行程式後,按下載入影像/選擇要瀏覽的圖片/按下開啟舊檔。



如果能正常顯示影像,代表到目前為只是成功的,可以繼續做下去。


回到表單設計頁面,點兩下"處理",進入程式碼編輯器中。


接著鍵入以下程式碼。
註:必須在標頭引入 using System.Drawing.Imaging;

Bitmap MyNewBmp = bitmap;
Rectangle MyRec = new Rectangle(0, 0, MyNewBmp.Width, MyNewBmp.Height);
BitmapData MyBmpData = MyNewBmp.LockBits(MyRec, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
unsafe
{
          //empty
}
MyNewBmp.UnlockBits(MyBmpData);
pictureBox1.Image = MyNewBmp;

解說:
Rectangle可以是一種矩形範圍,此範例為(0,0)的座標到(總寬度,總高度)的矩形範圍(意即選取了整張影像)
BitmapData 是像素資料。
LockBits可將點陣影像鎖定在系統記憶體內,可提高處理效能(但是最後務必使用UnlockBits來解鎖)。
MyNewBmp.LockBits的參數中第一個放入MyRec(剛剛的矩形範圍),第二個是數字3(ImageLockMode是列舉,其中的ReadWrite是列舉值3,代表這個BitmapData可以讀取也可以寫入),第三個參數是PixelFormat的列舉值,Format32bppArgb代表32位元的影像格式(包含Alpha、R、G、B的色彩通道)。

unsafe是C#要使用非安全性代碼時必須使用的關鍵字,此部分先空著,等一下會在這個括號裡加東西。
UnlockBits是用來解鎖LockBits鎖住的記憶體,這是必要的東做,不做的話有可能會出現難以預期的錯誤。
最後將MyMyNewBmp顯示在pictureBox上

註:如果無法使用unsafe的話,請對專案按右鍵/屬性,點選建置/勾選允許不安全的程式碼。



接著我們開始製作C++函式來給C#調用。

對方案按下右鍵/加入/新增專案

嘗試尋找一個C++的CLR類別庫專案。
註:如果找不到的話請見下一張圖。

如果沒有找到的話就必須先安裝CLI的套件。


新增完成後,進入C++的程式碼編輯器中。


接著在類別中新增以下函式:

public:
void inline colorTo255(unsigned char* ptr, int width, int height, int channel)
{
unsigned char** fp = new unsigned char* [height];
int Stride = width * channel, x = 0, y = 0;
for (int j = 0; j < height; j++)
fp[j] = ptr + (Stride * j);
for (y = 0; y < height; y++)
{
for (x = 0; x < Stride; x += channel)
{
fp[y][x] = 255 - fp[y][x];
fp[y][x + 1] = 255 - fp[y][x + 1];
fp[y][x + 2] = 255 - fp[y][x + 2];
}
}
delete[] fp;
}

解說:
我們會在稍後引入點陣圖第0個像素的指標(ptr),其他參數包括width(影像寬度)、height(影像高度)、channel(通道數,有ARGB四種,所以此範例中的值應為四)
Stride是指掃描寬度(影像的每一列有多少位元組),x、y為等一下會用到的座標。
在 for (int j = 0; j < height; j++)fp[j] = ptr + (Stride * j); 中,我們將fp設定為影像中每一列開頭的指標位置的指標。
在外層迴圈,為逐列掃描,一個影像有幾列高度(height)就有多高。
在內層迴圈,為逐行掃描,每次增加4個位元組。
fp[y][x] = 255 - fp[y][x]; 將影像中的(x,y)座標的B值反轉。
fp[y][x + 1] = 255 - fp[y][x + 1];將影像中的(x,y)座標的G值反轉。
fp[y][x + 2] = 255 - fp[y][x + 2];將影像中的(x,y)座標的R值反轉。
註:在這裡,色彩空間的排列方式為BGRA(意即fp[y][x + 3]會是Alpha值)
結束後,使用 delete[] fp; 刪除不再需要用到的fp


 然後我們對這個C++專案按右鍵/建置。

接著對C#專案按右鍵/加入/參考。


選擇剛剛建立的C++專案,按下確定。

加入完參考後就可以讓C#呼叫剛剛建立的類別了,我們回到C#的程式碼編輯器中,在表頭加入剛剛的名稱空間,並新增C++物件。

using ClassLibrary2;
註:意思就是引入剛剛在C++程式碼中出現的「namespace ClassLibrary2」

接著回到處理影像的那部分(unsafe那裡),輸入以下程式碼:

private void 處理ToolStripMenuItem_Click(object sender, EventArgs e)
{
      Bitmap MyNewBmp = bitmap;
      Rectangle MyRec = new Rectangle(0, 0, MyNewBmp.Width, MyNewBmp.Height);
      BitmapData MyBmpData = MyNewBmp.LockBits(MyRec, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
       unsafe
       {
            C.colorTo255((byte*)MyBmpData.Scan0, MyNewBmp.Width, MyNewBmp.Height, 4);
        }
        MyNewBmp.UnlockBits(MyBmpData);
        pictureBox1.Image = MyNewBmp;
}

解說:
colorTo255是剛剛建立的C++函式,MyBmpData.Scan0是MyBmpData的第0個像素(我們將它轉換成byte*),MyNewBmp.Width是影像寬度,MyNewBmp.Height是影像高度 ,4是指通道數(包含ARGB四個通道)。


最後可以來測試程式是否正確了,我們按照剛剛的步驟執行程式,試著載入圖片。



然後按下選單中的處理。


如果看到影像的色彩被反轉了,恭喜你,成功了。

=====分隔線=====
如果覺得這篇文有幫助到你們的話,請留言或幫忙按個廣告吧。

您的支持是我寫文的最大動力。

如果依然失敗了,也請留言讓我知道,看看哪個環節出了問題,我有看到就會回。


希望這篇文有幫助到各位ξ( ✿>◡❛)

沒有留言:

張貼留言

有興趣或有疑問的歡迎提問與交流喔!!!