一、DL現狀、本例范疇
本例顯然屬於object localization。
二、支撐環境和基本流程
首先是創建這個數據集。我采用“高拍儀拍攝3個松鼠食物”的方法來進行。共采集45張圖片,其中訓練的40張要有部分是比較難以識別的,檢測的5張相對質量較好。
三、GOCW的引入
希望能夠用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;
}
而在csharp中,直接
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);
就可以調用,並且獲得結果。
四、本例的實現、訓練和效果


GOCVhelper做算法研究和函數封裝;GOImage做dll;Csharp程序開發界面;
解決OpenCV版本問題,進行函數封裝。
現在環境配置已經精簡
此外將.dll拷貝到能夠被訪問的地方就可以。
下一步在保證效果不變的情況下,進行函數封裝。OK可行;
4.2、GOCW封裝
采用輸入圖片是Mat直接輸入;輸出結果還是ini外部存儲的方式,最為有效。
因為有良好的積累,所以很快就完成了基本算法移植
但是這還不夠,有兩個界面操作,1個是框選、一個是圓的產生和去除。其中框選需要結合QML一起來想,圓操作現在應該可行。
很快算法集成成功,主要還是得益於之前的有效積累。
這里還有一個升級版本
基於GOCW的界面,成功打通EasyDL通道
private void button4_Click(object sender, EventArgs e)
{
//保存json
List<Dictionary<String, float>> listDic = new List<Dictionary<String, float>>();
for (int i=0;i<listCenter.Count;i++)
{
PointF pointf = listCenter[i];
Dictionary<String, float> dic = new Dictionary<String, float>()
{
{ "name",99999},
{ "x1",(pointf.X-3)},
{ "y1",(pointf.Y-3)},
{ "x2",(pointf.X+3)},
{ "y2",(pointf.Y+3)}
};
listDic.Add(dic);
}
String Jsondata = JsonConvert.SerializeObject(listDic);
Jsondata = Jsondata.Replace("99999.0", "\"pip\"");
Jsondata = "{\"labels\": " + Jsondata + "}";
StreamWriter writer = new StreamWriter(strFliePath.TrimEnd(".jpg".ToArray())+"_.json", false);
writer.Write(Jsondata);
writer.Close();
//在原目錄保存縮放后的img
bmpCrop.Save(strFliePath.TrimEnd(".jpg".ToArray()) + "_.jpg");
}
其中這段:
StreamWriter
writer
=
new
StreamWriter
(
strFliePath
.
TrimEnd
(
".jpg"
.
ToArray
())+
"_.json"
,
false
);
編碼格式,卡了我一晚上。
最后通過比較工具才發現了編碼不同。
要不斷有計划地誰用過新工具
4.3在線訓練、觀察調參
方法應該是可行的,但是訓練的過程肯定是需要系統方法。在沒有足夠標注數據的情況下,必須首先研究自動標注方法。
五、小結
1、Csharp編寫界面非常重要,是核心能力。但是目前標注仍然是很困難,需要3-5min一幅圖;
2、什么樣的特征需要標注?需要標注到什么程度?這些都是值得研究的,需要長期思考的工程問題;
3、建立用戶參與的標注采集機制,是最終需要的,我們如何建立這個機制,關鍵是第一參與者的參與;
4、EasyDL是非常棒的BaseLine,它肯定不止采用了YOLO,它的效果是非常重要的參考;此外“智能標注”模式也值得參考。
附件列表