C#如果要導入TensorFlow套件
常見的有兩套TensorFlowSharp與Emgu.TF
本篇以Emgu.TF為主來做介紹
由於過去有使用過Emgu.CV套件
因此對於同團隊推出的Emgu.TF想必會比較熟悉
但由於Emgu.TF推出的時間還很早
目前每個小版本的更新都有不少的差異
官方提供的歷史版本可在此下載:
https://sourceforge.net/projects/emgutf/files/
GitHub與Nuget上的版本最新到1.13.1
https://github.com/emgucv/emgutf/releases/tag/1.13.1
雖然Emgu.TF在Nuget上可以參考
但如果想要多了解實作方式,還是建議下載Source Code來看
裡面有提供Example專案
這裡以Emgu.TF 1.8.0的版本介紹,因為裡面有兩組Example的專案
裡面提供了兩個範例專案
InceptionObjectRecognition、MultiboxPeopleDetection
Emgu.TF這個套件的好處是,幾乎檔案抓下來
就能直接編譯建置成功
這兩個執行專案若是第一次執行
會把所需的model進行下載
Model下載中
而這兩個範例的差別在於
InceptionObjectRecognition的範例是使用tensorflow_inception_graph.pb這個model
一次只辨識一張影像中的單一一個物件
如果你影像中只有一個物件,就適合這個model
文字顯示辨識出來的物件是什麼,機率是多高,花了多久的時間辨識~
MultiboxPeopleDetection的範例是使用multibox_model.pb這個model
可以偵測出影像中人的部分,並請把人的部分框選起來
但是似乎無法辨識其他物件?
只能框選出物件,但無法提供該物件是什麼類型
1.12的版本在Emgu.TF.Models.Shared專案中新增了一個model
MaskRcnnInceptionV2Coco.cs
目前最新版就提供這三種model類別
因此若是你想要使用其他的model,必須自己進行包裝
也就是本篇要介紹的內容
在要進行影像辨識時,通常需要兩個檔案
一個是物件辨識的model,一個是物件辨識的清單文件
常見的物件辨識的model在tensorflow官網可以找到
tensorflow detection model zoo
常見的分類就是SSD與Faster RCNN這兩個系列
在影像辨識常用的訓練集為COCO dataset
COCO dataset是一個open的資料集,提供的影像與標記物件相當豐富
所以大多數影像辨識的演算法都會優先採用此資料集做訓練
這樣與其他辨識演算法就能有比較客觀的比較基準
可以從剛剛COCO-trained models中下載一個比較輕量的model來做測試
ssd_mobilenet_v1_coco
下載後解壓縮如下圖,只需要其中的model檔frozen_inference_graph.pb
再來就是要下載COCO dataset的物件清單
可從tensorflow的github tensorflow model data
下載mscoco_label_map.pbtxt
(可以直接把此內容複製到記事本另存成mscoco_label_map.pbtxt.txt)
接下來就可以開始進行測試了
這邊用一個.net framework的主控台應用程式來示範
建立好專案後
先來用nuget參考所需的套件
包含Emgu.TF 1.13.1
參考的部分加入組件System.Drawing
Nuget套件與參考設定
由於需要讀取mscoco_label_map.pbtxt這個清單檔案
建議先放置在執行目錄下的Catalog資料夾
mscoco_label_map.pbtxt本身其實就是一個純文字檔
要自己寫parser去讀取裡面的內容也是可以
但這裡直接參考TensorFlowSharp的做法
這裡建立一個叫做CatalogUtil的類別,該類別請參考TensorFlowSharp
直接把TensorFlowSharp裡面的CatalogUtil的class借來用 XDD
接著把剛剛下載的model檔frozen_inference_graph.pb
也放在執行目錄下的model資料夾裡
接下來就開始寫程式了
整個main function就這樣而已
程式說明如下:
這邊主要的任務就是讀入剛剛所下載的mscoco_label_map.pbtxt.txt
以及frozen_inference_graph.pb
並且將tensorflow的session建立起來
接著就是讀取影像並且進行session.run去執行影像辨識
辨識後產生的結果則用boxes、scores、classes這幾個變數來儲存
num就是辨識出來的物件的數量
這邊額外寫了兩個function來協助讀取影像以及繪製結果
(藍色標記的ReadTensorFromImageFile、DrawBoxes)
由於Emgu.TF.Model裡面提供的讀取影像的方法
使用上總是會有一些奇怪的問題
所以這裡調整了Emgu.TF.Model中ImageIO的方法來用,如下:
主要與原本ImageIO.ReadTensorFromImageFile的差別就在紅字的部分
而在DrawBoxes的部分則也是參考了TensorFlowSharp的Example
並簡化了DrawBox的方法
整個測試的程式碼其實蠻簡單的
接著只要把測試的影像test.jpg放到執行目錄
執行完成後,即可看到結果儲存在resultImage.jpg
執行時若遇到下面的問題
表示該專案的執行目標平台不是設定在x64
到專案的屬性->建置 去設定即可
改成x64後就可正常執行
執行時會出現tensorflow的訊息
表示該環境是採用CPU進行運算處理
測試的原圖如下:
辨識後的結果如下:
以上就是利用Emgu.TF套件進行影像辨識
並參考TensorFlowSharp的一些方法來完成整個流程
用此方式,可以很快速的切換辨識的model
只要把執行目錄的model資料夾裡的model檔替換成其他model即可
不過Emgu.TF 僅能在CPU的環境運行
如果要在GPU上運作,請向Emgu.TF購買Commercial License
(其實很便宜,才一千多台幣而已)
而Emgu.TF的更新速度算是蠻快的,也一直持續有在更新
依照之前使用Emgu.CV的經驗
這應該會是個可以期待的tensorflow套件
常見的有兩套TensorFlowSharp與Emgu.TF
本篇以Emgu.TF為主來做介紹
由於過去有使用過Emgu.CV套件
因此對於同團隊推出的Emgu.TF想必會比較熟悉
但由於Emgu.TF推出的時間還很早
目前每個小版本的更新都有不少的差異
官方提供的歷史版本可在此下載:
https://sourceforge.net/projects/emgutf/files/
GitHub與Nuget上的版本最新到1.13.1
https://github.com/emgucv/emgutf/releases/tag/1.13.1
雖然Emgu.TF在Nuget上可以參考
但如果想要多了解實作方式,還是建議下載Source Code來看
裡面有提供Example專案
這裡以Emgu.TF 1.8.0的版本介紹,因為裡面有兩組Example的專案
裡面提供了兩個範例專案
InceptionObjectRecognition、MultiboxPeopleDetection
Emgu.TF這個套件的好處是,幾乎檔案抓下來
就能直接編譯建置成功
這兩個執行專案若是第一次執行
會把所需的model進行下載
Model下載中
而這兩個範例的差別在於
InceptionObjectRecognition的範例是使用tensorflow_inception_graph.pb這個model
一次只辨識一張影像中的單一一個物件
如果你影像中只有一個物件,就適合這個model
文字顯示辨識出來的物件是什麼,機率是多高,花了多久的時間辨識~
MultiboxPeopleDetection的範例是使用multibox_model.pb這個model
可以偵測出影像中人的部分,並請把人的部分框選起來
但是似乎無法辨識其他物件?
只能框選出物件,但無法提供該物件是什麼類型
1.12的版本在Emgu.TF.Models.Shared專案中新增了一個model
MaskRcnnInceptionV2Coco.cs
目前最新版就提供這三種model類別
因此若是你想要使用其他的model,必須自己進行包裝
也就是本篇要介紹的內容
在要進行影像辨識時,通常需要兩個檔案
一個是物件辨識的model,一個是物件辨識的清單文件
常見的物件辨識的model在tensorflow官網可以找到
tensorflow detection model zoo
常見的分類就是SSD與Faster RCNN這兩個系列
在影像辨識常用的訓練集為COCO dataset
COCO dataset是一個open的資料集,提供的影像與標記物件相當豐富
所以大多數影像辨識的演算法都會優先採用此資料集做訓練
這樣與其他辨識演算法就能有比較客觀的比較基準
可以從剛剛COCO-trained models中下載一個比較輕量的model來做測試
ssd_mobilenet_v1_coco
下載後解壓縮如下圖,只需要其中的model檔frozen_inference_graph.pb
再來就是要下載COCO dataset的物件清單
可從tensorflow的github tensorflow model data
下載mscoco_label_map.pbtxt
(可以直接把此內容複製到記事本另存成mscoco_label_map.pbtxt.txt)
接下來就可以開始進行測試了
這邊用一個.net framework的主控台應用程式來示範
建立好專案後
先來用nuget參考所需的套件
包含Emgu.TF 1.13.1
參考的部分加入組件System.Drawing
Nuget套件與參考設定
由於需要讀取mscoco_label_map.pbtxt這個清單檔案
建議先放置在執行目錄下的Catalog資料夾
mscoco_label_map.pbtxt本身其實就是一個純文字檔
要自己寫parser去讀取裡面的內容也是可以
但這裡直接參考TensorFlowSharp的做法
這裡建立一個叫做CatalogUtil的類別,該類別請參考TensorFlowSharp
直接把TensorFlowSharp裡面的CatalogUtil的class借來用 XDD
接著把剛剛下載的model檔frozen_inference_graph.pb
也放在執行目錄下的model資料夾裡
接下來就開始寫程式了
整個main function就這樣而已
程式說明如下:
string catalogPath = Path.Combine("catalog", "mscoco_label_map.pbtxt.txt");
IEnumerable<CatalogItem> catalog = CatalogUtil.ReadCatalogItems(catalogPath);
string modelFile = Path.Combine("model", "frozen_inference_graph.pb");
byte[] model = File.ReadAllBytes(modelFile);
Session TFSession = null;
Graph TFGraph = new Graph();
using (Emgu.TF.Buffer modelBuffer = Emgu.TF.Buffer.FromString(model))
{
using (ImportGraphDefOptions options = new ImportGraphDefOptions())
{
TFGraph.ImportGraphDef(modelBuffer, options);
}
TFSession = new Session(TFGraph);
}
這邊主要的任務就是讀入剛剛所下載的mscoco_label_map.pbtxt.txt
以及frozen_inference_graph.pb
並且將tensorflow的session建立起來
接著就是讀取影像並且進行session.run去執行影像辨識
Tensor image = ReadTensorFromImageFile("test.jpg");
Tensor[] finalTensor = TFSession.Run(
new Output[] { TFGraph["image_tensor"] },
new Tensor[] { image },
new Output[] { TFGraph["detection_boxes"], TFGraph["detection_scores"], TFGraph["detection_classes"], TFGraph["num_detections"] }
);
var boxes = (float[,,])finalTensor[0].JaggedData;
var scores = (float[,])finalTensor[1].JaggedData;
var classes = (float[,])finalTensor[2].JaggedData;
var num = (float[])finalTensor[3].Data;
Image resultImage = Image.FromFile("test.jpg");
resultImage = DrawBoxes(resultImage, boxes, scores, classes, 0.1, catalog);
resultImage.Save("resultImage.jpg");
辨識後產生的結果則用boxes、scores、classes這幾個變數來儲存
num就是辨識出來的物件的數量
這邊額外寫了兩個function來協助讀取影像以及繪製結果
(藍色標記的ReadTensorFromImageFile、DrawBoxes)
由於Emgu.TF.Model裡面提供的讀取影像的方法
使用上總是會有一些奇怪的問題
所以這裡調整了Emgu.TF.Model中ImageIO的方法來用,如下:
public static Tensor ReadTensorFromImageFile(String fileName, int inputHeight = -1, int inputWidth = -1, float inputMean = 0.0f, float scale = 1.0f, Status status = null)
{
using (StatusChecker checker = new StatusChecker(status))
{
var graph = new Graph();
Operation input = graph.Placeholder(DataType.String);
Operation jpegDecoder = graph.DecodeJpeg(input, 3); //dimension 3
Operation floatCaster = graph.Cast(jpegDecoder, DstT: DataType.Float); //cast to float
Tensor axis = new Tensor(0);
Operation axisOp = graph.Const(axis, axis.Type, opName: "axis");
Operation dimsExpander = graph.ExpandDims(floatCaster, axisOp); //turn it to dimension [1,3]
Operation resized;
bool resizeRequired = (inputHeight > 0) && (inputWidth > 0);
if (resizeRequired)
{
Tensor size = new Tensor(new int[] { inputHeight, inputWidth }); // new size;
Operation sizeOp = graph.Const(size, size.Type, opName: "size");
resized = graph.ResizeBilinear(dimsExpander, sizeOp); //resize image
}
else
{
resized = dimsExpander;
}
Tensor mean = new Tensor(inputMean);
Operation meanOp = graph.Const(mean, mean.Type, opName: "mean");
Operation substracted = graph.Sub(resized, meanOp);
Tensor scaleTensor = new Tensor(scale);
Operation scaleOp = graph.Const(scaleTensor, scaleTensor.Type, opName: "scale");
Operation scaled = graph.Mul(substracted, scaleOp);
Operation finalCast = graph.Cast(scaled, DataType.Uint8, false, "finalCast");
Session session = new Session(graph);
Tensor imageTensor = Tensor.FromString(File.ReadAllBytes(fileName), status);
Tensor[] imageResults = session.Run(new Output[] { input }, new Tensor[] { imageTensor },
new Output[] { finalCast });
return imageResults[0];
}
}
主要與原本ImageIO.ReadTensorFromImageFile的差別就在紅字的部分
而在DrawBoxes的部分則也是參考了TensorFlowSharp的Example
並簡化了DrawBox的方法
static Image DrawBoxes(Image image, float[,,] boxes, float[,] scores, float[,] classes, double minScore, IEnumerable<CatalogItem> catalog)
{
var x = boxes.GetLength(0);
var y = boxes.GetLength(1);
var z = boxes.GetLength(2);
float ymin = 0, xmin = 0, ymax = 0, xmax = 0;
for (int i = 0; i < x; i++)
{
for (int j = 0; j < y; j++)
{
if (scores[i, j] < minScore) continue;
for (int k = 0; k < z; k++)
{
var box = boxes[i, j, k];
switch (k)
{
case 0:
ymin = box;
break;
case 1:
xmin = box;
break;
case 2:
ymax = box;
break;
case 3:
xmax = box;
break;
}
}
int value = Convert.ToInt32(classes[i, j]);
CatalogItem catalogItem = catalog.FirstOrDefault(item => item.Id == value);
DrawBox(image, xmin, xmax, ymin, ymax, $"{catalogItem.DisplayName} : {(scores[i, j] * 100).ToString("0")}%");
}
}
return image;
}
DrawBoxes方法就是把boxes、scores、classes這幾個結果的陣列做處理
轉換成rectangle的座標位置、辨識率以及辨識物件名稱
接著DrawBox就是把image帶入,並透過.net framework的Graphics
把物件的資料畫在影像上,包含物件的框框、辨識物件的資訊
public static Image DrawBox(Image image, float xmin, float xmax, float ymin, float ymax, string text = "", string colorName = "red")
{
var left = xmin * image.Width;
var right = xmax * image.Width;
var top = ymin * image.Height;
var bottom = ymax * image.Height;
using (Graphics graphics = Graphics.FromImage(image))
{
Color color = Color.FromName(colorName);
Brush brush = new SolidBrush(color);
Pen pen = new Pen(brush);
graphics.DrawRectangle(pen, left, top, right - left, bottom - top);
var font = new Font("Ariel", 12);
SizeF size = graphics.MeasureString(text, font);
graphics.DrawString(text, font, brush, new PointF(left, top - size.Height));
}
return image;
}
整個測試的程式碼其實蠻簡單的
接著只要把測試的影像test.jpg放到執行目錄
執行完成後,即可看到結果儲存在resultImage.jpg
執行時若遇到下面的問題
表示該專案的執行目標平台不是設定在x64
到專案的屬性->建置 去設定即可
改成x64後就可正常執行
執行時會出現tensorflow的訊息
表示該環境是採用CPU進行運算處理
測試的原圖如下:
辨識後的結果如下:
以上就是利用Emgu.TF套件進行影像辨識
並參考TensorFlowSharp的一些方法來完成整個流程
用此方式,可以很快速的切換辨識的model
只要把執行目錄的model資料夾裡的model檔替換成其他model即可
不過Emgu.TF 僅能在CPU的環境運行
如果要在GPU上運作,請向Emgu.TF購買Commercial License
(其實很便宜,才一千多台幣而已)
而Emgu.TF的更新速度算是蠻快的,也一直持續有在更新
依照之前使用Emgu.CV的經驗
這應該會是個可以期待的tensorflow套件
請問您這篇有放在Github上嗎?若沒有的話能麻煩您丟上去借我參考嗎?非常感謝!我剛入門嘗試了一下您的作法,再加入參考system drawing.dll 的地方發生錯誤"工作失敗,因為找不到AxPlme.exe",已經有安裝Windows SDK,不知道問題出在哪邊!
回覆刪除你是用哪個版本的visual studio 以及建立的方案是.net framework哪個版本呢??
刪除請問在Graph TFGraph = new Graph();地方...........出現System.TypeInitializationException:''Emgu.TF.TfInvoke' 的類型初始設定式發生例外狀況。錯誤訊息....請問要如何處理
回覆刪除文章內容有寫到,可能你設定的專案的執行目標平台不是設定在x64
刪除到專案的屬性->建置 去設定即可