機器學習演算法總是離不開訓練樣本的,通常情況下,你找到的圖片並非僅僅含有正樣本的,而應該是同時含有正樣本和負樣本的圖片;例如,你打算利用機器學習的演算法來進行臉部偵測,需要找到人臉的正樣本(人臉圖片)和負樣本(非人臉圖片),這個時候,正樣本和負樣本往往不是那麼容易找到的(當然,人臉的訓練樣本目前在網上能找到很多公開的訓練樣本庫,但如果你要找車輛的訓練樣本呢?表情的訓練樣本呢?貓的訓練樣本?飛機的訓練樣本?。。。);這個時候,就需要拍攝或者下載很多包含正樣本(如,人臉)的圖片;當然,這個圖片當中,某些地區是人臉(正樣本),其它地區是非人臉(負樣本);顯然,用畫圖工具之類的軟體把一張一張的圖片中的正樣本地區人工扣去下來,作為正樣本,
剩下的地區,作為負樣本,這是一個可行的辦法;你可以這麼做,但作為程式員,似乎寫個程式,遍曆檔案夾中所有的圖片,依次顯示圖片,由使用者通過滑鼠點擊幾下得到正樣本地區和負樣本地區,這樣要更高效一些吧。前者是純人工的辦法,後者是半人工的辦法;當你扣取了足夠的正負樣本,訓練得到分類器之後,利用機器(電腦)就能自動的把圖片中的正樣本地區(人臉)給扣取出來,這就是全自動的辦法了;機器學習的目的,就是讓機器來代替人工高效的完成重複性的工作嘛;當然了,在沒有得到訓練樣本之前,你還是得利用純人工或者半人工的方法來解決訓練樣本的問題;筆者給出一個半人工的程式,方便朋友們以後在樣本製作過程中使用;
參考代碼:
#include "stdafx.h"#include "windows.h"#include <vector>#include <string>#include "opencv.hpp"#include "iostream"#include "fstream"using namespace std;typedef std::vector<std::string> file_lists;static int str_compare(const void *arg1, const void *arg2){return strcmp((*(std::string*)arg1).c_str(), (*(std::string*)arg2).c_str());//比較字串arg1 and arg2}file_lists ScanDirectory(const std::string &path, const std::string &extension){ WIN32_FIND_DATA wfd;//WIN32_FIND_DATA:Contains information about the file that is found by the //FindFirstFile, FindFirstFileEx, or FindNextFile function HANDLE hHandle; string searchPath, searchFile; file_lists vFilenames; int nbFiles = 0; searchPath = path + "/*" + extension; hHandle = FindFirstFile(searchPath.c_str(), &wfd);//Searches a directory for a file or subdirectory //with a name that matches a specific name if (INVALID_HANDLE_VALUE == hHandle) { fprintf(stderr, "ERROR(%s, %d): Cannot find (*.%s)files in directory %s/n", __FILE__, __LINE__, extension.c_str(), path.c_str()); exit(0); } do { //. or .. if (wfd.cFileName[0] == '.') { continue; } // if exists sub-directory if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)//dwFileAttributes:The file attributes of a file { //FILE_ATTRIBUTE_DIRECTORY:The handle identifies a directory continue; } else//if file { searchFile = path + "/" + wfd.cFileName; vFilenames.push_back(searchFile); nbFiles++; } }while (FindNextFile(hHandle, &wfd));//Call this member function to continue a file search begun //with a call to CGopherFileFind::FindFile FindClose(hHandle);//Closes a file search handle opened by the FindFirstFile, FindFirstFileEx, //or FindFirstStreamW function // sort the filenames qsort((void *)&(vFilenames[0]), (size_t)nbFiles, sizeof(string), str_compare);//Performs a quick sort return vFilenames;}bool rBtnDown = false;const int ptsSize = 4;CvPoint pts[ptsSize];int ptsCount = 0;void mouseOn(int e,int x,int y,int flags,void* param){if (e == CV_EVENT_LBUTTONDOWN){pts[ptsCount++] = cvPoint(x,y);}else if (e == CV_EVENT_RBUTTONDOWN){rBtnDown = true;}}void GetTrainSample(){string folderIn, fileExt, folderOut;ifstream fileIn;fileIn.open("config.ini", ios::in);if (!fileIn){cout<<"config.ini open error"<<endl;system("pause");exit(-1);}char str[512];memset(str, '\0', 512*sizeof(char));fileIn>>str;folderIn = str;memset(str, '\0', 512*sizeof(char));fileIn>>str;fileExt = str;memset(str, '\0', 512*sizeof(char));fileIn>>str;folderOut = str;file_lists files = ScanDirectory(folderIn, fileExt);int size = files.size();cout<<"檔案夾中的圖片總數:"<<size<<endl;string fileName;string path;string ptsName;int len;cvNamedWindow("img", 0);cvSetMouseCallback("img", mouseOn);for (int i=0; i<size; i++){cout<<i+1<<"/"<<size<<endl;int idx = files[i].find_last_of('\/');fileName.clear();fileName = files[i].substr(idx+1, files[i].length()-idx);path = folderOut + "/"+ fileName;ptsName = path;len = ptsName.length();if (ptsName[len-4] = '.'){ptsName[len-1] = 't';ptsName[len-2] = 'x';ptsName[len-3] = 't';}else{ptsName[len-1] = '\0';ptsName[len-2] = 't';ptsName[len-3] = 'x';ptsName[len-4] = 't';}ofstream fileOut;fileOut.open(ptsName.c_str(), ios::out);IplImage* pImg = cvLoadImage(files[i].c_str());if (!pImg){cout<<"img load error, fileName: "<<files[i].c_str();continue;}cvSaveImage(path.c_str(), pImg);while(!rBtnDown){cvShowImage("img", pImg);cvWaitKey(1);if (ptsCount == ptsSize){int minX,minY,maxX,maxY;minX = maxX = pts[0].x;minY = maxY = pts[0].y;for (int j=1; j<ptsSize; j++){minX = minX<pts[j].x ? minX:pts[j].x;minY = minY<pts[j].y ? minY:pts[j].y;maxX = maxX>pts[j].x ? maxX:pts[j].x;maxY = maxY>pts[j].y ? maxY:pts[j].y;}fileOut<<minX<<" "<<minY<<" "<<maxX<<" "<<maxY<<endl;ptsCount = 0;cvRectangle(pImg, cvPoint(minX,minY), cvPoint(maxX,maxY), CV_RGB(255,0,0));cvShowImage("img", pImg);cvWaitKey(1);}} rBtnDown = false;ptsCount = 0;cvReleaseImage(&pImg);fileOut.close();}}void Usage(){cout<<"config.ini說明"<<endl;cout<<"程式從config.ini檔案中依次讀取三行資訊:\n輸入圖片檔案夾路徑 \n輸入圖片尾碼 \n輸出路徑;\n\nconfig.ini中的內容舉例:\nc:\\inputDir \n.bmp \nD:\\result"<<endl;cout<<endl;cout<<"本程式使用說明:"<<endl;cout<<"使用者滑鼠左鍵單擊4次即可選定圖片中的一個矩形地區,選定若干個矩形地區之後,單擊滑鼠右鍵即可切換到下一張圖片"<<endl;cout<<"//////////////////////////////////////////////////////////////////////"<<endl;}int _tmain(int argc, _TCHAR* argv[]){Usage();GetTrainSample();system("pause");return 0;}
說明:
(1)以上代碼需要opencv,請自行配置相關的lib和dll;
(2)以上代碼在編譯過程中,請勿選擇“Unicode”字元集,如,VS2008,VS2010中,請如下設定,項目-->屬性-->常規-->字元集-->未設定;
(3)可執行檔所在路徑請建立一個config.ini檔案,該檔案包含三行:
輸入圖片路徑
圖片尾碼名
輸出路徑;
config.ini參考設定:
E:\Images
.bmp
E:\result
(4)config.ini中的輸入和輸出路徑請盡量不要包含中文路徑,可能會出錯;