主成分分析(PCA)简介 OpenCV-Python v4.7.0

上一个教程非线性可分离数据的支持向量机

Original authorTheodore Tsesmelis
CompatibilityOpenCV >= 3.0

目标

在本教程中,您将学习如何

  • 使用 OpenCV 类 cv::PCA 计算目标对象的方向。

什么是 PCA?

主成分分析(PCA)是一种统计程序,用于提取数据集中最重要的特征。
在这里插入图片描述
假设有一组二维点,如上图所示。每个维度都对应您感兴趣的一个特征。在这里,有些人可能会认为这些点的顺序是随机的。但是,如果你仔细观察,就会发现这里有一个线性模式(蓝线表示),这一点很难被忽视。PCA 的一个关键点是降维。降维是减少给定数据集维数的过程。例如,在上述案例中,我们可以将点的集合近似为一条直线,从而将给定点的维度从二维降低到一维。

此外,您还可以看到,沿蓝线的点变化最大,比沿特征 1 或特征 2 轴的点变化更大。这意味着,与只知道点在特征 1 轴或特征 2 轴上的位置相比,如果知道点在蓝线上的位置,就能获得更多关于点的信息。

因此,PCA 可以让我们找到数据变化最大的方向。事实上,在图中的点集合上运行 PCA 的结果由 2 个称为特征向量的向量组成,它们是数据集的主成分。

在这里插入图片描述
每个特征向量的大小由相应的特征值编码,表示数据沿主成分的变化程度。特征向量的起点是数据集中所有点的中心。对 N 维数据集应用 PCA 可以得到 N 个 N 维特征向量、N 个特征值和 1 个 N 维中心点。理论够了,让我们看看如何将这些想法付诸代码。

如何计算特征向量和特征值?

我们的目标是将给定的维数为 p 的数据集 X 变换到维数为 L 的另一个数据集 Y。等效地,我们正在寻找矩阵 Y,其中 Y 是矩阵 X 的卡尔胡宁-洛埃夫变换 (KLT):
Y = K L T { X } Y=KLT\left\{ X \right\} Y=KLT{X}

整理数据集

假设你有一组包含 p 个变量的观测数据,你想减少数据量,使每个观测值只用 L 个变量来描述,L < p。进一步假设,数据被排列成一组 n 个数据向量 x1…xn,每个 xi 代表 p 个变量的一个分组观测值。

  • 将 x1…xn 写成行向量,每个行向量有 p 列。
  • 将行向量放入 n×p 的单一矩阵 X 中。

计算经验均值

求出每个维度 j=1,…p 的经验平均数。
将计算出的平均值放入维数为 p×1 的经验平均值向量 u 中。

u [ j ] = 1 n ∑ X [ i , j ] i = 1 n u\left[ j \right] =\frac{1}{n}\overset{n}{\underset{i=1}{\sum{X\left[ i,j \right]}}} u[j]=n1i=1X[i,j]n

计算与平均值的偏差

均值减法是寻找主成分基的解决方案中不可或缺的一部分,它能使近似数据的均方误差最小化。因此,我们对数据进行如下居中处理:

从数据矩阵 X 的每一行中减去经验平均值向量 u。
将平均值减去后的数据存储在 n×p 矩阵 B 中。

B = X − h u T B=X-hu^T B=XhuT

其中,h 是包含所有 1 的 n×1 列向量:

h [ i ] = 1 , i = 1 , . . . , n h\left[ i \right] =1,i=1,...,n h[i]=1,i=1,...,n

求协方差矩阵

  • 根据矩阵 B 与自身的外积求 p×p 经验协方差矩阵 C:
    C = 1 n − 1 B ∗ ⋅ B C=\frac{1}{n-1}B^*\cdot B C=n11BB

其中 * 是共轭转置运算符。请注意,如果 B 完全由实数组成(在许多应用中都是这种情况),"共轭转置 "与常规转置相同。

求协方差矩阵的特征向量和特征值

  • 计算将协方差矩阵 C 对角化的特征向量矩阵 V:

V − 1 C V = D V^{-1}CV=D V1CV=D

其中 D 是 C 的特征值对角矩阵。

  • 矩阵 D 的形式为 p×p 对角矩阵:

D [ k , l ] = { λ k , k = l 0 ,   k ≠ l D\left[ k,l \right] =\left\{ \begin{array}{c} \lambda _k,k=l\\ 0,\ k\ne l\\ \end{array} \right. D[k,l]={λk,k=l0, k=l

这里,λj 是协方差矩阵 C 的第 j 个特征值。

  • 矩阵 V 的维数也是 p x p,包含 p 个列向量,每个列向量长度为 p,代表协方差矩阵 C 的 p 个特征向量。
  • 特征值和特征向量是有序配对的。第 j 个特征值对应第 j 个特征向量。


来源[1][2],特别感谢 Svetlin Penkov 提供的原始教程。

源代码

C++

  • 可下载代码:点击这里
  • 代码概览
#include "opencv2/core.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
using namespace std;
using namespace cv;
//  函数声明
void drawAxis(Mat&, Point, Point, Scalar, const float);
double getOrientation(const vector<Point> &, Mat&);
void drawAxis(Mat& img, Point p, Point q, Scalar colour, const float scale = 0.2)
{
 double angle = atan2( (double) p.y - q.y, (double) p.x - q.x ); // angle in radians
 double hypotenuse = sqrt( (double) (p.y - q.y) * (p.y - q.y) + (p.x - q.x) * (p.x - q.x));
 // 这里我们将箭头延长一个比例系数
 q.x = (int) (p.x - scale * hypotenuse * cos(angle));
 q.y = (int) (p.y - scale * hypotenuse * sin(angle));
 line(img, p, q, colour, 1, LINE_AA);
 // 创建箭头钩
 p.x = (int) (q.x + 9 * cos(angle + CV_PI / 4));
 p.y = (int) (q.y + 9 * sin(angle + CV_PI / 4));
 line(img, p, q, colour, 1, LINE_AA);
 p.x = (int) (q.x + 9 * cos(angle - CV_PI / 4));
 p.y = (int) (q.y + 9 * sin(angle - CV_PI / 4));
 line(img, p, q, colour, 1, LINE_AA);
}
double getOrientation(const vector<Point> &pts, Mat &img)
{
 //构建 pca 分析使用的缓冲区
 int sz = static_cast<int>(pts.size());
 Mat data_pts = Mat(sz, 2, CV_64F);
 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;
 }
 //执行 PCA 分析
 PCA pca_analysis(data_pts, Mat(), PCA::DATA_AS_ROW);
 //存储对象中心点
 Point cntr = Point(static_cast<int>(pca_analysis.mean.at<double>(0, 0)),
 static_cast<int>(pca_analysis.mean.at<double>(0, 1)));
 //存储特征值和特征向量
 vector<Point2d> eigen_vecs(2);
 vector<double> eigen_val(2);
 for (int i = 0; i < 2; i++)
 {
 eigen_vecs[i] = Point2d(pca_analysis.eigenvectors.at<double>(i, 0),
 pca_analysis.eigenvectors.at<double>(i, 1));
 eigen_val[i] = pca_analysis.eigenvalues.at<double>(i);
 }
 // 绘制主成分
 circle(img, cntr, 3, Scalar(255, 0, 255), 2);
 Point p1 = cntr + 0.02 * Point(static_cast<int>(eigen_vecs[0].x * eigen_val[0]), static_cast<int>(eigen_vecs[0].y * eigen_val[0]));
 Point p2 = cntr - 0.02 * Point(static_cast<int>(eigen_vecs[1].x * eigen_val[1]), static_cast<int>(eigen_vecs[1].y * eigen_val[1]));
 drawAxis(img, cntr, p1, Scalar(0, 255, 0), 1);
 drawAxis(img, cntr, p2, Scalar(255, 255, 0), 5);
 double angle = atan2(eigen_vecs[0].y, eigen_vecs[0].x); // 方向,单位为弧度
 return angle;
}
int main(int argc, char** argv)
{
 // 加载图像
 CommandLineParser parser(argc, argv, "{@input | pca_test1.jpg | input image}");
 parser.about( "This program demonstrates how to use OpenCV PCA to extract the orientation of an object.\n" );
 parser.printMessage();
 Mat src = imread( samples::findFile( parser.get<String>("@input") ) );
 // 检查图片是否加载成功
 if(src.empty())
 {
 cout << "Problem loading image!!!" << endl;
 return EXIT_FAILURE;
 }
 imshow("src", src);
 // 将图像转换为灰度图像
 Mat gray;
 cvtColor(src, gray, COLOR_BGR2GRAY);
 // 将图像转换为二值图像
 Mat bw;
 threshold(gray, bw, 50, 255, THRESH_BINARY | THRESH_OTSU);
 // 查找阈值化图像中的所有轮廓
 vector<vector<Point> > contours;
 findContours(bw, contours, RETR_LIST, CHAIN_APPROX_NONE);
 for (size_t i = 0; i < contours.size(); i++)
 {
 // 计算每个轮廓的面积
 double area = contourArea(contours[i]);
 // 忽略过小或过大的轮廓
 if (area < 1e2 || 1e5 < area) continue;
 // 绘制每条轮廓,仅用于可视化目的
 drawContours(src, contours, static_cast<int>(i), Scalar(0, 0, 255), 2);
 // 查找每个形状的方向
 getOrientation(contours[i], src);
 }
 imshow("output", src);
 waitKey();
 return EXIT_SUCCESS;
}

Java

  • 可下载代码:点击这里
  • 代码概览
import java.util.ArrayList;
import java.util.List;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfPoint;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.highgui.HighGui;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
//此程序演示了如何使用 OpenCV PCA 提取对象的方向。
class IntroductionToPCA {
 private void drawAxis(Mat img, Point p_, Point q_, Scalar colour, float scale) {
 Point p = new Point(p_.x, p_.y);
 Point q = new Point(q_.x, q_.y);
 double angle = Math.atan2(p.y - q.y, p.x - q.x); //  以弧度为单位的角度
 double hypotenuse = Math.sqrt((p.y - q.y) * (p.y - q.y) + (p.x - q.x) * (p.x - q.x));
 // 在这里,我们将箭头延长一个比例因子
 q.x = (int) (p.x - scale * hypotenuse * Math.cos(angle));
 q.y = (int) (p.y - scale * hypotenuse * Math.sin(angle));
 Imgproc.line(img, p, q, colour, 1, Imgproc.LINE_AA, 0);
 // 创建箭头钩
 p.x = (int) (q.x + 9 * Math.cos(angle + Math.PI / 4));
 p.y = (int) (q.y + 9 * Math.sin(angle + Math.PI / 4));
 Imgproc.line(img, p, q, colour, 1, Imgproc.LINE_AA, 0);
 p.x = (int) (q.x + 9 * Math.cos(angle - Math.PI / 4));
 p.y = (int) (q.y + 9 * Math.sin(angle - Math.PI / 4));
 Imgproc.line(img, p, q, colour, 1, Imgproc.LINE_AA, 0);
 }
 private double getOrientation(MatOfPoint ptsMat, Mat img) {
 List<Point> pts = ptsMat.toList();
 // 构建用于 pca 分析的缓冲区
 int sz = pts.size();
 Mat dataPts = new Mat(sz, 2, CvType.CV_64F);
 double[] dataPtsData = new double[(int) (dataPts.total() * dataPts.channels())];
 for (int i = 0; i < dataPts.rows(); i++) {
 dataPtsData[i * dataPts.cols()] = pts.get(i).x;
 dataPtsData[i * dataPts.cols() + 1] = pts.get(i).y;
 }
 dataPts.put(0, 0, dataPtsData);
 // 执行 PCA 分析
 Mat mean = new Mat();
 Mat eigenvectors = new Mat();
 Mat eigenvalues = new Mat();
 Core.PCACompute2(dataPts, mean, eigenvectors, eigenvalues);
 double[] meanData = new double[(int) (mean.total() * mean.channels())];
 mean.get(0, 0, meanData);
 // 存储对象的中心点
 Point cntr = new Point(meanData[0], meanData[1]);
 // 存储特征值和特征向量
 double[] eigenvectorsData = new double[(int) (eigenvectors.total() * eigenvectors.channels())];
 double[] eigenvaluesData = new double[(int) (eigenvalues.total() * eigenvalues.channels())];
 eigenvectors.get(0, 0, eigenvectorsData);
 eigenvalues.get(0, 0, eigenvaluesData);
 // 绘制主成分
 Imgproc.circle(img, cntr, 3, new Scalar(255, 0, 255), 2);
 Point p1 = new Point(cntr.x + 0.02 * eigenvectorsData[0] * eigenvaluesData[0],
 cntr.y + 0.02 * eigenvectorsData[1] * eigenvaluesData[0]);
 Point p2 = new Point(cntr.x - 0.02 * eigenvectorsData[2] * eigenvaluesData[1],
 cntr.y - 0.02 * eigenvectorsData[3] * eigenvaluesData[1]);
 drawAxis(img, cntr, p1, new Scalar(0, 255, 0), 1);
 drawAxis(img, cntr, p2, new Scalar(255, 255, 0), 5);
 double angle = Math.atan2(eigenvectorsData[1], eigenvectorsData[0]); // 方向,单位为弧度
 return angle;
 }
 public void run(String[] args) {
 // 加载图像
 String filename = args.length > 0 ? args[0] : "../data/pca_test1.jpg";
 Mat src = Imgcodecs.imread(filename);
 // 检查图像是否加载成功
 if (src.empty()) {
 System.err.println("Cannot read image: " + filename);
 System.exit(0);
 }
 Mat srcOriginal = src.clone();
 HighGui.imshow("src", srcOriginal);
 // 将图像转换为灰度图像
 Mat gray = new Mat();
 Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
 // 将图像转换为二值图像
 Mat bw = new Mat();
 Imgproc.threshold(gray, bw, 50, 255, Imgproc.THRESH_BINARY | Imgproc.THRESH_OTSU);
 // 查找阈值化图像中的所有轮廓
 List<MatOfPoint> contours = new ArrayList<>();
 Mat hierarchy = new Mat();
 Imgproc.findContours(bw, contours, hierarchy, Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_NONE);
 for (int i = 0; i < contours.size(); i++) {
 // 计算每个轮廓的面积
 double area = Imgproc.contourArea(contours.get(i));
 // 忽略过小或过大的轮廓
 if (area < 1e2 || 1e5 < area)
 continue;
 // 绘制每个轮廓,仅用于可视化目的
 Imgproc.drawContours(src, contours, i, new Scalar(0, 0, 255), 2);
 // 查找每个形状的方向
 getOrientation(contours.get(i), src);
 }
 HighGui.imshow("output", src);
 HighGui.waitKey();
 System.exit(0);
 }
}
public class IntroductionToPCADemo {
 public static void main(String[] args) {
 // Load the native OpenCV library
 System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
 new IntroductionToPCA().run(args);
 }
}

Python

  • 可下载代码:点击这里
  • 代码概览
from __future__ import print_function
from __future__ import division
import cv2 as cv
import numpy as np
import argparse
from math import atan2, cos, sin, sqrt, pi
def drawAxis(img, p_, q_, colour, scale):
 p = list(p_)
 q = list(q_)
 
 angle = atan2(p[1] - q[1], p[0] - q[0]) # 以弧度为单位的角度
 hypotenuse = sqrt((p[1] - q[1]) * (p[1] - q[1]) + (p[0] - q[0]) * (p[0] - q[0]))
 # 这里我们将箭头延长一个比例系数
 q[0] = p[0] - scale * hypotenuse * cos(angle)
 q[1] = p[1] - scale * hypotenuse * sin(angle)
 cv.line(img, (int(p[0]), int(p[1])), (int(q[0]), int(q[1])), colour, 1, cv.LINE_AA)
 # 创建箭头钩子
 p[0] = q[0] + 9 * cos(angle + pi / 4)
 p[1] = q[1] + 9 * sin(angle + pi / 4)
 cv.line(img, (int(p[0]), int(p[1])), (int(q[0]), int(q[1])), colour, 1, cv.LINE_AA)
 p[0] = q[0] + 9 * cos(angle - pi / 4)
 p[1] = q[1] + 9 * sin(angle - pi / 4)
 cv.line(img, (int(p[0]), int(p[1])), (int(q[0]), int(q[1])), colour, 1, cv.LINE_AA)
 
def getOrientation(pts, img):
 
 sz = len(pts)
 data_pts = np.empty((sz, 2), dtype=np.float64)
 for i in range(data_pts.shape[0]):
 data_pts[i,0] = pts[i,0,0]
 data_pts[i,1] = pts[i,0,1]
 # 进行 PCA 分析
 mean = np.empty((0))
 mean, eigenvectors, eigenvalues = cv.PCACompute2(data_pts, mean)
 # 存储对象的中心点
 cntr = (int(mean[0,0]), int(mean[0,1]))
 
 #绘制主成分
 cv.circle(img, cntr, 3, (255, 0, 255), 2)
 p1 = (cntr[0] + 0.02 * eigenvectors[0,0] * eigenvalues[0,0], cntr[1] + 0.02 * eigenvectors[0,1] * eigenvalues[0,0])
 p2 = (cntr[0] - 0.02 * eigenvectors[1,0] * eigenvalues[1,0], cntr[1] - 0.02 * eigenvectors[1,1] * eigenvalues[1,0])
 drawAxis(img, cntr, p1, (0, 255, 0), 1)
 drawAxis(img, cntr, p2, (255, 255, 0), 5)
 angle = atan2(eigenvectors[0,1], eigenvectors[0,0]) # 方向,单位为弧度
 
 return angle
parser = argparse.ArgumentParser(description='Code for Introduction to Principal Component Analysis (PCA) tutorial.\
 This program demonstrates how to use OpenCV PCA to extract the orientation of an object.')
parser.add_argument('--input', help='Path to input image.', default='pca_test1.jpg')
args = parser.parse_args()
src = cv.imread(cv.samples.findFile(args.input))
# 检查图像是否加载成功
if src is None:
 print('Could not open or find the image: ', args.input)
 exit(0)
cv.imshow('src', src)
# 将图像转换为灰度图像
gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
# 将图像转换为二值图像
_, bw = cv.threshold(gray, 50, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)
contours, _ = cv.findContours(bw, cv.RETR_LIST, cv.CHAIN_APPROX_NONE)
for i, c in enumerate(contours):
 # 计算每个轮廓的面积
 area = cv.contourArea(c)
 # 忽略过小或过大的轮廓
 if area < 1e2 or 1e5 < area:
 continue
 # 绘制每个轮廓,仅用于可视化目的
 cv.drawContours(src, contours, i, (0, 0, 255), 2)
 # 找出每个形状的方向
 getOrientation(c, src)
cv.imshow('output', src)
cv.waitKey()

注释
另一个使用 PCA 降维同时保持一定方差的示例,请参见 opencv_source_code/samples/cpp/pca.cpp

解释

  • 读取图像并将其转换为二进制图像

在这里,我们采用必要的预处理程序,以便能够检测到目标对象:

C++

// 加载图像
 CommandLineParser parser(argc, argv, "{@input | pca_test1.jpg | input image}");
 parser.about( "This program demonstrates how to use OpenCV PCA to extract the orientation of an object.\n" );
 parser.printMessage();
 Mat src = imread( samples::findFile( parser.get<String>("@input") ) );
 // 检查图片是否加载成功
 if(src.empty())
 {
 cout << "Problem loading image!!!" << endl;
 return EXIT_FAILURE;
 }
 imshow("src", src);
 // 将图像转换为灰度图像
 Mat gray;
 cvtColor(src, gray, COLOR_BGR2GRAY);
 // 将图像转换为二值图像
 Mat bw;
 threshold(gray, bw, 50, 255, THRESH_BINARY | THRESH_OTSU);

Java

// 加载图像
 String filename = args.length > 0 ? args[0] : "../data/pca_test1.jpg";
 Mat src = Imgcodecs.imread(filename);
 // 检查图像是否加载成功
 if (src.empty()) {
 System.err.println("Cannot read image: " + filename);
 System.exit(0);
 }
 Mat srcOriginal = src.clone();
 HighGui.imshow("src", srcOriginal);
 // 将图像转换为灰度图像
 Mat gray = new Mat();
 Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
 // 将图像转换为二值图像
 Mat bw = new Mat();
 Imgproc.threshold(gray, bw, 50, 255, Imgproc.THRESH_BINARY | Imgproc.THRESH_OTSU);

Python

// 加载图像
parser = argparse.ArgumentParser(description='Code for Introduction to Principal Component Analysis (PCA) tutorial.\
 This program demonstrates how to use OpenCV PCA to extract the orientation of an object.')
parser.add_argument('--input', help='Path to input image.', default='pca_test1.jpg')
args = parser.parse_args()
src = cv.imread(cv.samples.findFile(args.input))
# 检查图像是否加载成功
if src is None:
 print('Could not open or find the image: ', args.input)
 exit(0)
cv.imshow('src', src)
# 将图像转换为灰度图像
gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
# 将图像转换为二值图像
_, bw = cv.threshold(gray, 50, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)
contours, _ = cv.findContours(bw, cv.RETR_LIST, cv.CHAIN_APPROX_NONE)
for i, c in enumerate(contours):
  • 提取目标对象

然后按大小查找和过滤轮廓线,并获取剩余轮廓线的方向。

C++

// 查找阈值化图像中的所有轮廓
 vector<vector<Point> > contours;
 findContours(bw, contours, RETR_LIST, CHAIN_APPROX_NONE);
 for (size_t i = 0; i < contours.size(); i++)
 {
 // 计算每个轮廓的面积
 double area = contourArea(contours[i]);
 // 忽略过小或过大的轮廓
 if (area < 1e2 || 1e5 < area) continue;
 // 绘制每条轮廓,仅用于可视化目的
 drawContours(src, contours, static_cast<int>(i), Scalar(0, 0, 255), 2);
 // 查找每个形状的方向
 getOrientation(contours[i], src);
 }

Java

 // 查找阈值化图像中的所有轮廓
 List<MatOfPoint> contours = new ArrayList<>();
 Mat hierarchy = new Mat();
 Imgproc.findContours(bw, contours, hierarchy, Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_NONE);
 for (int i = 0; i < contours.size(); i++) {
 // 计算每个轮廓的面积
 double area = Imgproc.contourArea(contours.get(i));
 // 忽略过小或过大的轮廓
 if (area < 1e2 || 1e5 < area)
 continue;
 // 绘制每个轮廓,仅用于可视化目的
 Imgproc.drawContours(src, contours, i, new Scalar(0, 0, 255), 2);
 // 查找每个形状的方向
 getOrientation(contours.get(i), src);
 }

Python

# 查找阈值化图像中的所有轮廓
contours, _ = cv.findContours(bw, cv.RETR_LIST, cv.CHAIN_APPROX_NONE)
for i, c in enumerate(contours):
 # 计算每个轮廓的面积
 area = cv.contourArea(c)
 # 忽略过小或过大的轮廓
 if area < 1e2 or 1e5 < area:
 continue
 # 绘制每个轮廓,仅用于可视化目的
 cv.drawContours(src, contours, i, (0, 0, 255), 2)
 # 找出每个形状的方向
 getOrientation(c, src)
  • 提取方向

调用 getOrientation() 函数提取方向,该函数执行所有 PCA 程序。

C++

 //构建 pca 分析使用的缓冲区
 int sz = static_cast<int>(pts.size());
 Mat data_pts = Mat(sz, 2, CV_64F);
 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;
 }
 //执行 PCA 分析
 PCA pca_analysis(data_pts, Mat(), PCA::DATA_AS_ROW);
 //存储对象中心点
 Point cntr = Point(static_cast<int>(pca_analysis.mean.at<double>(0, 0)),
 static_cast<int>(pca_analysis.mean.at<double>(0, 1)));
 //存储特征值和特征向量
 vector<Point2d> eigen_vecs(2);
 vector<double> eigen_val(2);
 for (int i = 0; i < 2; i++)
 {
 eigen_vecs[i] = Point2d(pca_analysis.eigenvectors.at<double>(i, 0),
 pca_analysis.eigenvectors.at<double>(i, 1));
 eigen_val[i] = pca_analysis.eigenvalues.at<double>(i);
 }

Java

// 构建用于 pca 分析的缓冲区
 int sz = pts.size();
 Mat dataPts = new Mat(sz, 2, CvType.CV_64F);
 double[] dataPtsData = new double[(int) (dataPts.total() * dataPts.channels())];
 for (int i = 0; i < dataPts.rows(); i++) {
 dataPtsData[i * dataPts.cols()] = pts.get(i).x;
 dataPtsData[i * dataPts.cols() + 1] = pts.get(i).y;
 }
 dataPts.put(0, 0, dataPtsData);
 // 执行 PCA 分析
 Mat mean = new Mat();
 Mat eigenvectors = new Mat();
 Mat eigenvalues = new Mat();
 Core.PCACompute2(dataPts, mean, eigenvectors, eigenvalues);
 double[] meanData = new double[(int) (mean.total() * mean.channels())];
 mean.get(0, 0, meanData);
 // 存储对象的中心点
 Point cntr = new Point(meanData[0], meanData[1]);
 // 存储特征值和特征向量
 double[] eigenvectorsData = new double[(int) (eigenvectors.total() * eigenvectors.channels())];
 double[] eigenvaluesData = new double[(int) (eigenvalues.total() * eigenvalues.channels())];
 eigenvectors.get(0, 0, eigenvectorsData);
 eigenvalues.get(0, 0, eigenvaluesData);

Python

# 构建用于 pca 分析的缓冲区
 sz = len(pts)
 data_pts = np.empty((sz, 2), dtype=np.float64)
 for i in range(data_pts.shape[0]):
 data_pts[i,0] = pts[i,0,0]
 data_pts[i,1] = pts[i,0,1]
 # 进行 PCA 分析
 mean = np.empty((0))
 mean, eigenvectors, eigenvalues = cv.PCACompute2(data_pts, mean)
 # 存储对象的中心点
 cntr = (int(mean[0,0]), int(mean[0,1]))

首先,需要将数据排列在一个大小为 n x 2 的矩阵中,其中 n 是我们拥有的数据点的数量。然后我们就可以进行 PCA 分析了。计算出的平均值(即质量中心)存储在 cntr 变量中,特征向量和特征值存储在相应的 std::vector 中。

  • 结果可视化
    通过 drawAxis() 函数可视化最终结果,在该函数中,主成分被绘制成线条,每个特征向量乘以其特征值并转换到均值位置。
    C++
 // 绘制主成分
 circle(img, cntr, 3, Scalar(255, 0, 255), 2);
 Point p1 = cntr + 0.02 * Point(static_cast<int>(eigen_vecs[0].x * eigen_val[0]), static_cast<int>(eigen_vecs[0].y * eigen_val[0]));
 Point p2 = cntr - 0.02 * Point(static_cast<int>(eigen_vecs[1].x * eigen_val[1]), static_cast<int>(eigen_vecs[1].y * eigen_val[1]));
 drawAxis(img, cntr, p1, Scalar(0, 255, 0), 1);
 drawAxis(img, cntr, p2, Scalar(255, 255, 0), 5);
 double angle = atan2(eigen_vecs[0].y, eigen_vecs[0].x); // 方向,单位为弧度



 double angle = atan2( (double) p.y - q.y, (double) p.x - q.x ); // angle in radians
 double hypotenuse = sqrt( (double) (p.y - q.y) * (p.y - q.y) + (p.x - q.x) * (p.x - q.x));
 // 这里我们将箭头延长一个比例系数
 q.x = (int) (p.x - scale * hypotenuse * cos(angle));
 q.y = (int) (p.y - scale * hypotenuse * sin(angle));
 line(img, p, q, colour, 1, LINE_AA);
 // 创建箭头钩
 p.x = (int) (q.x + 9 * cos(angle + CV_PI / 4));
 p.y = (int) (q.y + 9 * sin(angle + CV_PI / 4));
 line(img, p, q, colour, 1, LINE_AA);
 p.x = (int) (q.x + 9 * cos(angle - CV_PI / 4));
 p.y = (int) (q.y + 9 * sin(angle - CV_PI / 4));
 line(img, p, q, colour, 1, LINE_AA);

Java

// 绘制主成分
 Imgproc.circle(img, cntr, 3, new Scalar(255, 0, 255), 2);
 Point p1 = new Point(cntr.x + 0.02 * eigenvectorsData[0] * eigenvaluesData[0],
 cntr.y + 0.02 * eigenvectorsData[1] * eigenvaluesData[0]);
 Point p2 = new Point(cntr.x - 0.02 * eigenvectorsData[2] * eigenvaluesData[1],
 cntr.y - 0.02 * eigenvectorsData[3] * eigenvaluesData[1]);
 drawAxis(img, cntr, p1, new Scalar(0, 255, 0), 1);
 drawAxis(img, cntr, p2, new Scalar(255, 255, 0), 5);
 double angle = Math.atan2(eigenvectorsData[1], eigenvectorsData[0]); // 方向,单位为弧度



double angle = Math.atan2(p.y - q.y, p.x - q.x); //  以弧度为单位的角度
 double hypotenuse = Math.sqrt((p.y - q.y) * (p.y - q.y) + (p.x - q.x) * (p.x - q.x));
 // 在这里,我们将箭头延长一个比例因子
 q.x = (int) (p.x - scale * hypotenuse * Math.cos(angle));
 q.y = (int) (p.y - scale * hypotenuse * Math.sin(angle));
 Imgproc.line(img, p, q, colour, 1, Imgproc.LINE_AA, 0);
 // 创建箭头钩
 p.x = (int) (q.x + 9 * Math.cos(angle + Math.PI / 4));
 p.y = (int) (q.y + 9 * Math.sin(angle + Math.PI / 4));
 Imgproc.line(img, p, q, colour, 1, Imgproc.LINE_AA, 0);
 p.x = (int) (q.x + 9 * Math.cos(angle - Math.PI / 4));
 p.y = (int) (q.y + 9 * Math.sin(angle - Math.PI / 4));
 Imgproc.line(img, p, q, colour, 1, Imgproc.LINE_AA, 0);

Python

#绘制主成分
 cv.circle(img, cntr, 3, (255, 0, 255), 2)
 p1 = (cntr[0] + 0.02 * eigenvectors[0,0] * eigenvalues[0,0], cntr[1] + 0.02 * eigenvectors[0,1] * eigenvalues[0,0])
 p2 = (cntr[0] - 0.02 * eigenvectors[1,0] * eigenvalues[1,0], cntr[1] - 0.02 * eigenvectors[1,1] * eigenvalues[1,0])
 drawAxis(img, cntr, p1, (0, 255, 0), 1)
 drawAxis(img, cntr, p2, (255, 255, 0), 5)
 angle = atan2(eigenvectors[0,1], eigenvectors[0,0]) # 方向,单位为弧度



 angle = atan2(p[1] - q[1], p[0] - q[0]) # 以弧度为单位的角度
 hypotenuse = sqrt((p[1] - q[1]) * (p[1] - q[1]) + (p[0] - q[0]) * (p[0] - q[0]))
 # 这里我们将箭头延长一个比例系数
 q[0] = p[0] - scale * hypotenuse * cos(angle)
 q[1] = p[1] - scale * hypotenuse * sin(angle)
 cv.line(img, (int(p[0]), int(p[1])), (int(q[0]), int(q[1])), colour, 1, cv.LINE_AA)
 # 创建箭头钩子
 p[0] = q[0] + 9 * cos(angle + pi / 4)
 p[1] = q[1] + 9 * sin(angle + pi / 4)
 cv.line(img, (int(p[0]), int(p[1])), (int(q[0]), int(q[1])), colour, 1, cv.LINE_AA)
 p[0] = q[0] + 9 * cos(angle - pi / 4)
 p[1] = q[1] + 9 * sin(angle - pi / 4)
 cv.line(img, (int(p[0]), int(p[1])), (int(q[0]), int(q[1])), colour, 1, cv.LINE_AA)

结果

代码会打开一幅图像,找出检测到的感兴趣物体的方向,然后通过绘制检测到的感兴趣物体的轮廓线、中心点以及关于提取方向的 x 轴和 y 轴,将结果可视化。

在这里插入图片描述

  • 测试图片
    在这里插入图片描述
  • 输出图片
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值