0. 預備
在上一篇文章中霜威,我們測試了100個中文字符使用Lenet5模型的正確率,其正確率達到了100%册烈。(不達到100%就有問題了戈泼,我的訓練集和測試集是極其近似的,僅僅是為了自己練手赏僧,而并未達到實際用途大猛。) 接下來要做的事情是,用C++ API讀取一個圖像淀零,并對其進行識別挽绩。在《Caffe Windows系列(2): 使用C++ API進行分類》一文中,談到了如果要能夠進行識別驾中,需要準備五個文件唉堪。
- deploy.prototxt:這個文件目前還沒有模聋。
-
network.caffemodel:這個文件已經(jīng)有了,即
lenet_iter_10000.caffemodel
唠亚。 - mean.binaryproto:這個文件還沒有链方。
- labels.txt:這個文件就是一個分類文件。
- img.jpg:要測試的圖像
我的labels.txt文件類似于如下的列表
丁
萬
嚴
于
任
何
余
侯
傅
馮
劉
盧
史
葉
向
呂
吳
...
馬
高
魏
黃
黎
龍
龔
1. deploy.prototxt
在mnist的例子中灶搜,lenet.prototxt就相當于deploy.txt祟蚀。它與真正用于訓練的lenet_train_test.prototxt有一些差別,但好在差別也不大占调。
- 差別1:輸入層暂题。lenet_train_test.prototxt的輸入是lmdb文件。而deploy.txt中是沒有使用lmdb文件的究珊。因此薪者,lenet.prototxt將輸入層簡化為:
layer {
name: "data"
type: "Input"
top: "data"
input_param { shape: { dim: 64 dim: 1 dim: 28 dim: 28 } }
}
不過我認為,使用中剿涮,shape的第一個dim應當寫為1才對言津。因為64是batch_size。而我們測試的時候取试,是一個一個測試的悬槽,因此,可以設(shè)置為1瞬浓。
- 差別2:作為TEST階段的accuracy層可以不再需要了初婆。
- 差別3:loss層變?yōu)閜rob層。因為不需要進行損失函數(shù)的計算了猿棉。
loss層:
layer {
name: "loss"
type: "SoftmaxWithLoss"
bottom: "ip2"
bottom: "label"
top: "loss"
}
prob層:
layer {
name: "prob"
type: "Softmax"
bottom: "ip2"
top: "prob"
}
依樣畫葫蘆磅叛,我們就可以寫出自己的deploy.prototxt文件來。
name: "LeNet"
layer {
name: "data"
type: "Input"
top: "data"
input_param { shape: { dim: 1 dim: 1 dim: 40 dim: 40 } }
}
layer {
name: "conv1"
type: "Convolution"
bottom: "data"
top: "conv1"
param {
lr_mult: 1
}
param {
lr_mult: 2
}
convolution_param {
num_output: 20
kernel_size: 5
stride: 1
weight_filler {
type: "xavier"
}
bias_filler {
type: "constant"
}
}
}
layer {
name: "pool1"
type: "Pooling"
bottom: "conv1"
top: "pool1"
pooling_param {
pool: MAX
kernel_size: 2
stride: 2
}
}
layer {
name: "conv2"
type: "Convolution"
bottom: "pool1"
top: "conv2"
param {
lr_mult: 1
}
param {
lr_mult: 2
}
convolution_param {
num_output: 50
kernel_size: 5
stride: 1
weight_filler {
type: "xavier"
}
bias_filler {
type: "constant"
}
}
}
layer {
name: "pool2"
type: "Pooling"
bottom: "conv2"
top: "pool2"
pooling_param {
pool: MAX
kernel_size: 2
stride: 2
}
}
layer {
name: "ip1"
type: "InnerProduct"
bottom: "pool2"
top: "ip1"
param {
lr_mult: 1
}
param {
lr_mult: 2
}
inner_product_param {
num_output: 1000
weight_filler {
type: "xavier"
}
bias_filler {
type: "constant"
}
}
}
layer {
name: "relu1"
type: "ReLU"
bottom: "ip1"
top: "ip1"
}
layer {
name: "ip2"
type: "InnerProduct"
bottom: "ip1"
top: "ip2"
param {
lr_mult: 1
}
param {
lr_mult: 2
}
inner_product_param {
num_output: 100
weight_filler {
type: "xavier"
}
bias_filler {
type: "constant"
}
}
}
layer {
name: "prob"
type: "Softmax"
bottom: "ip2"
top: "prob"
}
2. 繞開均值文件
由于Lenet-5的訓練并未使用到均值文件萨赁,因此弊琴,可以生成一個全零的均值文件來代替。如果使用python接口杖爽,生成全零的均值文件比較方便敲董,網(wǎng)上有很多文章。但如果使用C++接口慰安,它使用的均值文件是binaryproto后綴的腋寨,不是直接可視化的那種,因此泻帮,生成全零的均值文件并不是那么容易的事精置。相比之下,可能在cpp_classification
代碼的基礎(chǔ)上進行修改锣杂,從而繞過這個均值文件會更容易一些脂倦。
之前對于mean_file
的處理番宁,主要是SetMean這個函數(shù):
/* Load the mean file in binaryproto format. */
void Classifier::SetMean(const string& mean_file) {
BlobProto blob_proto;
ReadProtoFromBinaryFileOrDie(mean_file.c_str(), &blob_proto);
/* Convert from BlobProto to Blob<float> */
Blob<float> mean_blob;
mean_blob.FromProto(blob_proto);
CHECK_EQ(mean_blob.channels(), num_channels_)
<< "Number of channels of mean file doesn't match input layer.";
/* The format of the mean file is planar 32-bit float BGR or grayscale. */
std::vector<cv::Mat> channels;
float* data = mean_blob.mutable_cpu_data();
for (int i = 0; i < num_channels_; ++i) {
/* Extract an individual channel. */
cv::Mat channel(mean_blob.height(), mean_blob.width(), CV_32FC1, data);
channels.push_back(channel);
data += mean_blob.height() * mean_blob.width();
}
/* Merge the separate channels into a single image. */
cv::Mat mean;
cv::merge(channels, mean);
/* Compute the global mean pixel value and create a mean image
* filled with this value. */
cv::Scalar channel_mean = cv::mean(mean);
mean_ = cv::Mat(input_geometry_, mean.type(), channel_mean);
}
現(xiàn)在,我將SetMean函數(shù)改為:
void Classifier::SetMean(const string& mean_file) {
mean_ = cv::Mat::zeros(input_geometry_, CV_32F);
}
這樣的話赖阻,不管輸入的mean_file
是啥蝶押,我都會讓mean_成為一個全零矩陣。
3. 正式測試
將examples\classification設(shè)為啟動項火欧,配置調(diào)試參數(shù):
命令參數(shù)為:
deploy.prototxt lenet_iter_10000.caffemodel mean.binaryproto labels.txt test.bmp
test.bmp為:
運行后的結(jié)果為:
---------- Prediction for test.bmp ----------
1.0000 - "潘"
0.0000 - "萬"
0.0000 - "于"
0.0000 - "任"
0.0000 - "丁"
也就是說棋电,這個圖,100%像“潘”字苇侵,而0%像后面的“萬”赶盔、“于”、“任”榆浓、“丁”于未。這個結(jié)果是一個非常不錯的鼓勵。有理由相信陡鹃,CNN完全可以勝任印刷體漢字的識別烘浦。接下來,我會去嘗試身份證信息的識別~