本文基於 OpenCV 機器學習模塊實現手寫數字識別系統,涵蓋 ROI 提取、特徵歸一化、SVM 訓練/預測、MNIST 測試集評估全流程。代碼採用 C++ 實現,可直接編譯運行。
訓練階段: 圖片讀入 → ROI 擷取 → 縮放 8×8 → 特徵向量化 (64 維) → 歸一化 → SVM 訓練 → 模型保存 預測階段: 測試圖片/ MNIST 二進制 → 同樣預處理 → SVM 預測 → 準確率統計
class NumTrainData {
public:
NumTrainData() { memset(data, 0, sizeof(data)); result = -1; }
float data[64]; // 8×8 = 64 維特徵向量
int result; // 標籤 (0-9)
};
std::vector buffer; // 訓練樣本容器
const int featureLen = 64;
void GetROI(cv::Mat& src, cv::Mat& dst) {
int left = src.cols, right = 0, top = src.rows, bottom = 0;
// 尋找非零像素邊界
for (int i = 0; i < src.rows; i++) {
for (int j = 0; j < src.cols; j++) {
if (src.at(i, j) > 0) {
left = std::min(left, j);
right = std::max(right, j);
top = std::min(top, i);
bottom = std::max(bottom, i);
}
}
}
int width = right - left;
int height = bottom - top;
int len = std::max(width, height); // 正方形裁剪
dst = cv::Mat::zeros(len, len, CV_8UC1);
cv::Rect srcRect(left, top, width, height);
cv::Rect dstRect((len-width)/2, (len-height)/2, width, height);
src(srcRect).copyTo(dst(dstRect)); // 居中粘貼
}
// 訓練階段:每個樣本單獨歸一化
cv::Mat m = cv::Mat(1, featureLen, CV_32FC1);
memcpy(m.data, td.data, featureLen * sizeof(float));
cv::normalize(m, m); // L2 歸一化:向量模長 = 1
// 預測階段:同樣歸一化
cv::normalize(m, m);
為何歸一化? SVM 對特徵尺度敏感,歸一化消除像素強度差異帶來的主導權不平衡。
void newSvmStudy(std::vector& trainData) {
int testCount = trainData.size();
cv::Mat data(testCount, featureLen, CV_32FC1);
cv::Mat res(testCount, 1, CV_32SC1);
// 1. 構建訓練矩陣
for (int i = 0; i < testCount; i++) {
cv::Mat m(1, featureLen, CV_32FC1, trainData[i].data);
cv::normalize(m, m);
m.copyTo(data.row(i));
res.at(i, 0) = trainData[i].result;
}
// 2. 配置 SVM 參數 (RBF 核)
cv::Ptr svm = cv::ml::SVM::create();
svm->setType(cv::ml::SVM::C_SVC);
svm->setKernel(cv::ml::SVM::RBF);
svm->setGamma(8.0); // RBF 核寬度參數
svm->setC(10.0); // 懲罰係數
svm->setTermCriteria(cv::TermCriteria(cv::TermCriteria::EPS, 1000, FLT_EPSILON));
// 3. 訓練並保存
svm->train(data, cv::ml::ROW_SAMPLE, res);
svm->save("SVM_DATA.xml");
std::cout << "SVM 訓練完成,模型已保存" << std::endl;
}
int newSvmPredict() {
cv::Ptr svm = cv::ml::SVM::load("SVM_DATA.xml");
// 讀取 MNIST 二進制格式 (需下載 t10k-images.idx3-ubyte, t10k-labels.idx1-ubyte)
std::ifstream ifs("../res/t10k-images.idx3-ubyte", std::ios::binary);
std::ifstream lab_ifs("../res/t10k-labels.idx1-ubyte", std::ios::binary);
// 跳過文件頭 (魔數、數量、行、列)
char magic[4], count[4], rows[4], cols[4];
ifs.read(magic, 4); ifs.read(count, 4); ifs.read(rows, 4); ifs.read(cols, 4);
lab_ifs.read(magic, 4); lab_ifs.read(count, 4);
// 大端序轉換
auto swap32 = [](char* p) {
std::swap(p[0], p[3]); std::swap(p[1], p[2]);
};
swap32(count); swap32(rows); swap32(cols);
int n = *reinterpret_cast(count);
int r = *reinterpret_cast(rows);
int c = *reinterpret_cast(cols);
cv::Mat src(r, c, CV_8UC1);
cv::Mat temp(8, 8, CV_8UC1);
cv::Mat m(1, featureLen, CV_32FC1);
int right = 0, total = 0;
char label;
while (ifs.good() && total < n) {
lab_ifs.read(&label, 1);
label += '0'; // ASCII 轉換
ifs.read(reinterpret_cast(src.data), r * c);
GetROI(src, dst);
cv::resize(dst, temp, temp.size());
// 填充特徵向量
for (int i = 0; i < 8; i++)
for (int j = 0; j < 8; j++)
m.at(0, i*8+j) = temp.at(i, j);
cv::normalize(m, m);
int pred = static_cast(svm->predict(m));
if (pred == label - '0') right++;
total++;
if (total % 1000 == 0)
std::cout << "Processed: " << total << ", Acc: " << 1.0*right/total << std::endl;
}
std::cout << "最終準確率: " << 1.0*right/total << " (" << right << "/" << total << ")" << std::endl;
return 0;
}
| 參數 | 本例數值 | 作用 | 調優建議 |
|---|---|---|---|
| Kernel | RBF | 非線性映射核函數 | 嘗試 LINEAR/POLY/SIGMOID |
| C | 10.0 | 懲罰係數,控制過擬合 | 網格搜索 0.1~1000 |
| Gamma | 8.0 | RBF 核寬度 | 網格搜索 0.01~100 |
| 特徵維度 | 64 (8×8) | 輸入向量大小 | 可嘗試 16×16=256 維 |
| 歸一化 | L2 | 特徵尺度統一 | 必須與訓練一致 |
# CMakeLists.txt 關鍵部分
find_package(OpenCV REQUIRED)
add_executable(svm_digit main.cpp)
target_link_libraries(svm_digit ${OpenCV_LIBS})
# 編譯
mkdir build && cd build
cmake .. && make
# 運行訓練
./svm_digit # 預設 ON_STUDY=1 訓練模式
# 運行預測
# 修改代碼 #define ON_STUDY 0 重新編譯
./svm_digit
本文提供了基於 OpenCV SVM 的手寫數字識別完整工程實現,從數據讀取、ROI 預處理、特徵歸一化到模型訓練評估全流程可運行。SVM 作為經典機器學習算法,在小樣本、高維稀疏特徵場景下表現優異。現代項目中常作為基線模型或集成學習的基學習器使用。掌握此流程對理解機器學習工程化落地具有重要參考價值。