当前位置: 首页 > news >正文

【Python】绘制椭圆眼睛跟随鼠标交互算法配图详解

Python绘制椭圆眼睛跟随鼠标交互算法配图详解

摘要

本文详细讲解如何使用Python绘制椭圆眼睛跟随鼠标交互算法的配图,包括仿射变换原理、边界条件分析和完整算法演示。通过matplotlib和numpy库,我们将生成专业的可视化图像,帮助理解这一计算机图形学算法。

环境准备

首先安装所需的Python包:

pip install matplotlib numpy pillow

完整代码实现

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Ellipse, Circle
import matplotlib.patches as patches
import os
from matplotlib.animation import FuncAnimation# 设置中文字体(可选)
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False# 创建专门的图像文件夹
image_folder = "processing_images"
if not os.path.exists(image_folder):os.makedirs(image_folder)
print(f"图像将保存在: {os.path.abspath(image_folder)}")def get_save_path(filename):"""生成完整的保存路径"""return os.path.join(image_folder, filename)

代码详解:基础设置

知识点讲解

  • matplotlib.pyplot 是Python中最常用的绘图库,提供类似MATLAB的绘图接口
  • numpy 是科学计算基础库,用于高效的数组操作
  • matplotlib.patches 包含各种形状的绘制工具,如椭圆、圆形等
  • FuncAnimation 用于创建动画效果
  • rcParams 用于设置matplotlib的全局参数,如字体等

第一部分:椭圆到圆的映射关系图

在这里插入图片描述

def plot_ellipse_to_circle_mapping():"""图1:椭圆坐标系和圆形坐标系的映射关系功能说明:- 展示原始椭圆坐标系和变换后圆形坐标系的关系- 标注关键参数a(半长轴)和b(半短轴)- 显示仿射变换的基本概念"""# 创建画布和子图fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))# 参数设置a = 60  # 椭圆半长轴(x轴方向)b = 40  # 椭圆半短轴(y轴方向)xe, ye = 0, 0  # 椭圆中心坐标# 生成椭圆和圆的边界点# 使用参数方程表示椭圆和圆theta = np.linspace(0, 2*np.pi, 100)  # 0到2π的100个等分点x_ellipse = xe + a * np.cos(theta)    # 椭圆x坐标:x = xe + a*cos(θ)y_ellipse = ye + b * np.sin(theta)    # 椭圆y坐标:y = ye + b*sin(θ)x_circle = a * np.cos(theta)          # 圆x坐标(变换后)y_circle = a * np.sin(theta)          # 圆y坐标(变换后)# 子图1:原始椭圆坐标系ax1.plot(x_ellipse, y_ellipse, 'b-', linewidth=2, label='椭圆边界')# 绘制坐标轴ax1.axhline(y=0, color='k', linestyle='-', linewidth=0.5)  # x轴ax1.axvline(x=0, color='k', linestyle='-', linewidth=0.5)  # y轴# 标注参数ax1.text(a/2, 0, 'a', fontsize=12, ha='center', va='top')      # 标注半长轴aax1.text(0, b/2, 'b', fontsize=12, ha='left', va='center')     # 标注半短轴bax1.set_xlabel('x')ax1.set_ylabel('y')ax1.set_title('原始椭圆坐标系')ax1.grid(True, alpha=0.3)ax1.axis('equal')  # 保证坐标轴比例相等ax1.set_xlim(-a-10, a+10)ax1.set_ylim(-b-10, b+10)# 子图2:变换后的圆形坐标系ax2.plot(x_circle, y_circle, 'r-', linewidth=2, label='圆形边界')ax2.axhline(y=0, color='k', linestyle='-', linewidth=0.5)ax2.axvline(x=0, color='k', linestyle='-', linewidth=0.5)# 标注半径ax2.text(a/2, a/2, f'半径 = a = {a}', fontsize=12)ax2.set_xlabel('X')ax2.set_ylabel('Y')ax2.set_title('变换后圆形坐标系')ax2.grid(True, alpha=0.3)ax2.axis('equal')ax2.set_xlim(-a-10, a+10)ax2.set_ylim(-a-10, a+10)plt.suptitle('图1:椭圆到圆的仿射变换映射', fontsize=16, fontweight='bold')plt.tight_layout()plt.savefig(get_save_path('ellipse_to_circle_mapping.png'), dpi=300, bbox_inches='tight')plt.show()

代码详解:椭圆到圆映射

核心概念

  • 参数方程:椭圆使用参数方程表示,避免了复杂的隐式方程处理
  • np.linspace(0, 2*np.pi, 100) 生成0到2π的100个等分点,用于绘制平滑曲线
  • axis('equal') 确保x轴和y轴比例相同,避免图像变形

仿射变换原理

  • 通过缩放y坐标:Y = (a/b) * (y - ye) 将椭圆变换为圆形
  • 变换后的圆半径为原始椭圆的半长轴a

第二部分:仿射变换过程示意图

在这里插入图片描述

def plot_affine_transformation_process():"""图2:仿射变换过程示意图功能说明:- 显示网格在变换前后的变化- 展示变换公式和逆变换公式- 直观展示仿射变换对几何形状的影响"""fig = plt.figure(figsize=(10, 8))# 参数设置a = 60b = 40# 创建原始网格# 在椭圆范围内创建均匀分布的网格点x_orig = np.linspace(-a, a, 15)y_orig = np.linspace(-b, b, 15)x_orig, y_orig = np.meshgrid(x_orig, y_orig)  # 生成网格坐标# 应用仿射变换到网格x_trans = x_orig                    # x坐标不变y_trans = (a/b) * y_orig           # y坐标按比例缩放# 椭圆和圆的边界theta = np.linspace(0, 2*np.pi, 100)x_ellipse = a * np.cos(theta)y_ellipse = b * np.sin(theta)x_circle = a * np.cos(theta)y_circle = a * np.sin(theta)# 子图1:原始椭圆和网格ax1 = plt.subplot(2, 2, 1)# 绘制网格线for i in range(x_orig.shape[0]):ax1.plot(x_orig[i, :], y_orig[i, :], 'b-', linewidth=0.5, alpha=0.7)for i in range(x_orig.shape[1]):ax1.plot(x_orig[:, i], y_orig[:, i], 'b-', linewidth=0.5, alpha=0.7)ax1.plot(x_ellipse, y_ellipse, 'r-', linewidth=3, label='椭圆边界')ax1.set_title('原始椭圆和网格')ax1.set_xlabel('x')ax1.set_ylabel('y')ax1.grid(True, alpha=0.3)ax1.axis('equal')# 子图2:变换后网格ax2 = plt.subplot(2, 2, 2)for i in range(x_trans.shape[0]):ax2.plot(x_trans[i, :], y_trans[i, :], 'b-', linewidth=0.5, alpha=0.7)for i in range(x_trans.shape[1]):ax2.plot(x_trans[:, i], y_trans[:, i], 'b-', linewidth=0.5, alpha=0.7)ax2.plot(x_circle, y_circle, 'r-', linewidth=3, label='圆形边界')ax2.set_title('仿射变换后:椭圆变为圆')ax2.set_xlabel('X')ax2.set_ylabel('Y')ax2.grid(True, alpha=0.3)ax2.axis('equal')# 子图3:变换公式ax3 = plt.subplot(2, 2, 3)ax3.axis('off')  # 关闭坐标轴# 显示数学公式ax3.text(0.1, 0.8, '仿射变换公式:', fontsize=14, fontweight='bold')ax3.text(0.1, 0.6, r'$k = \frac{a}{b}$', fontsize=16)ax3.text(0.1, 0.4, r'$X = x - x_e$', fontsize=16)ax3.text(0.1, 0.2, r'$Y = k \cdot (y - y_e)$', fontsize=16)# 子图4:逆变换公式ax4 = plt.subplot(2, 2, 4)ax4.axis('off')ax4.text(0.1, 0.8, '逆变换公式:', fontsize=14, fontweight='bold')ax4.text(0.1, 0.6, r'$x = X + x_e$', fontsize=16)ax4.text(0.1, 0.4, r'$y = \frac{Y}{k} + y_e$', fontsize=16)plt.suptitle('图2:仿射变换过程示意图', fontsize=16, fontweight='bold')plt.tight_layout()plt.savefig(get_save_path('affine_transformation_process.png'), dpi=300, bbox_inches='tight')plt.show()

代码详解:仿射变换过程

网格生成技术

  • np.meshgrid() 从一维数组生成二维网格坐标,是绘制3D曲面和2D网格的基础
  • 通过遍历网格点绘制网格线,展示坐标变换效果

数学公式显示

  • 使用LaTeX语法在matplotlib中显示数学公式
  • r'$...$' 中的r表示原始字符串,避免转义字符问题
  • LaTeX语法可以显示复杂的数学符号和公式

第三部分:眼珠位置边界条件图

在这里插入图片描述

def plot_pupil_position_boundary():"""图3:眼珠位置计算边界条件功能说明:- 展示安全区域内外的眼珠位置计算- 分别在变换后坐标系和原始椭圆坐标系中显示- 演示边界条件判断逻辑"""fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))# 参数设置a = 60b = 40pupil_scale = 0.3  # 眼珠相对于眼睛大小的比例rp = a * pupil_scale  # 眼珠半径xe, ye = 0, 0# 生成测试点:在不同角度和距离上测试num_points = 8angles = np.linspace(0, 2*np.pi, num_points+1)[:-1]  # 8个等分角度distances = [a*0.7, a*1.2]  # 内外两个测试距离# 圆形和椭圆边界theta = np.linspace(0, 2*np.pi, 100)x_circle = a * np.cos(theta)y_circle = a * np.sin(theta)x_ellipse = xe + a * np.cos(theta)y_ellipse = ye + b * np.sin(theta)# 安全区域边界(眼珠不能超过的边界)safe_circle_x = (a - rp) * np.cos(theta)safe_circle_y = (a - rp) * np.sin(theta)safe_ellipse_x = (a - rp) * np.cos(theta)safe_ellipse_y = (b/a) * (a - rp) * np.sin(theta)  # 注意逆变换# 使用颜色映射区分不同角度colors = plt.cm.tab10(np.linspace(0, 1, num_points))# 子图1:变换后坐标系ax1.plot(x_circle, y_circle, 'k-', linewidth=2, label='圆形边界 (半径=a)')ax1.plot(safe_circle_x, safe_circle_y, 'g--', linewidth=2, color=(0, 0.5, 0), label='安全区域边界')# 遍历所有测试点for i, angle in enumerate(angles):for j, dist in enumerate(distances):# 鼠标位置(变换后坐标系)Xm = dist * np.cos(angle)Ym = dist * np.sin(angle)# 计算眼珠位置d = np.sqrt(Xm**2 + Ym**2)  # 到圆心的距离if d <= a - rp:# 安全区域内:眼珠直接跟随鼠标Xp, Yp = Xm, Ymmarker = 'o'  # 圆形标记else:# 安全区域外:眼珠停留在边界上u = np.array([Xm/d, Ym/d])  # 单位方向向量Xp, Yp = u * (a - rp)marker = 's'  # 方形标记# 绘制鼠标位置label = Noneif i == 0:  # 只为第一个角度添加图例标签if j == 0:label = '鼠标在安全区内'else:label = '鼠标在安全区外'ax1.plot(Xm, Ym, marker, color=colors[i], markersize=8, linewidth=2, label=label)# 绘制眼珠pupil_circle_x = rp * np.cos(theta) + Xppupil_circle_y = rp * np.sin(theta) + Ypax1.plot(pupil_circle_x, pupil_circle_y, '-', color=colors[i], linewidth=1.5)# 绘制连线(仅在安全区外)if d > a - rp:ax1.plot([0, Xm], [0, Ym], ':', color=colors[i], linewidth=1)ax1.text(0, a+5, '安全区域外', ha='center', fontsize=12)ax1.text(0, (a-rp)/2, '安全区域内', ha='center', fontsize=12)ax1.set_xlabel('X')ax1.set_ylabel('Y')ax1.set_title('变换后坐标系中的眼珠位置计算')ax1.grid(True, alpha=0.3)ax1.axis('equal')ax1.legend(loc='upper left', bbox_to_anchor=(1, 1))# 子图2:原始椭圆坐标系ax2.plot(x_ellipse, y_ellipse, 'k-', linewidth=2, label='椭圆边界')ax2.plot(safe_ellipse_x, safe_ellipse_y, 'g--', linewidth=2, color=(0, 0.5, 0), label='安全区域边界')for i, angle in enumerate(angles):for j, dist in enumerate(distances):# 变换后坐标系中的位置Xm = dist * np.cos(angle)Ym = dist * np.sin(angle)# 计算眼珠位置(变换后坐标系)d = np.sqrt(Xm**2 + Ym**2)if d <= a - rp:Xp, Yp = Xm, Ymmarker = 'o'else:u = np.array([Xm/d, Ym/d])Xp, Yp = u * (a - rp)marker = 's'# 逆变换到椭圆坐标系xm_ellipse = Xm + xeym_ellipse = (b/a) * Ym + yexp_ellipse = Xp + xeyp_ellipse = (b/a) * Yp + ye# 绘制鼠标位置label = Noneif i == 0:if j == 0:label = '鼠标在安全区内'else:label = '鼠标在安全区外'ax2.plot(xm_ellipse, ym_ellipse, marker, color=colors[i], markersize=8, linewidth=2, label=label)# 绘制眼珠位置(椭圆)pupil_ellipse_x = xp_ellipse + rp * np.cos(theta)pupil_ellipse_y = yp_ellipse + (b/a) * rp * np.sin(theta)ax2.plot(pupil_ellipse_x, pupil_ellipse_y, '-', color=colors[i], linewidth=1.5)# 绘制连线(仅在安全区外)if d > a - rp:ax2.plot([xe, xm_ellipse], [ye, ym_ellipse], ':', color=colors[i], linewidth=1)ax2.set_xlabel('x')ax2.set_ylabel('y')ax2.set_title('逆变换回椭圆坐标系')ax2.grid(True, alpha=0.3)ax2.axis('equal')ax2.legend(loc='upper left', bbox_to_anchor=(1, 1))plt.suptitle('图3:眼珠位置计算的边界条件', fontsize=16, fontweight='bold')plt.tight_layout()plt.savefig(get_save_path('pupil_position_boundary.png'), dpi=300, bbox_inches='tight')plt.show()

代码详解:边界条件分析

颜色映射技术

  • plt.cm.tab10 提供10种区分度良好的颜色
  • np.linspace(0, 1, num_points) 在0到1之间生成等分数值,用于颜色选择

边界条件算法

  • 安全区域边界:a - rp,确保眼珠不超出眼睛边界
  • 单位向量计算:u = [Xm/d, Ym/d],用于确定眼珠移动方向
  • 距离计算:d = sqrt(Xm² + Ym²),判断鼠标是否在安全区域内

图例优化

  • 使用条件判断避免重复的图例标签
  • bbox_to_anchor=(1, 1) 将图例放置在子图外部

第四部分:完整算法演示动画

在这里插入图片描述

def plot_complete_algorithm_demonstration():"""图4:完整算法演示动画功能说明:- 动态展示鼠标移动时眼珠位置的变化- 实时显示仿射变换和逆变换过程- 直观演示边界条件的作用"""fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))# 参数设置a = 60b = 40pupil_scale = 0.3rp = a * pupil_scalexe, ye = 0, 0# 圆形和椭圆边界theta = np.linspace(0, 2*np.pi, 100)x_circle = a * np.cos(theta)y_circle = a * np.sin(theta)x_ellipse = xe + a * np.cos(theta)y_ellipse = ye + b * np.sin(theta)# 安全区域边界safe_circle_x = (a - rp) * np.cos(theta)safe_circle_y = (a - rp) * np.sin(theta)# 初始化图形元素# 子图1:变换后坐标系ax1.plot(x_circle, y_circle, 'k-', linewidth=2, label='圆形边界')ax1.plot(safe_circle_x, safe_circle_y, 'g--', linewidth=2, label='安全区域边界')mouse_dot1, = ax1.plot([], [], 'ro', markersize=8, label='鼠标位置')pupil1, = ax1.plot([], [], 'b-', linewidth=2, label='眼珠')connection1, = ax1.plot([], [], 'r:', linewidth=1)ax1.set_xlabel('X')ax1.set_ylabel('Y')ax1.set_title('变换后坐标系')ax1.grid(True, alpha=0.3)ax1.axis('equal')ax1.legend()ax1.set_xlim(-a-10, a+10)ax1.set_ylim(-a-10, a+10)# 子图2:原始椭圆坐标系ax2.plot(x_ellipse, y_ellipse, 'k-', linewidth=2, label='椭圆边界')mouse_dot2, = ax2.plot([], [], 'ro', markersize=8, label='鼠标位置')pupil2, = ax2.plot([], [], 'b-', linewidth=2, label='眼珠')connection2, = ax2.plot([], [], 'r:', linewidth=1)ax2.set_xlabel('x')ax2.set_ylabel('y')ax2.set_title('原始椭圆坐标系')ax2.grid(True, alpha=0.3)ax2.axis('equal')ax2.legend()ax2.set_xlim(-a-10, a+10)ax2.set_ylim(-b-10, b+10)plt.suptitle('图4:椭圆眼睛跟随鼠标算法演示', fontsize=16, fontweight='bold')def animate(frame):"""动画更新函数参数:frame - 当前动画帧数,用于计算鼠标位置功能:- 根据帧数计算模拟鼠标位置- 应用仿射变换计算眼珠位置- 更新两个子图的图形元素"""# 生成模拟鼠标位置(Lissajous曲线)t = frame * 0.1mouse_x = 80 * np.cos(t)           # x坐标随时间变化mouse_y = 60 * np.sin(2*t)         # y坐标以2倍频率变化# 计算眼珠位置(核心算法)k = a / b  # 缩放因子Xm = mouse_x - xe  # 变换后x坐标Ym = k * (mouse_y - ye)  # 变换后y坐标d = np.sqrt(Xm**2 + Ym**2)  # 到圆心距离# 边界条件判断if d <= a - rp:Xp, Yp = Xm, Ym  # 安全区域内:直接跟随else:u = np.array([Xm/d, Ym/d])  # 单位方向向量Xp, Yp = u * (a - rp)  # 安全区域外:停留在边界# 逆变换回椭圆坐标系xp = Xp + xeyp = Yp / k + ye# 更新变换后坐标系图形pupil_circle_x = rp * np.cos(theta) + Xppupil_circle_y = rp * np.sin(theta) + Ypmouse_dot1.set_data([Xm], [Ym])pupil1.set_data(pupil_circle_x, pupil_circle_y)# 显示/隐藏连线if d > a - rp:connection1.set_data([0, Xm], [0, Ym])connection1.set_visible(True)else:connection1.set_visible(False)# 更新原始椭圆坐标系图形pupil_ellipse_x = xp + rp * np.cos(theta)pupil_ellipse_y = yp + (b/a) * rp * np.sin(theta)mouse_dot2.set_data([mouse_x], [mouse_y])pupil2.set_data(pupil_ellipse_x, pupil_ellipse_y)if d > a - rp:connection2.set_data([xe, mouse_x], [ye, mouse_y])connection2.set_visible(True)else:connection2.set_visible(False)return mouse_dot1, pupil1, connection1, mouse_dot2, pupil2, connection2# 创建动画anim = FuncAnimation(fig, animate, frames=100, interval=100, blit=True)plt.tight_layout()# 保存动画(需要安装pillow)try:anim.save(get_save_path('algorithm_demonstration.gif'), writer='pillow', fps=10)print("动画已保存为 algorithm_demonstration.gif")except Exception as e:print(f"无法保存动画: {e}")print("请安装pillow: pip install pillow")plt.show()return anim

代码详解:动画技术

FuncAnimation使用

  • FuncAnimation(fig, animate, frames=100, interval=100, blit=True)
    • fig: 动画所在的图形对象
    • animate: 更新函数,每帧调用
    • frames: 总帧数
    • interval: 帧间隔(毫秒)
    • blit=True: 只重绘变化的部分,提高性能

Lissajous曲线

  • 使用参数方程生成复杂的鼠标移动轨迹
  • mouse_x = 80 * cos(t), mouse_y = 60 * sin(2*t)
  • 产生优美的曲线运动,更好地演示算法效果

动画优化

  • 使用set_visible()控制连线的显示/隐藏
  • blit=True 只更新变化的图形元素,提高渲染效率

主程序执行

# 运行所有绘图函数
if __name__ == "__main__":print("生成椭圆眼睛跟随鼠标交互算法配图...")print("1. 生成椭圆到圆的映射图...")plot_ellipse_to_circle_mapping()print("2. 生成仿射变换过程图...")plot_affine_transformation_process()print("3. 生成眼珠位置边界条件图...")plot_pupil_position_boundary()print("4. 生成完整算法演示动画...")anim = plot_complete_algorithm_demonstration()print("所有配图生成完成!")print(f"图像保存在: {os.path.abspath(image_folder)}")

不常用知识点详解

1. 颜色映射(Color Map)

colors = plt.cm.tab10(np.linspace(0, 1, num_points))
  • plt.cm 包含多种颜色映射方案
  • tab10 提供10种区分度良好的颜色
  • np.linspace(0, 1, num_points) 在0-1范围内生成等分数值
  • 结果是一个RGBA颜色数组,每个元素代表一个颜色

2. 网格生成(Meshgrid)

x = np.linspace(-a, a, 15)
y = np.linspace(-b, b, 15)
x_grid, y_grid = np.meshgrid(x, y)
  • 从一维坐标数组生成二维网格坐标
  • 常用于3D曲面绘图和2D网格显示
  • 返回的x_grid和y_grid都是二维数组

3. 动画技术中的blit优化

anim = FuncAnimation(..., blit=True)
  • blit=True 只重绘变化的图形元素,大幅提高性能
  • 需要更新函数返回所有需要重绘的图形对象
  • 对于复杂动画,性能提升非常明显

4. LaTeX数学公式渲染

ax.text(0.1, 0.6, r'$k = \frac{a}{b}$', fontsize=16)
  • 使用r''原始字符串避免转义字符问题
  • $...$ 包围LaTeX数学公式
  • 支持分数、上下标、希腊字母等复杂数学符号
http://www.dtcms.com/a/506699.html

相关文章:

  • 【C++模版进阶】如何理解非类型模版参数、特化与分离编译?
  • 字符串专题总结:从模拟运算到模板掌握
  • 【Java链表】从概念结构到单向链表创建,增删查改全流程实战
  • 从C10K到Reactor:事件驱动,如何重塑高并发服务器的网络架构
  • 顺义做网站公司重庆企业网络推广软件
  • 淘宝怎么做网站郑州网站开发公
  • input + React自定义上传组件【可自定义拓展】
  • 「日拱一码」125 多层特征融合
  • 第六部分:VTK进阶(第164章 复合数据集 vtkMultiBlockDataSet 组织)
  • k8s(十一)HPA部署与使用
  • 【ReaLM】结合错误数据与课程学习 提升垂域效果
  • 通了网站建设宿迁网站定制
  • Git仓库推送到GitHub
  • 本地多语言切换具体操作代码
  • 济南建设主管部门网站短视频网站如何做推广
  • AWS US-East-1 区宕机
  • C语言——关机小程序(有system()和strcmp()函数的知识点)
  • php网站案例购物网页设计图片
  • golang面经7:interface相关
  • [Agent可视化] 配置系统 | 实现AI模型切换 | 热重载机制 | fsnotify库(go)
  • 【第7篇】引入低配大模型
  • 【Linux】Linux 进程信号核心拆解:pending/block/handler 三张表 + signal/alarm 实战
  • Java-154 深入浅出 MongoDB 用Java访问 MongoDB 数据库 从环境搭建到CRUD完整示例
  • 1.云计算与服务器基础
  • 基于Draw.io的实时协作架构设计与性能优化实践
  • 网站右侧固定标题怎么做深圳品牌馆设计装修公司
  • ASP.NET MVC 前置基础:宿主环境 HttpRuntime 管道,从部署到流程拆透(附避坑指南)
  • 北京单位网站建设培训俱乐部网站方案
  • 如何将一加手机的照片传输到笔记本电脑?
  • 手机群控软件如何构建高效稳定的运营环境?