首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

OpenCV + SVM 手寫數字識別完整實現:從數據預處理到模型訓練預測

     編輯:關於C語言

OpenCV + SVM 手寫數字識別完整實現:從數據預處理到模型訓練預測

前言

本文基於 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;

三、關鍵預處理:ROI 擷取與歸一化

1. ROI 自動擷取 (去除邊框空白)


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));  // 居中粘貼
}

2. 特徵歸一化 (關鍵步驟)


// 訓練階段:每個樣本單獨歸一化
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 對特徵尺度敏感,歸一化消除像素強度差異帶來的主導權不平衡。

四、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;
}

五、MNIST 測試集評估


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;
}

六、關鍵超參數說明

參數本例數值作用調優建議
KernelRBF非線性映射核函數嘗試 LINEAR/POLY/SIGMOID
C10.0懲罰係數,控制過擬合網格搜索 0.1~1000
Gamma8.0RBF 核寬度網格搜索 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

八、結果分析與改進方向

  • 基線準確率:MNIST 測試集約 92-95% (取決於超參數)
  • 改進方向:
    1. 特徵工程:HOG 特徵、邊緣檢測、骨架化
    2. 模型升級:CNN (LeNet-5)、隨機森林、KNN
    3. 數據增強:旋转、平移、噪聲注入擴充訓練集
    4. 集成學習:多模型投票/堆疊
  • 工程化注意:模型文件版本管理、預處理流程封裝為庫、異常輸入檢測

九、總結

本文提供了基於 OpenCV SVM 的手寫數字識別完整工程實現,從數據讀取、ROI 預處理、特徵歸一化到模型訓練評估全流程可運行。SVM 作為經典機器學習算法,在小樣本、高維稀疏特徵場景下表現優異。現代項目中常作為基線模型或集成學習的基學習器使用。掌握此流程對理解機器學習工程化落地具有重要參考價值。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved