opencv-PCA主要成分分析

资料:https://www.cnblogs.com/xyf327/p/14824106.html    

OpenCV3.3中给出了主成分分析(Principal Components Analysis, PCA)的实现,即cv::PCA类

PCA在opencv项目中的应用:

  • 获取物体主要方向(形心)
  • 对数据集降维处理

(1)、cv::PCA::PCA:构造函数;

(2)、cv::PCA::operator():函数调用运算符;

(3)、cv::PCA::project:将输入数据投影到PCA主成分空间;

(4)、cv::PCA::backProject:重建原始数据;

(5)、cv::PCA::write:将特征值、特征向量、均值写入指定的文件;

(6)、cv::PCA::read:从指定文件读入特征值、特征向量、均值;

(7)、cv::PCA::eigenvectors:协方差矩阵的特征向量;

(8)、cv::PCA::eigenvalues:协方差矩阵的特征值;

(9)、cv::PCA::mean:均值

特征向量  特征值

4.png

 

 

#include<opencv2/opencv.hpp>
#include<iostream>
#include  <vector>



int main(int argc, char** argv) {

    double getOrientation(std::vector<cv::Point> &pts, cv::Mat & img);
    cv::Mat src = cv::imread("D:/bb/tu1/4.png");
    cv::Mat gray, binary;
    cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);
    cv::threshold(gray, binary, 150, 255, cv::THRESH_BINARY);//阈值处理
    std::vector<std::vector<cv::Point> > contours;
    std::vector<cv::Vec4i> hierarchy;
    findContours(binary, contours, hierarchy, cv::RETR_LIST, cv::CHAIN_APPROX_NONE);//寻找轮廓坐标
    for (int i = 0; i < contours.size(); ++i) {
        double area = contourArea(contours[i]);
        if (area < 1e2 || 1e4 < area) continue;//去除过小或者过大的轮廓区域(科学计数法表示le2表示1X10的2次方)
        cv::drawContours(src, contours, i, cv::Scalar(0, 0, 255), 2, 8, hierarchy, 0);//画出轮廓

        double angle = getOrientation(contours[i], src);
        std::cout << angle << std::endl;
    }

    cv::imshow("结果", src);
    cv::waitKey(0);
    return 0;
}


double getOrientation(std::vector<cv::Point>& pts, cv::Mat& img) {
    cv::Mat data_pts = cv::Mat(pts.size(), 2, CV_64FC1);//使用mat来保存指定轮廓坐标点数据,也是为了后面pca处理需要
    //创建contours[i].size()行2列的矩阵;contours[i].size()是轮廓的坐标点数

    //OpenCV的PCA输入必须要是单信道32位浮点数格式或是单信道64位浮点数格式的,参数为CV_32FC1或是CV_64FC1
    for (int i = 0; i < data_pts.rows; ++i) {
        data_pts.at<double>(i, 0) = pts[i].x;
        data_pts.at<double>(i, 1) = pts[i].y;
    }

    cv::PCA pca_analysis(data_pts, cv::Mat(), 0);//执行PCA分析
    //参数具体含义,看下面的实例

    cv::Point pos = cv::Point(pca_analysis.mean.at<double>(0, 0), pca_analysis.mean.at<double>(0, 1));
    //获得最主要分量(均值),在本例中,对应的就是轮廓中点,也是图像中点
    //pca_analysis.mean.at<double>(0, 0)   返回轮廓中心(均值)x坐标
    //pca_analysis.mean.at<double>(0, 1)   返回轮廓中心(均值)y坐标

    //cv::circle(img, pos, 3, cv::Scalar(0, 0, 255), -1);//画出轮廓中心

    std::vector<cv::Point2d> eigen_vecs(2);//保存轮廓特征向量的两个点
    std::vector<double> eigen_val(2);  //保存特征值--每个轮廓有两个
    for (int j = 0; j < 2; ++j) {
        eigen_vecs[j] = cv::Point2d(pca_analysis.eigenvectors.at<double>(j, 0), pca_analysis.eigenvectors.at<double>(j, 1));
        //pca_analysis.eigenvectors.at<double>(j, 0)   返回轮廓协方差矩阵的特征向量x
        //pca_analysis.eigenvectors.at<double>(j, 1)   返回轮廓协方差矩阵的特征向量y  
        //每个轮廓的特征向量包含两个点:参数1:j表示第几个点;参数2:表示x或y
        //特征向量值在(-1,1)

        eigen_val[j] = pca_analysis.eigenvalues.at<double>(j, 0);//返回特征值
        //参数1:0表示x方向的特征值;1表示y方向的特征值

        cv::line(img, pos, pos + 0.02 * cv::Point(eigen_vecs[0].x * eigen_val[0], eigen_vecs[0].y * eigen_val[0]), CV_RGB(255, 255, 0));
        //计算出直线,在主要方向上绘制直线(每个特征向量乘以其特征值并转换为平均位置。有一个 0.02 的缩放系数,它只是为了确保矢量适合图像并且没有 10000 像素的长度)

        cv::line(img, pos, pos + 0.1 * cv::Point(eigen_vecs[1].x * eigen_val[1], eigen_vecs[1].y * eigen_val[1]), CV_RGB(0, 255, 255));
        
    }

    return std::atan2(eigen_vecs[0].y, eigen_vecs[0].x);//最终计算并返回一个最强的(即具有最大特征值)的特征向量的角度
}

 

 

对数据集降维处理

对一副宽p、高q的二维灰度图,要完整表示该图像,需要m = p*q维的向量空间,比如100*100的灰度图像,它的向量空间为100*100=10000。下图是一个3*3的灰度图和表示它的向量表示:

 该向量为行向量,共9维,用变量表示就是[v0, v1, v2, v3, v4, v5, v6, v7, v8],其中v0...v8,的范围都是0-255。

现在的问题是假如我们用1*10000向量,表示100*100的灰度图,是否向量中的10000维对我们同样重要?肯定不是这样的,有些维的值可能对图像更有用,有些维相对来说作用小些。为了节省存储空间,我们需要对10000维的数据进行降维操作,这时就用到了PCA算法,该s算法主要就是用来处理降维的,降维后会尽量保留更有意义的维数,它的思想就是对于高维的数据集来说,一部分维数表示大部分有意义的数据

#include<opencv2/opencv.hpp>
#include<iostream>
#include  <vector>

//把图像归一化为0-255,便于显示
cv::Mat norm_0_255(const cv::Mat& src)
{
    cv::Mat dst;
    switch (src.channels())
    {
    case 1:
        cv::normalize(src, dst, 0, 255, cv::NORM_MINMAX, CV_8UC1);
        break;
    case 3:
        cv::normalize(src, dst, 0, 255, cv::NORM_MINMAX, CV_8UC3);
        break;
    default:
        src.copyTo(dst);
        break;
    }
    return dst;
}


//转化给定的图像为行矩阵
cv::Mat asRowMatrix(std::vector<cv::Mat>& src, int rtype, double alpha = 1, double beta = 0) {
    /*
    参数1:需要转换的矩阵集合
    参数2:转换后的矩阵数据类型
    */
    size_t n = src.size(); //矩阵数量
    if (n == 0)return cv::Mat();//如果没有矩阵,返回空矩阵
    size_t d = src[0].total();//矩阵中的像素总数

    cv::Mat data(n, d, rtype);
    for (int i = 0; i < n; i++) {    //拷贝数据
        cv::Mat xi = data.row(i);
        if (src[i].isContinuous()) {    //如果是连续矩阵
            src[i].reshape(1, 1).convertTo(xi, rtype, alpha, beta);
            //修改成1通道1行矩阵并转换数据类型后保存到xi矩阵
        }
        else {  //非连续矩阵
            src[i].clone().reshape(1, 1).convertTo(xi, rtype, alpha, beta);
        
        }

    }

    return data;
}




int main(int argc, char** argv) {

    std::vector<cv::Mat> db;
    //读入10个人脸图像,这些图像大小相等,是一个人的各种表情图片
    //10个人脸下载:链接:https://pan.baidu.com/s/1CPOebkpwGAvHvE24-SGvCQ 提取码:6666
    db.push_back(cv::imread("D:/bb/lian/1.png", 0));
    //96X116图像:116行X96列=11136
    db.push_back(cv::imread("D:/bb/lian/2.png", 0));
    db.push_back(cv::imread("D:/bb/lian/3.png", 0));
    db.push_back(cv::imread("D:/bb/lian/4.png", 0));
    db.push_back(cv::imread("D:/bb/lian/5.png", 0));
    db.push_back(cv::imread("D:/bb/lian/6.png", 0));
    db.push_back(cv::imread("D:/bb/lian/7.png", 0));
    db.push_back(cv::imread("D:/bb/lian/8.png", 0));
    db.push_back(cv::imread("D:/bb/lian/9.png", 0));
    db.push_back(cv::imread("D:/bb/lian/10.png", 0));

    cv::Mat data = asRowMatrix(db, CV_32FC1);
    //执行之后,data是10行矩阵,每行是一个图片
    
    int num_components = 5;  // PCA算法保持5主成分分量---降维处理

    cv::PCA pca(data, cv::Mat(), 0, num_components);//执行pca算法
    //参数1:为要进行PCA变换的输入Mat
    //参数2:为该Mat的均值向量
    //参数3:为输入矩阵数据的存储方式,如果其值为CV_PCA_DATA_AS_ROW=0则说明输入Mat的每一行代表一个样本,同理当其值为CV_PCA_DATA_AS_COL=1时,代表输入矩阵的每一列为一个样本
    //参数4:该PCA计算时保留的最大主成分的个数。如果是缺省值,则表示所有的成分都保留

    cv::Mat mean = pca.mean.clone();  //均值矩阵

    std::cerr << "mean行数=" << mean.rows << ",    " << "mean列数=" << mean.cols << std::endl;
    //行数=1,列数=11136   

    cv::Mat eigenvalues = pca.eigenvalues.clone();//特征值矩阵
    cv::Mat eigenvectors = pca.eigenvectors.clone();//特征向量矩阵
        
    mean=mean.reshape(1, db[0].rows);
    
    mean = norm_0_255(mean);

    cv::namedWindow("均值脸",0);
    cv::imshow("均值脸", mean);


    
    //五个特征脸

    std::cerr << "eigenvectors行数=" << eigenvectors.rows << ",    " << "eigenvectors列数=" << eigenvectors.cols << std::endl;
    //行数=5,列数=11136;这个行数由PCA的参数4决定,也就是最大主成分的个数

    cv::Mat row0 = pca.eigenvectors.row(0).clone();//获取第一个特征脸的数据
    row0 = row0.reshape(1, db[0].rows);
    row0 = norm_0_255(row0);
    cv::namedWindow("第1特征脸", 0);
    cv::imshow("第1特征脸", row0);

    cv::Mat row1 = pca.eigenvectors.row(1).clone();//获取第2个特征脸的数据
    row1 = row1.reshape(1, db[0].rows);
    row1 = norm_0_255(row1);
    cv::namedWindow("第2特征脸", 0);
    cv::imshow("第2特征脸", row1);

    cv::Mat row2 = pca.eigenvectors.row(2).clone();//获取第3个特征脸的数据
    row2 = row2.reshape(1, db[0].rows);
    row2 = norm_0_255(row2);
    cv::namedWindow("第3特征脸", 0);
    cv::imshow("第3特征脸", row2);

    cv::Mat row3 = pca.eigenvectors.row(3).clone();//获取第4个特征脸的数据
    row3 = row3.reshape(1, db[0].rows);
    row3 = norm_0_255(row3);
    cv::namedWindow("第4特征脸", 0);
    cv::imshow("第4特征脸", row3);

    cv::Mat row4 = pca.eigenvectors.row(4).clone();//获取第5个特征脸的数据
    row4 = row4.reshape(1, db[0].rows);
    row4 = norm_0_255(row4);
    cv::namedWindow("第5特征脸", 0);
    cv::imshow("第5特征脸", row4);

    
    cv::waitKey(0);
    return 0;
}

 

 

 

 

 

 

posted @ 2021-11-13 11:04  天子骄龙  阅读(593)  评论(0编辑  收藏  举报