VS版本VS2015 opencv版本3.4.1
簡單介紹流程:
opencv自帶有ANN-MLP(神經網絡--多層感知器)的模塊,該模塊在我們編寫訓練程序時提供很大的幫助
首先簡單介紹多層感知機構的概念

上圖為一個簡單的感知器,其中X1,X2....Xn為圖像的基本特征,W1,W2....Wn代表每個特征的權重,對所有特征進行簡單的線性求和可以得到一個函數輸出結果,通過決策超平面進行邏輯判斷,可以對函數結果進行分類,大於0的分為一類,小於0的分為另一類。
如果把上圖的X1-Xn表示為數字1的特征,那么另一張數字1通過相同的權重求后通過決策面后的結果不一定是1,也就是說第二張1的分類是錯誤的,所以,第二張圖片輸入時需要對每個特征的權重進行調整,調整后可以使得分類正確;這也就是我們說的樣本訓練,樣本訓練的目的就是為了調整所有特征的權重值,直到訓練出來的一套權重值可以滿足所有的樣本分類。那么可以預知樣本越多,訓練出來的權重參數的適用性也就越廣。
不過存在另一個問題是現實的情況不可能是簡單的將特征加權求和,要分類的結果也遠遠不止兩類,所以簡單的神經感知結構就不適用了,需要引入激活函數的概念,激活函數有幾種固定的類型,常用的是S型激活函數sigmoid,激活的特征是可對所有的權重求導,若將(實際輸出-期望輸出)作為每次訓練后的誤差函數(誤差肯定越小越好),那么將該誤差函數對所有權重求導可以得到誤差與每一個權重之間的關系,可以知道在所有權重中哪個是影響誤差的最大因素,利用梯度下降原則調整該權重值,即可使得誤差減小,每一次訓練都會重復進行這一過程,那么在足夠多的樣本訓練下就可以得到期望的分類集。

上面介紹了大概的思想,接下來就是如何實現:
一、特征的尋找與存取
如何尋找特征,在本例子中要識別的是0-9共10個數字,選取什么作為特征,如何提取特征就是第一步要考慮的。對於這些簡單的數字,最簡單的方式就是先對所有樣本歸一化為同一大小,然后提取每一個像素值作為特征輸入;另外的特征提取方法還有梯度特征提取,通過sobel算子提取數字圖片的水平、垂直特征作為特征輸入。
在本例子中采用的是直接提取圖片每一個像素值作為特征
那么在程序中如何實現將特征提取與存取:
1、將樣本圖片歸一化為統一大小,這里歸一化為8x16像素大小;
2、明確樣本總數以及分類結果數,這里要識別0-9共10個數,如果每張數字的樣本用50張,那么樣本總數為10x50=500張,分類結果數自然為10(10個數嘛);
3、程序實現將所有樣本圖片進行遍歷二值化把所有像素點存入一個樣本數據數組中,這個數組的行列分別為樣本總數500和每一張圖片的特征總數8x16,目的是使得每一行代表一張樣本的特征數據;
4、第三步中的特征數據就是感知器里提到的x1-Xn特征輸入,那么輸出是什么,輸出是指定每一張樣本圖片的輸出結果,輸出對應於輸入,有500個輸入,那么就有500個輸出,可是500個輸出要分為10類,那么最直接的就是0-49為一類,50-99為另一類,以此類推;輸出在程序里的體現也是用一個數組表示,定義一個500x10的數組,樣本數字0輸出為前50行,可定義輸出為[1,0,0,0,0,0,0,0,0,0];樣本數字1的輸出為下50行,可定義輸出為[0,1,0,0,0,0,0,0,0,0];依此可得到一個500x10的輸出數組;
5、輸入輸出都有了,最后就是對這些樣本參數進行訓練,設置訓練模型的參數,包括訓練網絡的層數,訓練原理,訓練終止條件,訓練激活函數等等。
const int imageRows = 8; const int imageCols = 16; //圖片共有10類 const int classSum = 10; //每類共50張圖片 const int imagesSum = 50; //每一行一個訓練圖片 float trainingData[classSum*imagesSum][imageRows*imageCols] = { { 0 } };//特征存放數組 //訓練樣本標簽 float labels[classSum*imagesSum][classSum] = { { 0 } }; //結果分類輸出數組 Mat src, resizeImg, trainImg; for (int i = 0; i < classSum; i++) { //目標文件夾路徑 std::string inPath = "E:\\study\\VS2015\\practice\\charSamples\\";//存放的樣本的目錄 char temp[256]; int k = 0; sprintf_s(temp, "%d", i); inPath = inPath + temp + "\\*.png"; std::cout << inPath; //用於查找的句柄 long long handle; struct _finddata_t fileinfo; //第一次查找 handle = _findfirst(inPath.c_str(), &fileinfo);//遍歷文件夾下的.png文件 //std::cout << "handle :"<<fileinfo.name; if (handle == -1) return -1; do { //找到的文件的文件名 std::string imgname = "E:/study/VS2015/practice/charSamples/"; imgname = imgname + temp + "/" + fileinfo.name; src = imread(imgname, 0); if (src.empty()) { std::cout << "can not load image \n" << std::endl; return -1; } //將所有圖片大小統一轉化為8*16 resize(src, resizeImg, Size(imageRows, imageCols), (0, 0), (0, 0), INTER_AREA); threshold(resizeImg, trainImg, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU); for (int j = 0; j<imageRows*imageCols; j++) { trainingData[i*imagesSum + k][j] = (float)resizeImg.data[j]; } // 設置標簽數據,標簽數據里就是500x10的輸出數據,訓練時會按照這個結果進行誤差調節,調整各個特征參數的權重值 for (int j = 0; j < classSum; j++) { if (j == i) labels[i*imagesSum + k][j] = 1; else labels[i*imagesSum + k][j] = 0; } k++; } while (!_findnext(handle, &fileinfo)); Mat labelsMat(classSum*imagesSum, classSum, CV_32FC1, labels); _findclose(handle); } //訓練樣本數據及對應標簽 Mat trainingDataMat(classSum*imagesSum, imageRows*imageCols, CV_32FC1, trainingData); Mat labelsMat(classSum*imagesSum, classSum, CV_32FC1, labels);
二、模型參數的設置
Ptr<ANN_MLP>model = ANN_MLP::create(); Mat layerSizes = (Mat_<int>(1, 5) << imageRows*imageCols, 128, 128, 128, classSum);//設置的網絡層數為5層,數組值為每一層的的節點數,輸入為8x16=128,輸出為10 model->setLayerSizes(layerSizes); model->setTrainMethod(ANN_MLP::BACKPROP, 0.001, 0.1); //訓練方式為反向傳播 model->setActivationFunction(ANN_MLP::SIGMOID_SYM, 1.0, 1.0);//設置激活函數 model->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER | TermCriteria::EPS, 10000, 0.0001));//設置終止條件,迭代次數滿足10000或者誤差小於0.0001 Ptr<TrainData> trainData = TrainData::create(trainingDataMat, ROW_SAMPLE, labelsMat); model->train(trainData); //保存訓練結果 model->save("E:/study/VS2015/practice/MLPModel.xml"); //保存訓練好的模型
設置完畢后即可點擊運行進行訓練,這里訓練的時間會稍稍費點時間,不要以為是程序卡死了,等待3-5分鍾即可
三、調用模型進行測試
模型有了之后需要驗證,下面就是對模型進行驗證,注意在對圖片測試時同樣要對圖片的特征進行提取,只是此時的輸出是通過輸入特征經過模型計算后得到的,該輸出會與原有的10個輸出進行比對,比較更接近哪個結果,由此實現識別,所以由此也可以看出樣本數量越多或者特征選取更恰當會使得訓練出來的模型適用性更高。
int main() { //將所有圖片大小統一轉化為8*16 const int imageRows = 8; const int imageCols = 16; //讀取訓練結果 Ptr<ANN_MLP> model = StatModel::load<ANN_MLP>("E:/study/VS2015/practice/MLPModel.xml");//模型存放路徑 ////==========================預測部分==============================//// //讀取測試圖像 Mat test, dst; test = imread("E:/study/VS2015/practice/model4.png", 0); if (test.empty()) { std::cout << "can not load image \n" << std::endl; return -1; } //將測試圖像轉化為1*128的向量,為什么是128列,應為訓練使采用的輸入節點數就是500x128的數組 resize(test, test, Size(imageRows, imageCols), (0, 0), (0, 0), INTER_AREA);//測試圖片的特征存放數組 threshold(test, test, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);//測試圖片二值化 Mat_<float> testMat(1, imageRows*imageCols); for (int i = 0; i < imageRows*imageCols; i++) { testMat.at<float>(0, i) = (float)test.at<uchar>(i / 8, i % 8);//提取每個點的像素 } //使用訓練好的MLP model預測測試圖像 model->predict(testMat, dst); std::cout << "testMat: \n" << testMat << "\n" << std::endl; std::cout << "dst: \n" << dst << "\n" << std::endl;//模型計算得到的輸出 double maxVal = 0; Point maxLoc; minMaxLoc(dst, NULL, &maxVal, NULL, &maxLoc); std::cout << "測試結果:" << maxLoc.x << "置信度:" << maxVal * 100 << "%" << std::endl; imshow("test", test); waitKey(0); return 0; }
結尾附上樣本集的文件下載鏈接鏈接:https://pan.baidu.com/s/14zy7_oGpw8VXG4uAzZ8zBg
提取碼:yqt7
