為基於OpenCV的影象處理程式編寫介面—關於QTMFCCSharp的選擇以及GOCW的介紹
基於OpenCV編寫影象處理專案,除了演算法以外,比較重要一個問題就是介面設計問題。對於c++語系的程式設計師來說,一般來說有QT/MFC兩種考慮。QT的確功能強大,特別是QML編寫android介面很有一套(
https://www。
cnblogs。com/jsxyhelu/p/
8286476。html
),在樹莓派上進行設計也很方便(
https://www。
cnblogs。com/jsxyhelu/p/
7839062。html
);但是使用QT的一個現實問題就是和現有平臺的結合,比如客戶需要將結果匯出到excel中,使用QT就比較彆扭(當然不是說不可以)。所以現在我一般這樣來做:對於Android和PI,或者需要在Linux上執行的專案,使用QT編寫介面,呼叫Opencv函式;對於需要在windows上執行的專案,使用MFC編寫介面,直接就可以引用OpenCV。
有人會吐槽MFC使用起來非常麻煩,這點我非常同意。但MFC經過這麼多年的發展,今日仍有活力,並且短時間內不會消失。因為相比較其他一些所見即所得的語言和環境來說(QT/Csharp),mfc的訊息對映機制和座標體系等,的確有它的優勢,對於影象處理程式來說尤其如此;加以積累,能夠快速做出很多專業的東西;近期出現的ribbon介面也為mfc加分不少(
https://www。
cnblogs。com/jsxyhelu/p/
9209052。html
)
選擇了MFC這個方向,思考影象處理程式問題,一般來說分為“處理影象”和“處理影片”兩類:對於影象處理來說,我提供的GOPaint框架(
https://www。
cnblogs。com/jsxyhelu/p/
6440910。html
)能夠提供一個基本的靜態影象處理框架;而GOMFCTemplate2(
https://www。
cnblogs。com/jsxyhelu/p/
GOMFCTemplate2。html
)則適合用來處理影片。這兩種都分別成功運用於多種影片處理專案中。
但是這裡我想更進一步:希望能夠用Csharp編寫介面,因為它更好用;但是又不想引入EmguCV類似的庫,因為裡面很多東西不是我需要的。那麼最直接的方法就是使用Csharp呼叫基於Opencv編寫的類庫檔案(Dll)的,我取名叫做GreenOpenCsharpWarper(GOCW)
經過比較長時間的探索研究,目前的GOCW已經可以直接以函式的形式在記憶體中傳遞bitmap和Mat物件,達到了函式級別的應用。因為這裡涉及到託管程式碼編寫,也就是CLR程式編寫,所以有比較複雜的地方;為了展現GOCW的優良特性,我編寫實現GOGPY專案,也就是一個“Csharp編寫介面,OpenCV實現演算法的實時影片處理程式”,相關細節都包含其中。之所以叫“GPY”,是採集硬體這塊,我採用了成像質量較好的高拍儀裝置(GaoPaiYi)。
這裡簡單將最核心內容進行講解。GOCW的核心問題,無非就是基於CLR之上的兩個方向的資料流轉換。核心函式為
Bitmap^ GOClrClass::testMethod(cli::array<
unsigned
char
>^ pCBuf1)
{
pin_ptr
unsigned
char
* pby1 = p1;
cv::Mat img_data1(pCBuf1->Length,1,CV_8U,pby1);
cv::Mat img_object = cv::imdecode(img_data1,IMREAD_UNCHANGED);//獲得資料到img_object中去
//////////////////////////////////處理過程///////////////////////////////////////
cvtColor(img_object,img_object,40);
/////////////////////////////////////////////////////////////////////////////////
Bitmap^ bb = MatToBitmap(img_object);
if
(!img_object。data)
return
nullptr;
std::vector
cv::imencode(”。jpg“, img_object, buf);
return
bb;
}
以及
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(0, 0, img。cols, img。rows), ImageLockMode::WriteOnly, fmt);
//byte *dstData = reinterpret_cast
Byte *dstData =
reinterpret_cast
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;
}
而在chsarp中,直接
Bitmap b =
new
Bitmap(cam。Width, cam。Height, cam。Stride, PixelFormat。Format24bppRgb, m_ip);
// If the image is upsidedown
b。RotateFlip(RotateFlipType。RotateNoneFlipY);
srcImage = b;
if
(picPreview。Image != null)
picPreview。Image。Dispose();
//呼叫clr+opencv影象處理模組
MemoryStream ms =
new
MemoryStream();
b。Save(ms, System。Drawing。Imaging。ImageFormat。Jpeg);
byte[] bytes = ms。GetBuffer();
Bitmap bitmap = client。testMethod(bytes);
就可以呼叫,並且獲得結果。
以下內容為2017年更新的內容,適當參考:
一、CLR編寫的DLL部分
1、按照正常方法引入Opencv;
2、提供介面函式,進行影象處理(這裡只是實現了cvtColor,實際過程中可以用自己編寫的複雜函式)
String^ Class1::Method(cli::array<
unsigned
char
>^ pCBuf1)
{
pin_ptr
unsigned
char
* pby1 = p1;
cv::Mat img_data1(pCBuf1->Length,1,CV_8U,pby1);
cv::Mat img_object = cv::imdecode(img_data1,IMREAD_UNCHANGED);
//////////////////////////////////處理過程/////////
cvtColor(img_object,img_object,40);
/////////////////////////////////////////////////////////////////////////////////
if
(!img_object。data)
return
nullptr;
//獲得目錄,儲存檔案
cv::imwrite(”c:/Method。jpg“,img_object);
return
”c:/Method。jpg“;
}
String^ Class1::Method2(cli::array<
unsigned
char
>^ pCBuf1)
{
pin_ptr
unsigned
char
* pby1 = p1;
cv::Mat img_data1(pCBuf1->Length,1,CV_8U,pby1);
cv::Mat img_object = cv::imdecode(img_data1,IMREAD_UNCHANGED);
//////////////////////////////////處理過程///////////////////////
cvtColor(img_object,img_object,6);
/////////////////////////////////////////////////////////////////////////////////
if
(!img_object。data)
return
nullptr;
//獲得目錄,儲存檔案
cv::imwrite(”c:/Method2。jpg“,img_object);
return
”c:/Method2。jpg“;
}
二、Winform呼叫介面部分
(TIP:不僅可以用Winform呼叫,
http://
asp。net/webservice
都是可以呼叫的)
1、直接引用clr dll
2、編寫helper檔案(應該也可以叫做 warpper),透過外部IO的方法獲取clr dll的檔案
class
GOCsharpHelper
{
Class1 client =
new
Class1();
string strResult1 = null;
string strResult2 = null;
//輸入引數是string或bitmap
public
Bitmap ImageProcess(string ImagePath){
Image ImageTemp = Bitmap。FromFile(ImagePath);
return
ImageProcess(ImageTemp);
}
//輸出結果是bitmap
public
Bitmap ImageProcess(Image image)
{
MemoryStream ms =
new
MemoryStream();
image。Save(ms, System。Drawing。Imaging。ImageFormat。Jpeg);
byte[] bytes = ms。GetBuffer();
strResult1 = client。Method(bytes);
Image ImageResult = Bitmap。FromFile(strResult1);
return
(Bitmap)ImageResult;
}
public
Bitmap ImageProcess2(string ImagePath)
{
Image ImageTemp = Bitmap。FromFile(ImagePath);
return
ImageProcess2(ImageTemp);
}
//輸出結果是bitmap
public
Bitmap ImageProcess2(Image image)
{
MemoryStream ms =
new
MemoryStream();
image。Save(ms, System。Drawing。Imaging。ImageFormat。Jpeg);
byte[] bytes = ms。GetBuffer();
strResult2 = client。Method2(bytes);
Image ImageResult = Bitmap。FromFile(strResult2);
return
(Bitmap)ImageResult;
}
public
void
Clear()
{
if
(File。Exists(strResult1))
File。Delete(strResult1);
if
(File。Exists(strResult2))
File。Delete(strResult2);
}
}
3、使用例子(注意控制元件的dispose):
private
void
button2_Click(object sender, EventArgs e)
{
if
(pictureBox1。Image != null)
pictureBox1。Image。Dispose();
if
(pictureBox2。Image != null)
pictureBox2。Image。Dispose();
Image image1 = gocsharphelper。ImageProcess(” E:/sandbox/logo。jpg“);
pictureBox1。Image = image1;
Image image2 = gocsharphelper。ImageProcess2(”E:/sandbox/lena。jpg“);
pictureBox2。Image = image2;
}
三、解釋說明
使用外部I/O不僅僅是權宜之計,實際上Opencv的Decode使用的就是外部I/O。就目前研究的水平來說,這是最穩定的。
目前搭建成功的框架已經能夠完成“csharp呼叫opencv的”目標,並且在除錯、引數傳遞方面都很強。
如果是處理靜態圖片,已經夠用。
四、殺手程式
GOImageResearch:
使用這種方法編寫的影象處理預分析程式。