文章目录
一,简介:
在图像处理和计算机视觉领域,曲别针方向的计算是一项具有挑战性的任务,它涉及到形状识别、轮廓提取和几何分析等多个技术环节。本文将详细介绍如何运用OpenCV库,这一强大的开源计算机视觉工具,来实现曲别针方向的精确计算。文章将深入探讨以下关键步骤:图像预处理、边缘检测、轮廓识别、主成分分析(PCA)以及角度计算。通过本文,读者将学习到如何利用OpenCV的高级功能和算法,从图像中提取曲别针的轮廓,并通过PCA方法确定其方向.
二,算法流程:
该算法流程首先将输入图像转换为灰度图和二值图,然后检测图像中的轮廓。对于每个轮廓,算法计算其面积并筛选出合适的轮廓。接着,使用主成分分析(PCA)来确定每个形状的方向。具体步骤包括:计算起点和终点之间的向量角度和长度,根据比例放大箭头长度并在图像上绘制轴线,同时在轴线的两端添加箭头钩增强可视化效果。最后,算法在图像上标注出每个形状的旋转角度,并显示最终的输出图像。
三,算法实现:
3.1:在图像上绘制箭头:
首先,函数将输入的点p_和q_转换为列表形式,并计算从起点p到终点q的向量角度和长度。接着,根据给定的比例scale放大箭头的长度,并在图像上绘制轴线。最后,函数通过计算并绘制两个箭头钩子来完善箭头的视觉效果,每个钩子的位置是基于原始轴线角度的偏移量。
实现代码:
def drawAxis(img, p_, q_, color, scale):
p = list(p_)
q = list(q_)
# 计算起点p到终点q的向量角度和长度
angle = atan2(p[1] - q[1], p[0] - q[0]) # 计算向量与x轴的夹角(弧度)
hypotenuse = sqrt((p[1] - q[1]) ** 2 + (p[0] - q[0]) ** 2) # 计算起点到终点的直线距离
# 将箭头长度放大scale倍
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])), color, 3, 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])), color, 3, 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])), color, 3, cv.LINE_AA)
3.2 主成分分析获取轮廓方向,中点,主成分轴线的端点:
在opencv中执行主成分分析的函数为cv2.PCACompute2()
参数:
输入:
● data_pts:一个二维 NumPy 数组,包含所有数据点的坐标,每个数据点由一个包含两个元素的元组(x, y)表示。
● mean:可选参数,用于指定数据点的平均值。如果未提供,OpenCV 会自动计算数据点的平均值。
● eigenvectors:可选参数,用于指定特征向量。如果未提供,OpenCV 会自动计算特征向量。
● eigenvalues:可选参数,用于指定特征值。如果未提供,OpenCV 会自动计算特征值。
● noise_cov:可选参数,用于指定噪声的协方差矩阵。如果未提供,OpenCV 会使用单位协方差矩阵。
● flags:可选参数,用于指定计算方式。默认值为 0,表示使用 OpenCV 内置的计算方式。
● iterations:可选参数,用于指定迭代次数。默认值为 0,表示使用 OpenCV 内置的迭代次数。
● eigenvalue_threshold:可选参数,用于指定特征值阈值。如果特征值小于这个阈值,它们将被忽略。默认值为 0.0,表示不使用阈值。
● eigenvector_threshold:可选参数,用于指定特征向量阈值。如果特征向量的模小于这个阈值,它们将被忽略。默认值为 0.0,表示不使用阈值。
返回值:
● mean:数据点的平均值。
● eigenvectors:特征向量。
● eigenvalues:特征值。
cv2.PCACompute2函数它接受一个包含二维坐标点的NumPy数组data_pts作为输入,计算数据点的平均值mean,然后构建协方差矩阵以确定数据点间的相关性。接着,该函数通过特征值分解来获取特征值和特征向量,并按照特征值的大小进行排序,从而选择前k个主成分。默认情况下,它选择最大的特征值及其特征向量。最后,cv2PCACompute2返回平均值mean、特征向量eigenvectors和特征值eigenvalues三个输出,这些结果可用于进一步的数据分析和处理。
通过执行PCA,可以简化数据,提取最重要的特征,这在处理高维数据时非常有用,因为它可以帮助减少数据的复杂性和冗余。在图像处理中,PCA可以用于图像压缩、特征提取和降维等任务。
3.2.1 通过pca计算角度:
在PCA分析中,eigenvectors[0, :]实际上包含了正弦值和余弦值,其中eigenvectors[0, 1]表示正弦值,eigenvectors[0, 0]表示余弦值。因此,通过计算atan2(eigenvectors[0, 1], eigenvectors[0, 0]),我们实际上是在计算这两个值的反正切值,从而得到第一主成分方向相对于x轴的角度。
angle = atan2(eigenvectors[0, 1], eigenvectors[0, 0])
注:在计算机视觉中,二阶矩和PCA是两种计算图像物体方向的方法。二阶矩简单、对噪声不敏感,适用于快速估计物体的大致方向,如简单的人脸识别;而PCA提供更精确的形状和方向信息,但计算复杂、对噪声敏感,适用于复杂的图像分析。PCA角度更准确,考虑了物体整体形状,但二阶矩在简单场景下也可能足够。实际应用中,选择哪种方法取决于需求和资源。
3.2.2 通过pca计算轮廓中点:
mean, eigenvectors, eigenvalues = cv.PCACompute2(data_pts, mean)
# 存储对象的中心点
cntr = (int(mean[0, 0]), int(mean[0, 1]))
3.2.3 pca获得主成分轴线的端点:
主成分轴线(Principal Component Axis)是在主成分分析(PCA)中用于表示数据集的主要方向和变化的一种视觉工具。它是通过PCA分析得到的特征向量和特征值来定义的。这条轴线是从轮廓的中心点cntr出发,沿着第一主成分方向延伸的。eigenvectors[0, :]和eigenvalues[0, :]分别代表了数据集的第一主成分方向和对应的特征值。
其端点定义为:
p1 = (cntr[0] + 0.02 * eigenvectors[0, 0] * eigenvalues[0, 0], cntr[1] + 0.02 * eigenvectors[0, 1] * eigenvalues[0, 0])
注:
● cntr[0] + 0.02 * eigenvectors[0, 0] * eigenvalues[0, 0]:这是主成分轴线的x坐标,它是从中心点cntr的x坐标开始,向右延伸的距离。这个距离是根据特征值和特征向量的模计算得出的,乘以0.02是为了放大这个距离,使其在图像上更加明显。
● cntr[1] + 0.02 * eigenvectors[0, 1] * eigenvalues[0, 0]:这是主成分轴线的y坐标,它是从中心点cntr的y坐标开始,向上延伸的距离。这个距离是根据特征值和特征向量的模计算得出的,乘以0.02是为了放大这个距离,使其在图像上更加明显。
四,整体代码实现:
import cv2 as cv # 导入OpenCV库,并命名为cv
from math import atan2, cos, sin, sqrt, pi # 导入数学计算所需函数
import numpy as np # 导入NumPy库,用于数学计算和矩阵操作
# 定义一个函数,用于绘制图像上的轴线
def drawAxis(img, p_, q_, color, scale):
p = list(p_)
q = list(q_)
# 计算起点p到终点q的向量角度和长度
angle = atan2(p[1] - q[1], p[0] - q[0]) # 计算向量与x轴的夹角(弧度)
hypotenuse = sqrt((p[1] - q[1]) ** 2 + (p[0] - q[0]) ** 2) # 计算起点到终点的直线距离
# 将箭头长度放大scale倍
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])), color, 3, 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])), color, 3, 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])), color, 3, cv.LINE_AA)
# 定义一个函数,用于获取形状的方向
def getOrientation(pts, img):
# 初始化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]))
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])
drawAxis(img, cntr, p1, (255, 255, 0), 1)
# 计算方向角度
angle = atan2(eigenvectors[0, 1], eigenvectors[0, 0]) # 方向(弧度)
# 在图像上标注旋转角度
label = " angle: " + str(-int(np.rad2deg(angle)) - 90)
cv.putText(img, label, (cntr[0], cntr[1]), cv.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 1, cv.LINE_AA)
return angle
# 加载图像
img = cv.imread(r"F:\traditional_vison\20201110141637342.png")
if img is None:
print("错误: 文件未找到")
exit(0)
cv.imshow('input', img)
# 将图像转换为灰度图
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# 将图像转换为二值图
_, bw = cv.threshold(gray, 0,255, cv.THRESH_OTSU)
cv.imshow('bw', bw)
contours, _ = cv.findContours(bw, cv.RETR_LIST, cv.CHAIN_APPROX_NONE)
# 遍历所有轮廓
for i, c in enumerate(contours):
# 计算每个轮廓的面积
area = cv.contourArea(c)
# 忽略过小或过大的轮廓
if area < 3700 or 100000 < area:
continue
# 为了可视化目的,绘制每个轮廓
cv.drawContours(img, contours, i, (0, 0, 255), 2)
# 获取每个形状的方向
getOrientation(c, img)
# 显示输出图像
cv.imshow('out', img)
cv.waitKey(0)
cv.destroyAllWindows()
# 将输出图像保存到当前目录
五,效果:
原图:
结果图: