QT 下用opencv實現影象分類(1)
一.概述
1.按影象中的內容給影象分類是計算機視覺中比較適合初學者的專案,我見過好多手機相簿都有這一個功能,比如把美食歸為一個標籤,藍天白雲歸為一個標籤等等。還有我之前做過的車牌識別的專案都用到影象分類。
2.我做這個專案的環境是QT加opencv3.2,專案在MAC上跑過,在win7也跑過,關於QT加opencv庫的配置,各個系統網上有非常詳細的教程,有興趣的可以試試。
3.這個專案所涉及到的知識點,QT的讀寫檔案與資料夾,opencv的特徵提取,BOW,SVM等,C++的幾個關聯容器等。
二.主要功能
專案的是作用是先對一些已經手動歸類好的圖進行輸入並訓練,然後對未知的影象進行預測判斷並分類,主要是為了代替人的視覺完成對未知影象的歸類。
大體流程圖:
三.資料準備
1.在寫專案之前,必須先準備好要訓練的圖片集,就是把擁有相同內容的影象放在一個資料夾下,格式如下:
2.影象不能太大,也不能太小,試過在訓練集放了全是幾M以上的影象,跑特徵聚類時跑了一天沒有跑出來,如果影象太小隻有幾K,到特徵提取那裡有可能會報錯,認為是空的影象矩陣。
3.要用來訓練的影象可以到那些專業桌布的網站下載,一般那些網站都分好類了。
四.基本流程與步驟
1.讀取訓練集
1.1 讀取給定有影象訓練集,遍歷得到訓練集的種類。如果之前沒有訓練過,按訓練集的種類新建相關的資料夾。
1.2 從訓練集中隨機提取一張影象做為模板,並重命名成與影象內容相關的檔名,儲存。
1.3 遍歷模板資料夾,把相關的模板影象存到容器裡。
程式碼示例:
//建立相關的資料夾 void categorizer::initFolder(QString path) { QStringList rootFolder; rootFolder <<"SVM_file" << "Data_TXT" << "template_image" << "result_image"<<"test_image"; createFolder(path,rootFolder); QStringList cateFolder = getFolder(path+"train_images"); for(int i = 0;i < cateFolder.size(); i++) { QStringList fileList = getFileNames(path+"train_images/"+cateFolder.at(i)); if(!QFile::exists(path+"template_image/"+cateFolder.at(i)+".jpg")) { QFile::copy(path+"train_images/"+cateFolder.at(i)+"/"+fileList.at(1), path+"template_image/"+cateFolder.at(i)+".jpg"); QFile::remove(path+"train_images/"+cateFolder.at(i)+"/"+fileList.at(1)); } else { qDebug() << "當前檔案已存在"; } } }
//開始遍歷模板資料資料夾
QStringList fileList = traversalFolderImage(TEMPLATE_FOLDER);
if(fileList.empty())
{
qDebug() << "模板目錄為空!" ;
return;
}
for(int i = 0; i < fileList.count(); i++)
{
QString fileName = fileList.at(i);
//獲取字元在字串中的位置
int k = fileName.indexOf('.',1);
//獲取字串某位置的值
QString str = fileName.mid(0,k);
string className = str.toStdString();
//讀入模板圖片
Mat image = imread(TEMPLATE_FOLDER+fileName.toStdString());
//儲存模板圖片
sm_result_objects[className] = image;
}
2.構造訓練集
2.1 遍歷訓練集資料夾下的所有影象檔案,儲存multimap容器。
2.2 multimap的儲存方式如下圖:
程式碼部分:
//Subdirectories列出所有子目錄中的條目
QDirIterator iter(TRAIN_FOLDER, QDirIterator::Subdirectories);
//如果目錄中至少有一個條目,則返回true,否則將返回false。
while (iter.hasNext())
{
iter.next();
//返回當前目錄下檔案的檔名,不帶路徑
QFileInfo info = iter.fileInfo();
//判斷是否是目錄
if(info.isDir())
{
//下層目錄或者下下層目錄,跳出迴圈
if(info.fileName()=="." || info.fileName() == "..")
{
continue;
}
//得到檔名並轉成C++字串
categor = info.fileName().toStdString();
//加到容器中
s_category_name.push_back(categor);
}
else
{
//得到絕對路徑名
QString file_name = info.absoluteFilePath();
//轉成C++字串
string file_name_std = file_name.toStdString();
//讀取圖片,以灰度影象讀取
Mat temp = imread(file_name_std,CV_LOAD_IMAGE_GRAYSCALE);
//將資料夾名與資料夾下的圖片名的形式組合儲存
pair<string,Mat> p(categor,temp);
i++;
qDebug() <<"開始讀取訓練集的第"<<i<<"張圖片......";
//加到multimap,鍵名可重複出現
sm_trains_set.insert(p);
}
}
i_categories_size = s_category_name.size();
3.提取訓練集裡每張影象的特徵,得出詞典
3.1 提取每張影象的特徵點。
3.2 得到特徵點描述符。
3.3 把特徵描述符儲存到一個Mat容器當中去,此時一張影象的特徵描述符在Mat容器中佔一行。
3.4 聚類得到聚類之後的檔案並儲存。
程式碼示例:
//進行資料庫儲存的初始化,以可讀的方式
FileStorage vacab_fs(DATA_FOLDER"surf_vocab.xml",FileStorage::READ);
//之前已生成好的詞典,不用重新須做聚類計算
//檢查檔案是否已經開啟
if(vacab_fs.isOpened())
{
qDebug()<< "圖片的聚類詞典已經存在.....";
//儲存或讀取操作完成後,需要關閉檔案並釋放快取
vacab_fs.release();
}
else
{
Mat m_vocab_descriptors;
//對於每一幅模板,提取SURF運算元,存入到vocab_descriptors中
//定義一個迭代器
multimap<string,Mat>::iterator i = sm_trains_set.begin();
//開始遍歷圖片
for(;i!=sm_trains_set.end();i++)
{
//定義關鍵點
vector<KeyPoint> key_points;
//讀取圖片
Mat m_temp_iamge = (*i).second;
//detect函式檢測SURF/SIFT特徵的關鍵點,並儲存在vector容器中,最後使用drawKeypoints函式繪製出特徵點。
s_feature_decter->detect(m_temp_iamge,key_points,Mat());
Mat m_descrip;
//得到特徵描述符
s_descriptor_extractor->compute(m_temp_iamge,key_points,m_descrip);
/*Mat的每一行都是一個影象提取到的特徵點*/
m_vocab_descriptors.push_back(m_descrip);
}
qDebug() << "訓練圖片開始聚類......";
//增加描述符到訓練集
bowtrainer->add(m_vocab_descriptors);
//根據訓練集進行聚類
m_vocab = bowtrainer->cluster();
qDebug() << "聚類完畢,得出詞典......";
//開啟檔案進行寫操作
FileStorage file_stor(DATA_FOLDER"surf_vocab.xml",FileStorage::WRITE);
file_stor<<"vocabulary"<<m_vocab;
file_stor.release();
}