opencv-python实战项目十四:pca计算回形针方向


一,简介:

在图像处理和计算机视觉领域,曲别针方向的计算是一项具有挑战性的任务,它涉及到形状识别、轮廓提取和几何分析等多个技术环节。本文将详细介绍如何运用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()

# 将输出图像保存到当前目录

五,效果:

原图:
在这里插入图片描述
结果图:
在这里插入图片描述

  • 10
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值