ニューラルネットワークの知識ゼロでも
私を含め、一般人はよく知らないのではないだろうか?
知らなければそれはそれでいい。知識など後から身につく。
とりあえず体験することにした。
OpenCVで作ってみた
プログラム自体ど素人。とりあえず『OPENCV』をインストールして、C++でナンバープレートの数字認識プログラムを書いてみる。去年からプログラムを独学で学び始めて今で半年すぎたくらいで、多々変なコードもあるかとおもいますが、いろいろご指摘いただければありがたいです。
海外のサイトを参考にしてどうにか作成。日本語のサイトでは石立 喬氏のニューラルネットワーク『ニューラルネットワークで数字を判別する 』が大いに役に立った。
ピクセル値をバイナリ化する
まず数字の0から9までのタイルを作成する。今回は自動車のナンバープレートの写真をもとに作成した。サイズがバラバラだが、リサイズするので問題ないということにした。これを学習用のサンプルとする。これは初期画像で、後に二値化しておく。
画像の前処理で白黒化してあるので、ピクセル値は0または255だ。これを255で割ると0と1になる。
このデータをdatファイルにいったん保存する。
数字0から開始し順番にバイナリデーターを後ろに追加していくコード。
fstream inFile;
stringstream dataFile;
dataFile << "./dat/data" << i << ".dat";
if (counter == 0){
inFile.open(dataFile.str(), ios::out | ios::in | ios::trunc);
}
else
{
inFile.open(dataFile.str(), ios::out | ios::app | ios::binary);
}
for (int y = 0; y < 20; y++){
for (int x = 0; x < 10; x++){
inFile << (int)(resizedImage.at<uchar>(y, x)) / 255;
}
}
inFile.close();
下のようなバイナリデーターが作成される。
これは0から9の画像を二値化した時の連続したピクセル値データーで、一文字(10×20)、数字10個分あるので2000バイトのファイルが作成される。マトリックスはフロートで入力しないとエラーとなる。
00000000000000000000000000000000011110000011111100001100110000100011000110001100001000110000100011000010001100001000110000100011000010001100001101110000111111000001110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000110000000011000000001100000000110000000011000000001100000000110000000011000000001100000000110000000011000000001100000000110000000000000000000000000000000000000000000000001100000011111100011111110000100011100000001110000000110000000111000000011000000011100000001100000001110000000110000000111000000011111110001111111000000000000000000000000000000000000000000000000000000000000000011100000011111100000000110000000001000000001100000001100000000110000000011000000000110000000001000000000100000000010000000011000011111000000011000000000000000000000000000000000000000000000000001000000001110000001111000000111100000111110000011111000001111100001111110000110011000111001100011101110001111111100111111110000001110000000011000000001100000000000000000000000000000000000000000000000000000011111110001111111000110000000011000000001100000000111110000011111100000101111000000001100000000110000000011000000001100000001110001111110000111110000000000000000000000000000000000000000000000000000000000011000000011100000001100000001100000001100000001110000000110000000011110000001111110000110011000110000100011000010000110011000011111000000000000000000000000000000000000000000000000000000000000000011111111000111111100000001110000000110000000111000000011000000001100000001100000000110000000011000000001000000001100000000110000000011000000001100000000000000000000000000000000000000000000000000000000001111000001111110001110011000110001100001100110000111111000001111000001111110000110011000111000110011000011000110001100011001100000111110000000100000000000000000000000000000000000000000000000000000000011110000011111100001100111001100001100111000110001111111000011111100000101110000000011000000001100000000110000000011000000011000011111100001111100000000000000000000000
データーをマトリックス化
バイナリーデーターを10×200のマトリックスのピクセルに打ち込む。datファイルを開いて、順番にピクセルの左上から右下にデーターを入力していく。
Mat numberData = (Mat_<float>(10, 200));
stringstream ss;
ss << "./dat/data" << i << ".dat";
ifstream inputStream;
inputStream.open(ss.str(), ifstream::binary);
for (int x = 0; x < numberData.rows; x++){
for (int y = 0; y < numberData.cols; y++){
char moji;
int dataNum;
inputStream.get(moji);
dataNum = atoi(&moji);
//cout << dataNum;
if (dataNum == 1) numberData.at<float>(x, y) = 1.0f;
else if (dataNum == 0) numberData.at<float>(x, y) = 0.0f;
}
}
学習したニューロンはxmlへ保存
学習用には10×10ピクセルの教師用マットを作成する。iとjが交差する点で線を描く。左上から右下に対角線状に線が引かれる。これを使って学習する。
なんとか交差法で学習・予測するためらしいが、詳しい原理や内容は今のところ理解できず。
Mat teacherMatrix(const int& colPixels,const int& rowPixels){
Mat trainTeacher = Mat(Size(colPixels, rowPixels), CV_32F);
for (int j = 0; j < trainTeacher.rows; j++){
for (int i = 0; i < trainTeacher.cols; i++){
if (i == j) trainTeacher.at<float>(j, i) = 1.0f;
else trainTeacher.at<float>(j, i) = 0.0f;
}
}
return trainTeacher;
}
trainメソッドで学習したニューロンは、いったんxmlへ保存する。
neuron->train(numberData, ROW_SAMPLE, teacherMatrix(10, 10));
FileStorage fs;
fs.open("./dat/neuron.xml", FileStorage::WRITE);
neuron->write(fs);
fs.release();
ニューロンのセットアップ
入力が200、中間層が10、出力が10になるようにセットする。中間層が多すぎると精度が落ちたりする。TrainMethodなどの引数は、よくわからないので今は適当でOK。OpenCVのリファレンスにもう少し詳しく書いてくれるとありがたいのだが・・・。
void neuronSetUp(){
const int inputNeuron = 200;
const int hiddenNeuron = 10;
const int outputNeuron = 10;
vector<int> layerSize = { inputNeuron, hiddenNeuron, outputNeuron };
neuron->setTrainMethod(ANN_MLP::BACKPROP,0.00001,0.00001);
neuron->setLayerSizes(layerSize);
neuron->setActivationFunction(ANN_MLP::SIGMOID_SYM, 0.66,1.0);
neuron->setTermCriteria(TermCriteria(TermCriteria::EPS | CV_TERMCRIT_ITER,50000,0.00001));
}
プログラム実行
国内の普通自動車ナンバープレートの一番大きな数字を認識するかどうか実験。
手順として、まずニューロンをxmlファイルからロードする。カメラから撮像したナンバープレートのROIをグレイスケール化し、さらに上記のようにバイナリ化する。0と1のデータをいったんdatファイルに保存。再度、バイナリデータを呼び出して、predictで使用するマトリックスにバイナリデータを入力していく。
String processNeuralNetwork(string& nameMatWindow) {
(中略)
neuron->predict(binaryNumMat, result);
minMaxLoc(result, NULL, NULL, NULL, &maxLocationResult);
//cout << maxLocationResult.x;
stringstream ss;
ss << maxLocationResult.x;
detectedNumber = ss.str();
return detectedNumber;
}
結果
インターネットからサンプル用(架空)のナンバープレートの写真をA4用紙に印刷し、壁に貼った状態で撮影した。
実際、それなりに認識していると思うが、学習用のパラメーターをいじるとほぼすべての数字で誤認識となったりするので、さらに研究が必要であると感じる。
今回は学習用の画像タイルを数字ひとつにつき1枚だけ用意したが、複数枚のセットで学習する場合、どうすればいいのか分からず、今後の課題も残った。