Opencv学习之路—Opencv下基于HOG特征的KNN算法分类训练

在计算机视觉研究当中,HOG算法和LBP算法算是基础算法,但是却十分重要。后期很多图像特征提取的算法都是基于HOG和LBP,所以了解和掌握HOG,是学习计算机视觉的前提和基础。

HOG算法的原理很多资料都可以查到,简单来说,就是将图像分成一个cell,通过对每个cell的像素进行梯度处理,进而根据梯度方向和梯度幅度来得到cell的图像特征。随后,将每个cell的图像特征连接起来,得到一个BLock的特征,进而得到一张图片的特征。Opencv当中自带HOG算法,可以直接调用,进行图像的特征提取。但是作为一个初学者,自然应该自己手写一下HOG算法,这样能够更加透彻地去理解。

下面是我自己写的HOG,代码比较粗糙,为了适应下面的KNN分类器,HOG算法的接口设计为输入一张图片,返回一个vector向量。

class HOG{
private:
    Mat img;
public:
    vector<float>bins;                                //返回一个图片的HOG特征;
    void GetImage(Mat src);
    void Cut_to_Block();                               //将图片分割成一个个Block;
    void Cut_to_Cell(int pixel_x, int pixel_y);        //将图片分割成一个个Cell;
    void Cell_to_bin(int x, int y);                    //对每个Cell进行处理,得到每个Cell的bins;
};

void HOG::GetImage(Mat src){
    bins.clear();
    cvtColor(src, img, COLOR_RGB2GRAY);
    Cut_to_Block();
}

void HOG::Cut_to_Block(){
    for (int i = 1; i <= img.rows - 17; i = i + 8){
        for (int j = 1; j <= img.cols - 17; j = j + 8){
            Cut_to_Cell(i, j);
        }
    }
}

void HOG::Cut_to_Cell(int pixel_x, int pixel_y){
    for (int i = pixel_x, m = 0; m < 2; i = i + 8, m++){
        for (int j = pixel_y, n = 0; n < 2; j = j + 8, n++){
            Cell_to_bin(i, j);
        }
    }
}

void HOG::Cell_to_bin(int x, int y){
    int pixel_x;                  //cell的像素的起始位置行坐标;
    int pixel_y;                  //cell的像素的起始位置纵坐标;
    float pixel[10][10];         //我们一般默认cell为8*8的像素大小,但是为了储存周边店的像素,需要多加两个像素储存点的位置;
    float gradient_M[9][9];            //保存梯度的幅值;
    float gradient_Angle[9][9];        //保存像素梯度的方向;
    float gradient_h[9][9];
    float gradient_v[9][9];
    float bin[9];                    //存放一个Cell当中的bins值;

    pixel_x = x;
    pixel_y = y;

    //为了计算方便,我们将每个Cell的像素先提取出来,存放在pixel[][]当中;
    for (int i = pixel_x - 1, m = 0; i < pixel_x + 9; i++, m++){
        uchar *data = img.ptr<uchar>(i);
        for (int j = pixel_y - 1, n = 0; j < pixel_y + 9; j++, n++){
            pixel[m][n] = data[j];
        }
    }

    //计算每个像素的梯度幅值和梯度角度;
    for (int i = 1; i<9; i++){
        for (int j = 1; j<9; j++){
            gradient_h[i][j] = pixel[i + 1][j] - pixel[i - 1][j];
            gradient_v[i][j] = pixel[i][j + 1] - pixel[i][j - 1];
            gradient_M[i][j] = sqrt(gradient_h[i][j] * gradient_h[i][j] + gradient_v[i][j] * gradient_v[i][j]);
            gradient_Angle[i][j] = atan2(gradient_h[i][j], gradient_v[i][j]) * 180;
        }
    }

    //根据每个像素的幅值进行维度的区分分类;
    for (int i = 0; i<9; i++){
        bin[i] = 0;
    }

    for (int i = 1; i<9; i++){
        for (int j = 1; j<9; j++){
            if ((gradient_Angle[i][j] >= 0 && gradient_Angle[i][j]<20) || (gradient_Angle[i][j] >= 180 && gradient_Angle[i][j]<200)){
                bin[0] = bin[0] + gradient_M[i][j];
            }
            if ((gradient_Angle[i][j] >= 20 && gradient_Angle[i][j]<40) || (gradient_Angle[i][j] >= 200 && gradient_Angle[i][j]<220)){
                bin[1] = bin[1] + gradient_M[i][j];
            }
            if ((gradient_Angle[i][j] >= 40 && gradient_Angle[i][j]<60) || (gradient_Angle[i][j] >= 220 && gradient_Angle[i][j]<240)){
                bin[2] = bin[2] + gradient_M[i][j];
            }
            if ((gradient_Angle[i][j] >= 60 && gradient_Angle[i][j]<80) || (gradient_Angle[i][j] >= 240 && gradient_Angle[i][j]<260)){
                bin[3] = bin[3] + gradient_M[i][j];
            }
            if ((gradient_Angle[i][j] >= 80 && gradient_Angle[i][j]<100) || (gradient_Angle[i][j] >= 260 && gradient_Angle[i][j]<280)){
                bin[4] = bin[4] + gradient_M[i][j];
            }
            if ((gradient_Angle[i][j] >= 100 && gradient_Angle[i][j]<120) || (gradient_Angle[i][j] >= 280 && gradient_Angle[i][j]<300)){
                bin[5] = bin[5] + gradient_M[i][j];
            }
            if ((gradient_Angle[i][j] >= 120 && gradient_Angle[i][j]<140) || (gradient_Angle[i][j] >= 300 && gradient_Angle[i][j]<320)){
                bin[6] = bin[6] + gradient_M[i][j];
            }
            if ((gradient_Angle[i][j] >= 140 && gradient_Angle[i][j]<160) || (gradient_Angle[i][j] >= 320 && gradient_Angle[i][j]<340)){
                bin[7] = bin[7] + gradient_M[i][j];
            }
            if ((gradient_Angle[i][j] >= 160 && gradient_Angle[i][j] <= 180) || (gradient_Angle[i][j] >= 340 && gradient_Angle[i][j] <= 360)){
                bin[8] = bin[8] + gradient_M[i][j];
            }
        }
    }

    //归一化;
    float sum_bin = 0;
    for (int i = 0; i<9; i++){
        sum_bin = sum_bin + bin[i];
    }
    for (int i = 0; i<9; i++){
        bin[i] = bin[i] / sum_bin;
        if (bin[i]>0.2){
            bin[i] = 0.2;
        }
    }
    sum_bin = 0;
    for (int i = 0; i<9; i++){
        sum_bin = sum_bin + bin[i];
    }
    for (int i = 0; i<9; i++){
        bin[i] = bin[i] / sum_bin;
    }

    //返回bin[]的值到bins向量当中;
    for (int i = 0; i < 9; i++){
        bins.push_back(bin[i]);
    }
}

写完了HOG算法,下面就开始写KNN 分类器了。KNN算法很容易理解,就是在一个元素周围选取最邻近的K个元素,然后分析这k个元素当中,哪一类占的比例最大,那么这个元素就属于该类。

同样Opencv当中也有KNN算法,为类CvKNearest(),直接调用便可以进行训练,具体地可以查阅相关文档。

class KNN{
private:
    vector < vector < float >> datatrain;
    vector<int> dataclass;
    CvKNearest *knn;
public:
    KNN();
    //对从HOG算法传递出来的数据进行整合处理,src表示一张图的HOG特征数组,classfile表示这张图所代表的分类;
    void Data_integration(vector<float> src, int classfile);
    void KNN_Train();                      //将HOG得到的数据进行相关处理,然后进行KNN训练;
    int KNN_Test(vector<float> src);      //将KNN训练好之后,传入一个HOG特征值,返回一个分类;
};

KNN::KNN(){
    knn=new CvKNearest();
}

void KNN::Data_integration(vector<float> src, int classfile){
    datatrain.push_back(src);
    dataclass.push_back(classfile);
}

void KNN::KNN_Train(){
    CvMat *DataTrain=cvCreateMat(390,900,CV_32FC1);
    CvMat *DataClass=cvCreateMat(390,1,CV_32FC1);
    for(int i=0;i<390;i++){
        cvmSet(DataClass,i,0,dataclass[i]);
        for(int j=0;j<900;j++){
            cvmSet(DataTrain,i,j,datatrain[i][j]);
        }
    }
    knn->train(DataTrain,DataClass,0,false,30,false);
}

int KNN::KNN_Test(vector<float> src){
    CvMat *DataSample = cvCreateMat(1, 900, CV_32FC1);
    for (int i = 0; i < 900; i++){
        cvmSet(DataSample,0,i,src[i]);
    }
    int k;
    k = (int)knn->find_nearest(DataSample, 30);
    return k;
}

有个HOG 和 KNN,那现在就可以进行训练了。我有了13类车牌图片进行训练,每类30张。

在这里,有一个东西要注意一下,那就是批量读取图片。我采用了一个很笨的方法,那就是把每张图片的地址存在一个txt文档当中,然后先读取地址,然后在读取图片。这样的方法,在图片数量较少的情况下可以使用的,但是图片数量成千上百张,就很麻烦了。

int main(){
    HOG Hog;
    KNN Knn;
    string Imageadress[390];
    ifstream fin("train.txt");           //图片地址事先保存在train.txt文件当中;
    for (int i = 0; i<390; i++){
        getline(fin, Imageadress[i]);    //从文件当中一行一行读出地址,保存到Imgaeadree当中;
    }

    Mat Image[390];
    for (int i = 0; i < 390; i++){
        Image[i] = imread(Imageadress[i], 1);   //读入图片文件;
    }

    for (int i = 0; i < 390; i++){
        int k = 0;
        k = i / 30;             //通过整除30,来获得该图片属于哪个分类当中的;
        Hog.GetImage(Image[i]);
        Knn.Data_integration(Hog.bins, k);
    }
    Knn.KNN_Train();

    //进行检测;
    ifstream testin("test4.txt");
    string testImageadress[70];
    Mat testimg[70];
    for(int i=0;i<70;i++)
    {
        getline(testin,testImageadress[i]);
    }

    for(int i=0;i<70;i++){
        testimg[i]=imread(testImageadress[i],1);
    }

    int count=0;
    for(int i=0;i<70;i++){
        int k;
        Hog.GetImage(testimg[i]);
        k=Knn.KNN_Test(Hog.bins);
        cout<<k<<endl;
        if(k!=3){
            count++;
        }
    }
    cout<<"错误的数量:"<<count<<endl;
}

训练完毕之后,我又使用13类图片,每类70张,进行检测分类。

很不幸,识别结果不是很理想,奔驰等简单的车牌识别率很高,可以达到百分之百,但是复杂的车牌识别率就瞬间下来了,当中的原因,是因为HOG算法写得有问题啊,不够好,需要改进。

时间: 2024-10-07 06:33:17

Opencv学习之路—Opencv下基于HOG特征的KNN算法分类训练的相关文章

Opencv学习之路——自己编写的HOG算法

#include<opencv2\core\core.hpp> #include<opencv2\highgui\highgui.hpp> #include<opencv2\opencv.hpp> #include<iostream> #include<fstream> using namespace std; using namespace cv; #define Max 100 class Cell{ private: int pixel_x

opencv学习笔记(七)SVM+HOG

opencv学习笔记(七)SVM+HOG 一.简介 方向梯度直方图(Histogram of Oriented Gradient,HOG)特征是一种在计算机视觉和图像处理中用来进行物体检测的特征描述子.它通过计算和统计图像局部区域的梯度直方图来构成特征.Hog特征结合SVM分类器已经被广泛用于图像识别中,尤其在行人检测中获得了极大的成功.需要提醒的是,HOG+SVM进行行人检测的方法是法国研究院Dalal在2005的CVPR上提出的. 最近在做车标识别相关的研究,用到了SVM+HOG的方法进行识

QT+opencv学习笔记一 opencv配置及Mat浅拷贝

今天终于把Qt + opencv配置成功了,中间有一些曲折,在这里记录一下. vs2013 + opencv的方法之前记录过,但这次的不太一样,我们一开始按照这篇文章配置pro Qt5中进行OpenCV开发教程 但是,死活出不来结果,研究发现,我们用的是 mingw 不是 vs,这个方法适合vs编译器 (具体可以参考:win7下的Qt环境+OpenCV视觉库) 后来,我们找到了这篇文章 QT creator+OpenCV2.4.2+MinGW 在windows下开发环境配置 但是还是有点曲折,

OpenCV学习笔记一 OpenCV 2.49 + Eclipse 配置教程

1.安装opencv库 1.1 双击opencv-2.4.9.exe,安装到指定目录. 1.2安装VC2010运行库,百度搜索VC2010运行库,下载安装即可.如果完成下面的所有配置之后运行程序时提示还缺少dll等文件,需要用360修复,进入人工服务,搜索提示缺少的dll文件,点击修复即可. 1.3将opencv\build\x86\vc11路径下的bin.lib.staticlib三个文件夹的路径添加到环境变量中. 2.配置Eclipse 2.1 新建一个工程file -> new -> c

opencv学习之路(41)、人脸识别

一.人脸检测并采集个人图像 //take_photo.cpp #include<opencv2/opencv.hpp> using namespace cv; using namespace std; void take_photo() { VideoCapture cap(0); //打开摄像头 if (!cap.isOpened()) return; //加载级联检测器 CascadeClassifier cascade; cascade.load("F:/opencv3.2/R

opencv学习之路(5)、鼠标和滑动条操作

一.鼠标事件 1 #include<opencv2/opencv.hpp> 2 #include<iostream> 3 using namespace cv; 4 using namespace std; 5 6 Mat img(500,500,CV_8UC3,Scalar(255,255,255));//定义成全局变量 7 8 void OnMouse(int event,int x,int y,int flags,void* param){ 9 if(event==CV_EV

opencv学习之路(6)、鼠标截图,滑动条播放视频

一.鼠标截图 1 #include<opencv2/opencv.hpp> 2 #include<iostream> 3 using namespace cv; 4 using namespace std; 5 6 Mat img=imread("E://1.jpg"); 7 Mat temp=img.clone(); 8 Mat ROI; 9 Point pt; 10 bool flag=false; 11 12 void onMouse(int event,

opencv学习之路(2)、读取视频,读取摄像头

一.介绍 视频读取本质上就是读取图像,因为视频是由一帧一帧图像组成的.1秒24帧基本就能流畅的读取视频了. ①读取视频有两种方法: A. VideoCapture cap; cap.open("1.avi"); B. VideoCapture cap("1.avi"); ②循环显示每一帧: while(1) { Mat frame; //定义Mat变量,用来存储每一帧 cap>>frame; //读取当前帧方法一 //cap.read(frame); /

opencv学习之路(24)、轮廓查找与绘制(三)——凸包

一.简介 二.绘制点集的凸包 1 #include<opencv2/opencv.hpp> 2 using namespace cv; 3 4 void main() 5 { 6 //---绘制点集的凸包 7 Mat img(400, 400, CV_8UC3, Scalar::all(0)); //定义绘制图像 8 RNG rng; //定义随机数对象 9 while(1) 10 { 11 char key; 12 int count = (unsigned int)rng % 100; /