基於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 <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); //獲得數據到img_object中去
//////////////////////////////////處理過程///////////////////////////////////////
cvtColor(img_object,img_object, 40);
/////////////////////////////////////////////////////////////////////////////////
Bitmap ^ bb = MatToBitmap(img_object);
if ( !img_object.data)
return nullptr;
std : :vector <uchar > buf;
cv : :imencode( ".jpg", img_object, buf);
return bb;
}
{
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); //獲得數據到img_object中去
//////////////////////////////////處理過程///////////////////////////////////////
cvtColor(img_object,img_object, 40);
/////////////////////////////////////////////////////////////////////////////////
Bitmap ^ bb = MatToBitmap(img_object);
if ( !img_object.data)
return nullptr;
std : :vector <uchar > buf;
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*>(data->Scan0.ToPointer());
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;
}
{
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*>(data->Scan0.ToPointer());
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;
}
而在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);
// 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 <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);
//////////////////////////////////處理過程/////////
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 <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);
//////////////////////////////////處理過程///////////////////////
cvtColor(img_object,img_object, 6);
/////////////////////////////////////////////////////////////////////////////////
if ( !img_object.data)
return nullptr;
//獲得目錄,保存文件
cv : :imwrite( "c:/Method2.jpg",img_object);
return "c:/Method2.jpg";
}
{
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);
//////////////////////////////////處理過程/////////
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 <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);
//////////////////////////////////處理過程///////////////////////
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調用,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);
}
}
{
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;
}
{
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:
使用這種方法編寫的圖像處理預分析程序。

在使用這個框架的過程中,有網友反饋
“
clr怎么調試啊,在clr工程中相關的數據全是無效的,怎么看呢,不能保證寫的圖像算法完全正確啊……
”
那么clr肯定是可以調試,出現這個問題的原因是沒有掌握相關調試技巧。這里是相關解決方法:



2019年8月30日22:50:18 更新
主要是添加了OpenCVDNN模塊,將代碼升級到2017版本,並解決細節問題,現在應該說處理靜態圖片,那是相當好的了。

附件列表