如何用Python图像分析准确计算椭圆长半轴与X轴的夹角?
问题
我需要求解椭圆长半轴与X轴的夹角,思路为找到椭圆长轴、获取长轴方程后通过斜率的反正切值计算角度。Python实现步骤如下:
- 加载图像并转换为灰度图;
- 通过阈值对灰度图进行二值化处理(阈值以上像素为白色,以下为黑色);
- 遍历二值化图像的每个像素,若为黑色(值为0)则计算其与椭圆质心的欧氏距离,取最大距离对应的像素与质心确定长轴,进而计算夹角。
但我用PPT制作了一个约25°的旋转椭圆(如下图),计算结果却约为30°,无法定位问题根源,请求技术帮助。

当前计算结果图:
原始实现代码:
import cv2 import numpy as np from scipy import ndimage import matplotlib.pyplot as plt import math def euclidean_distance(a, row, col): """ Calculates the Euclidean distance between two points. Args: a (tuple): Center of mass (x, y). row (int): Row index of the pixel. col (int): Column index of the pixel. Returns: float: Euclidean distance. """ return np.sqrt((a[0] - col) ** 2 + (a[1] - row) ** 2) def angle(a, row, col): """ Calculates the angle between the major axis and the x-axis. Args: a (tuple): Center of mass (x, y). row (int): Row index of the pixel. col (int): Column index of the pixel. Returns: float: Angle in degrees. """ slope = (a[0] - col) / (a[1] - row) return math.atan(slope) # Load the image image_path = "/Users/yahya2/Desktop/xy1.png" img = cv2.imread(image_path) a = ndimage.center_of_mass(img) # Convert the image to grayscale gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # Binarize the grayscale image _, img_bin = cv2.threshold(gray, 128, 255, cv2.THRESH_BINARY) # Get the dimensions of the image rows, cols = img_bin.shape max_length = 0 max_row, max_col = 0, 0 # Loop through each pixel in the image for row in range(rows): for col in range(cols): # Check if the pixel is black (0) if img_bin[row, col] == 0: length = euclidean_distance(a, row, col) if length > max_length: max_length = length max_row, max_col = row, col # Calculate the angle major_axis_angle = angle(a, max_row, max_col) print(f"Major axis angle (degrees): {major_axis_angle:.2f}") # Visualize the result fig, ax = plt.subplots(figsize=(8, 8)) ax.imshow(img, cmap='gray') plt.scatter(max_col, max_row, color='red', marker='x', s=100) plt.show()
问题分析与修正方案
核心问题点
- 质心计算错误:原代码直接对彩色图像计算质心,背景白色像素会干扰质心位置,导致质心偏离椭圆实际中心,必须用二值化后的图像(仅保留椭圆区域)计算质心。
- 角度计算逻辑错误:
- 图像坐标系中,
row对应y轴、col对应x轴,原代码斜率计算的分子分母颠倒,导致角度偏差; math.atan仅能返回-90°~90°的角度,无法覆盖所有象限,应使用math.atan2计算任意象限的角度并转换为度数。
- 图像坐标系中,
- 距离计算的坐标对应错误:
ndimage.center_of_mass返回的是(行, 列)即(y, x)格式,原代码的距离计算中坐标对应关系混乱。
修正后的代码
import cv2 import numpy as np from scipy import ndimage import matplotlib.pyplot as plt import math def euclidean_distance(center, row, col): # 质心格式为(y, x),像素坐标为(row=y, col=x),对应计算欧氏距离 return np.sqrt((center[1] - col) ** 2 + (center[0] - row) ** 2) def calculate_angle(center, row, col): # 计算质心到目标点的向量差:(x差, y差) dx = col - center[1] dy = row - center[0] # atan2返回弧度值,转换为度数后调整到0~180°范围(椭圆长轴无方向区分) angle_rad = math.atan2(dy, dx) angle_deg = math.degrees(angle_rad) if angle_deg < 0: angle_deg += 180 return angle_deg # 加载图像并转灰度 image_path = "/Users/yahya2/Desktop/xy1.png" img = cv2.imread(image_path) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化:确保椭圆为黑色(0),背景为白色(255) _, img_bin = cv2.threshold(gray, 128, 255, cv2.THRESH_BINARY) # 用二值化图像计算质心,避免背景干扰 center = ndimage.center_of_mass(img_bin) rows, cols = img_bin.shape max_length = 0 max_row, max_col = 0, 0 # 遍历像素寻找离质心最远的点(长轴端点) for row in range(rows): for col in range(cols): if img_bin[row, col] == 0: dist = euclidean_distance(center, row, col) if dist > max_length: max_length = dist max_row, max_col = row, col # 计算并输出角度 major_axis_angle = calculate_angle(center, max_row, max_col) print(f"长半轴与X轴夹角(度数): {major_axis_angle:.2f}") # 可视化结果:标注质心、长轴端点及长轴 fig, ax = plt.subplots(figsize=(8, 8)) ax.imshow(img, cmap='gray') plt.scatter(center[1], center[0], color='blue', marker='o', s=100, label='质心') plt.scatter(max_col, max_row, color='red', marker='x', s=100, label='长轴端点') plt.plot([center[1], max_col], [center[0], max_row], color='green', linewidth=2, label='长轴') plt.legend() plt.show()
更可靠的替代方案
直接使用OpenCV的椭圆拟合函数cv2.fitEllipse,可以跳过手动找端点的步骤,直接得到长轴角度,精度更高:
# 提取椭圆轮廓 contours, _ = cv2.findContours(img_bin, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 拟合椭圆(需确保轮廓点数量≥5) if len(contours) > 0 and len(contours[0]) >= 5: ellipse = cv2.fitEllipse(contours[0]) # ellipse[2]即为长轴与X轴的夹角(0~180°) print(f"拟合椭圆得到的长轴夹角: {ellipse[2]:.2f}")
内容的提问来源于stack exchange,提问作者yahya ashraf
相关产品推荐
相关产品推荐

