You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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()
问题分析与修正方案

核心问题点

  1. 质心计算错误:原代码直接对彩色图像计算质心,背景白色像素会干扰质心位置,导致质心偏离椭圆实际中心,必须用二值化后的图像(仅保留椭圆区域)计算质心。
  2. 角度计算逻辑错误:
    • 图像坐标系中,row对应y轴、col对应x轴,原代码斜率计算的分子分母颠倒,导致角度偏差;
    • math.atan仅能返回-90°~90°的角度,无法覆盖所有象限,应使用math.atan2计算任意象限的角度并转换为度数。
  3. 距离计算的坐标对应错误: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 07:00:55