上一个教程: 非线性可分离数据的支持向量机
Original author | Theodore Tsesmelis |
---|---|
Compatibility | OpenCV >= 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=1∑X[i,j]n
计算与平均值的偏差
均值减法是寻找主成分基的解决方案中不可或缺的一部分,它能使近似数据的均方误差最小化。因此,我们对数据进行如下居中处理:
从数据矩阵 X 的每一行中减去经验平均值向量 u。
将平均值减去后的数据存储在 n×p 矩阵 B 中。
B = X − h u T B=X-hu^T B=X−huT
其中,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=n−11B∗⋅B
其中 * 是共轭转置运算符。请注意,如果 B 完全由实数组成(在许多应用中都是这种情况),"共轭转置 "与常规转置相同。
求协方差矩阵的特征向量和特征值
- 计算将协方差矩阵 C 对角化的特征向量矩阵 V:
V − 1 C V = D V^{-1}CV=D V−1CV=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 个特征向量。
源代码
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 轴,将结果可视化。
- 测试图片
- 输出图片