在ubuntu16.04+opencv3.0環境下使用mnist手寫體資料集編寫相關程式
阿新 • • 發佈:2019-01-23
因為最近可能做專案需要,因此搜尋了手寫體數字檢測部落格,在查看了大量部落格後總結了一些自己的學習小體會。但是但是-----敲黑板、劃重點了。呵呵,就是還是先把參考的幾篇好部落格的分享給大家。 http://www.itnose.net/detail/6525586.html,這篇文章裡面最可取的就是分析了mnist資料集存在的坑。很多坑我也不是太明白,哈哈。 http://blog.csdn.net/xuan_zizizi/article/details/71102018,這篇部落格,對就是這篇,上一個網址我就是在這裡找到的。所以這篇部落格挺好的。 首先介紹下mnist資料集吧,這個資料集至少在現在看來在手寫體方面使用的還是比較多的。下載它可以在這裡`http://yann.lecun.com/exdb/mnist/`,開啟後你可以看到四個可以下載的包,但是你會發現網傳的訓練60000張圖片,測試10000張圖片,下載下來的包怎麼這麼小,解壓下來一看,我靠居然是二進位制檔案。不著急,因為很多前輩已經給了很多詳細的參考,特別是在http://www.itnose.net/detail/6525586.html下,寫的非常詳細,如果感興趣可以認真看下。 有3個函式需要先貼出來. 1.大小端轉換`int reverseInt(int i)
{
unsigned char c1, c2, c3, c4;
c1 = i & 255;
c2 = (i >> 8) & 255;//>>表示右移
c3 = (i >> 16) & 255;
c4 = (i >> 24) & 255;
return ((int)c1 << 24) + ((int)c2 << 16) + ((int)c3 << 8) + c4;//左移
}`
因為MNIST是大端儲存,然而大部分的Intel處理器都是小端儲存,所以對於int、long、float這些多位元組的資料型別,就要一個一個byte地翻轉過來,才能正確顯示。
2.讀取訓練圖片
Mat read_mnist_image(const char* fileName)
{
int magic_number = 0;
int number_of_images = 0;
int n_rows = 0;
int n_cols = 0;
Mat DataMat;
ifstream file;
file.open(fileName, std::ifstream::binary);
//fstream file;//(fileName, ios_base::binary);
//file.open(fileName, ios_base::binary);
if (file.is_open())
{
cout << "成功開啟影象集\n";
file.read((char*)&magic_number, sizeof(magic_number));
file.read((char*)&number_of_images, sizeof(number_of_images));
file.read((char*)&n_rows, sizeof(n_rows));
file.read((char*)&n_cols, sizeof(n_cols));
cout << "magic_number=/n"<<magic_number << "number_of_images=\n " << number_of_images << " " << n_rows << " " << n_cols << endl;
magic_number = reverseInt(magic_number);
number_of_images = reverseInt(number_of_images);
n_rows = reverseInt(n_rows);
n_cols = reverseInt(n_cols);
cout << "MAGIC NUMBER = " << magic_number<< " ;NUMBER OF IMAGES = " << number_of_images<< " ; NUMBER OF ROWS = " << n_rows << " ; NUMBER OF COLS = " << n_cols << endl;
//-test-
//輸出第一張和最後一張圖,檢測讀取資料無誤
Mat s = Mat::zeros(n_rows, n_rows * n_cols, CV_32FC1);
Mat e = Mat::zeros(n_rows, n_rows * n_cols, CV_32FC1);
cout << "開始讀取Image資料......\n";
DataMat = Mat::zeros(number_of_images, n_rows * n_cols, CV_32FC1);
for (int i = 0; i < number_of_images; i++)
{
for (int j = 0; j < n_rows * n_cols; j++)
{
unsigned char temp = 0;
file.read((char*)&temp, sizeof(temp));
float pixel_value = float((temp + 0.0) / 255.0);
DataMat.at<float>(i, j) = pixel_value;
//列印第一張和最後一張影象資料
if (i == 0)
{
s.at<float>(j / n_cols, j % n_cols) = pixel_value;
}
else if (i == number_of_images - 1)
{
e.at<float>(j / n_cols, j % n_cols) = pixel_value;
}
}
}
//imshow("first image", s);
//cout << "first image = " << s << endl;
//imshow("last image", e);
waitKey(0);
}
file.close();
return DataMat;
}
3.讀取標籤
Mat read_mnist_label(const char* fileName)
{
int magic_number;
int number_of_items;
Mat LabelMat;
ifstream file;
file.open(fileName, std::ifstream::binary);
//fstream file;//(fileName, ios_base::binary);
//file.open(fileName, ios_base::binary);
if (file.is_open())
{
cout << "成功開啟標籤\n";
file.read((char*)&magic_number, sizeof(magic_number));
file.read((char*)&number_of_items, sizeof(number_of_items));
magic_number = reverseInt(magic_number);
number_of_items = reverseInt(number_of_items);
cout << "MAGIC NUMBER = " << magic_number << " ; NUMBER OF ITEMS = " << number_of_items << endl;
//-test-
//number_of_items = testNum;
//記錄第一個label和最後一個label
unsigned int s = 0, e = 0;
cout << "開始讀取Label資料......\n";
LabelMat = Mat::zeros(number_of_items, 1, CV_32SC1);
for (int i = 0; i < number_of_items; i++)
{
unsigned char temp = 0;
file.read((char*)&temp, sizeof(temp));
LabelMat.at<unsigned int>(i, 0) = (unsigned int)temp;
//列印第一個和最後一個label
if (i == 0) s = (unsigned int)temp;
else if (i == number_of_items - 1) e = (unsigned int)temp;
}
cout << "first label = " << s << endl;
cout << "last label = " << e << endl;
}
file.close();
return LabelMat;
}
其中在讀圖片和讀標籤函式中做了一點修改,不知道是因為什麼原因,當把檔名以string 型別賦值的時候,file.open()一直報錯,所以最後只能改成char testImage[] = “t10k-images.idx3-ubyte”。
最後就是在主函式中進行訓練資料集和測試資料集。
int main()
{
//讀取訓練資料
Mat trainData;
Mat labels;
trainData = read_mnist_image(trainImage);
labels = read_mnist_label(trainLabel);
cout << " 訓練資料行數:" << trainData.rows << "訓練資料列數:" << trainData.cols << endl;
cout << " 訓練標籤行數:" << labels.rows << "訓練資料列數: " << labels.cols << endl;
cout << "訓練資料讀取完成" << endl;
//訓練引數
Ptr<SVM> svm = SVM::create();
svm->setType(SVM::C_SVC);
svm->setKernel(SVM::RBF);
svm->setGamma(0.01);
svm->setC(10.0);
svm->setTermCriteria(TermCriteria(CV_TERMCRIT_EPS, 1000, FLT_EPSILON));
cout << "引數設定完成" << endl;
//訓練分類器
cout << "開始訓練分類器" << endl;
svm->train(trainData, ROW_SAMPLE, labels);
cout << "分類器訓練完成" << endl;
//儲存訓練器
svm->save("./mnist_svm.xml");
cout << "save as ./mnist_svm.xml" << endl;
//下載分類器
//cout << "開始匯入SVM檔案...\n";
//Ptr<SVM> svm1 = StatModel::load<SVM>("mnist_dataset/mnist_svm.xml");
//cout << "成功匯入SVM檔案...\n";
//讀取測試資料
Mat testData;
Mat tLabel;
testData = read_mnist_image(testImage);
tLabel = read_mnist_label(testLabel);
cout << "測試資料讀取完成" << endl;
float count = 0;
for (int i = 0; i < testData.rows; i++)
{
Mat sample = testData.row(i);
float res=0.0;
res=svm->predict(sample);
res = abs(res - tLabel.at<unsigned int>(i, 0)) <= FLT_EPSILON ? 1.f : 0.f;
count += res;
}
cout << "正確的識別個數: " << count << endl;
cout << "錯誤率為:" << (10000 - count + 0.0) / 10000 * 100.0 << "%\n";
system("pause");
return 0;
}
那麼最後程式成功執行完成後就會在build資料夾或者專案的根目錄下存在一個mnist_svm.xml。這就是我們訓練出來的模型了。以後需要預測手寫體數字直接載入這個模型就可以了,不用每次都訓練資料集。
好了這就是訓練模型的程式了。
-------------------------------我是分割線
哈哈,但是此時,如果你要讀取自己的視訊或者本地圖片進行預測,你會發現很多坑的,哈哈。
例如預測結果......en ,都是些什麼玩意兒。完全對不上,說好準確率98%以上的,結果錯誤率。具體原因,請聽下回分解。
(mnisit讀出來的圖片是黑底白字)。