首页
关于道锋
友情链接
公告栏
麟图图床
麟云文件
麟云证书
BH5UVN
Search
1
使用ReDroid打造自己的云手机
5,065 阅读
2
Cloudflare SAAS 接入自选教程
2,774 阅读
3
CloudFront CDN配置教程
2,347 阅读
4
Frpc使用XTCP不通过服务器传输
2,206 阅读
5
兽装曲腿制作文档
2,205 阅读
默认
科学
热力学
Furry
小说
星河野望
手工制作
道具制作
音影
图像工具
计算机
渗透
硬件
编程
网络
记录
AI人工智能
CVE
软件工具
装机教程
C/C++
C#
Go
HTML5+JS+CSS
JAVA
Lua
Rust
PHP
Python2/3
Nodejs
编译
C/C++学习日志
Golang学习日志
Rust开发技巧
Rust学习日志
Rust开发教程
Nonebot2机器人框架
python开发教程
python开发技巧
Python学习日志
ai绘画
电子电路
电路设计
PCB打板
制作实战
无线电
摄影
运维
WEB
KVM云计算
docker
Ansible
代码管理
Kubernetes
Linux
MySQL
shell
集群
Zabbix
Prometheus
数据安全
Redis
istio
ELK
Nginx
Apache
Tomcat
Elasticsearch
Logstash
Kibana
测评
服务器
登录
Search
标签搜索
开源
源码
教程
服务器
环境搭建
摄影
rustlang
Rust
VS CODE
v2ray
bbr
加速
网络优化
拥塞控制
CloudFront教程
CF教程
AWS教程
CloudFront接入
Frpc
Frps
道锋潜鳞
累计撰写
447
篇文章
累计收到
129
条评论
首页
栏目
默认
科学
热力学
Furry
小说
星河野望
手工制作
道具制作
音影
图像工具
计算机
渗透
硬件
编程
网络
记录
AI人工智能
CVE
软件工具
装机教程
C/C++
C#
Go
HTML5+JS+CSS
JAVA
Lua
Rust
PHP
Python2/3
Nodejs
编译
C/C++学习日志
Golang学习日志
Rust开发技巧
Rust学习日志
Rust开发教程
Nonebot2机器人框架
python开发教程
python开发技巧
Python学习日志
ai绘画
电子电路
电路设计
PCB打板
制作实战
无线电
摄影
运维
WEB
KVM云计算
docker
Ansible
代码管理
Kubernetes
Linux
MySQL
shell
集群
Zabbix
Prometheus
数据安全
Redis
istio
ELK
Nginx
Apache
Tomcat
Elasticsearch
Logstash
Kibana
测评
服务器
页面
关于道锋
友情链接
公告栏
友人
iMin博客
特资啦!个人资源分享站
三石的记录
咬一口激动的鱼
中二病晚期の物語
奇梦博客
布丁の小窝
道麟笔记
迷失的小K
koto's Site
西西のBlog
锐冰龙小站
Nick的琐碎日常
渣渣120
猎空のBlog“
Suntの小破站
BG5VXJ-无线电
Abyss博客
麟图图床
麟云文件
麟云证书
BH5UVN
搜索到
3
篇与
的结果
2025-07-11
双眼动画路径生成:从物理模拟到串口通信的完整解决方案
双眼动画系统的实现:从物理模拟到串口通信的完整解决方案最近在做一个机器人眼睛动画项目,需要实现逼真的眼球运动和眨眼效果。经过一段时间的开发,完成了一个基于多线程的眼睛动画系统,支持实时的眼球运动模拟、自然的眨眼动画,以及通过串口与硬件设备进行通信。项目代码已经开源在 GitHub,这篇文章详细分析一下其中的技术实现。系统架构设计整个系统采用了三线程架构:一个动画更新线程负责核心的眼球运动和眨眼逻辑计算,两个输出线程分别处理左右眼的数据发送。这种设计的好处是将计算逻辑和I/O操作解耦,确保动画计算的稳定性不受串口通信延迟的影响。// 系统核心结构 typedef struct { float eyeOldX, eyeOldY; // 当前位置 float eyeNewX, eyeNewY; // 目标位置 bool inMotion; // 运动状态 uint32_t moveStartTimeMs; // 运动开始时间 uint32_t moveDurationMs; // 运动持续时间 BlinkState blinkState; // 眨眼状态 float eyeOpenness; // 眼睛开合度 } EyeState;使用了一个共享的EyeState结构体来维护眼睛的完整状态,通过std::mutex保护多线程访问,确保数据一致性。眼球运动算法实现眼球运动的核心思路是模拟人类眼球的真实运动模式:大幅度的跳跃式移动(扫视)和小幅度的微调(微眼跳)。算法设计了一个状态机来控制这两种运动模式的切换。路径生成的数学基础在深入具体算法之前,先要理解整个坐标系统的设计。系统使用了一个以MAP_RADIUS为半径的圆形映射区域,其中MAP_RADIUS = 240像素。这个设计的巧妙之处在于:#define SCREEN_WIDTH 240 #define SCREEN_HEIGHT 240 #define MAP_RADIUS 240实际的可视区域被限制在一个更小的圆形范围内,计算公式为:float effective_radius = (MAP_RADIUS * 2 - SCREEN_WIDTH * M_PI_2) * scale_factor;这里的M_PI_2(π/2)是一个关键的修正因子,它考虑了从方形屏幕到圆形映射的几何变换。通过这种方式,我们可以确保生成的所有眼球位置都在有效的显示范围内,避免眼球"跑出屏幕"的情况。扫视运动(Saccadic Movement)的路径生成扫视是眼球的主要运动模式,特点是快速、大幅度的跳跃。路径生成算法采用了极坐标到直角坐标的转换方法,但用了一种更高效的实现:// 扫视运动的有效半径计算 float r = (MAP_RADIUS * 2 - SCREEN_WIDTH * M_PI_2) * 0.75f; // 首先在X轴上随机选择位置 float relativeX = random_float(-r, r); // 根据圆的方程计算Y轴的最大范围 float maxY = sqrtf(r * r - relativeX * relativeX); float relativeY = random_float(-maxY, maxY); // 转换为绝对坐标 state->eyeNewX = MAP_RADIUS + relativeX; state->eyeNewY = MAP_RADIUS + relativeY;这个算法的核心是圆的方程:x² + y² = r²。给定X坐标后,Y坐标的取值范围就确定了:y = ±√(r² - x²)。这种方法比传统的极坐标转换(x = r*cos(θ), y = r*sin(θ))更高效,因为避免了三角函数的计算。扫视运动的scale_factor = 0.75f意味着眼球的扫视范围被限制在理论最大范围的75%内,这样可以留出一些边界缓冲,让运动看起来更自然。运动持续时间设置为83-166毫秒,这个数值是基于人眼扫视的生理特性确定的:人类的快速扫视通常在50-200毫秒之间完成。微眼跳(Microsaccade)的增量路径算法微眼跳采用了完全不同的路径生成策略——增量式生成。与扫视的绝对位置生成不同,微眼跳是基于当前位置的相对偏移:// 微眼跳的范围计算(约为扫视范围的1/10) float r = (MAP_RADIUS * 2 - SCREEN_WIDTH * M_PI_2) * 0.07f; // 生成相对于当前位置的偏移量 float dx = random_float(-r, r); float h = sqrtf(r * r - dx * dx); float dy = random_float(-h, h); // 应用偏移到当前位置 state->eyeNewX = state->eyeOldX + dx; state->eyeNewY = state->eyeOldY + dy;这种设计的优势是微眼跳不会让眼球产生大幅度的"跳跃感",而是在当前注视点附近进行微小的调整。0.07的缩放因子意味着微眼跳的范围约为扫视范围的1/10,持续时间也更短(7-25毫秒),这完全符合人眼微眼跳的生理特征。运动状态机的路径决策逻辑路径生成的决策逻辑通过一个复杂的状态机实现,它需要在扫视和微眼跳之间做出智能选择:if ((t - state->lastSaccadeStopMs) > state->saccadeIntervalMs) { // 执行扫视运动 // ... 扫视路径生成代码 state->moveDurationMs = random_range(83, 166); state->saccadeIntervalMs = 0; // 标记需要重新计算下次扫视间隔 } else { // 执行微眼跳 // ... 微眼跳路径生成代码 state->moveDurationMs = random_range(7, 25); }这里的关键是saccadeIntervalMs的动态计算。每次扫视结束后,系统会根据当前的凝视缩放因子重新计算下次扫视的间隔时间:uint32_t scaledMaxGazeMs = static_cast<uint32_t>(maxGazeMs * gazeScaleFactor); state->saccadeIntervalMs = random_range(state->moveDurationMs, scaledMaxGazeMs);这种设计让眼球的运动模式更加自然:在大部分时间里进行微小的微眼跳调整,偶尔进行大幅度的扫视运动。运动插值的数学模型为了让眼球运动更加平滑,系统使用了一个特殊的缓动函数进行路径插值:float e = (float)dt / (float)scaledDurationMs; // 线性进度 [0, 1] float e2 = e * e; // e的平方 e = e2 * (3.0f - 2.0f * e); // 平滑步函数: 3e² - 2e³这个函数被称为"平滑步函数"(smoothstep),它的数学表达式是f(t) = 3t² - 2t³。这个函数有几个重要特性:f(0) = 0, f(1) = 1:确保插值在正确的边界f'(0) = 0, f'(1) = 0:保证运动开始和结束时速度为零中间段的导数为正:确保单调递增相比线性插值,这种缓动函数让眼球运动具有"加速-匀速-减速"的特征,更符合人眼的运动模式。边界检测和约束处理虽然路径生成算法理论上会保证所有点都在有效范围内,但在实际实现中,还是需要考虑边界情况的处理。特别是在微眼跳的增量计算中,当前位置靠近边界时,简单的偏移可能会导致越界。代码中通过预先计算有效半径的方式来避免这个问题,但在更复杂的应用场景中,可能需要额外的边界检测和坐标钳制逻辑:// 理论上需要的边界检测(当前代码中通过预计算避免了这个问题) float clamp_to_circle(float x, float y, float center_x, float center_y, float radius) { float dx = x - center_x; float dy = y - center_y; float distance = sqrtf(dx*dx + dy*dy); if (distance > radius) { float scale = radius / distance; return {center_x + dx*scale, center_y + dy*scale}; } return {x, y}; }参数化控制对路径生成的影响用户可调节的参数对路径生成有直接影响:凝视缩放因子(gazeScaleFactor):影响扫视的触发频率,值越大,扫视间隔越长移动速度因子(moveSpeedFactor):通过除法运算延长运动持续时间,从而减慢运动速度// 速度控制的实现 uint32_t scaledDurationMs = static_cast<uint32_t>(state->moveDurationMs / moveSpeedFactor);这种参数化设计让同一套路径生成算法可以适应不同的应用需求,从慢速的冥想状态到快速的警觉状态都能很好地模拟。眨眼动画系统眨眼动画采用了状态机模式,包含三个状态:未眨眼、闭合中、打开中。这种设计可以精确控制眨眼的每个阶段,实现更自然的效果。typedef enum { NOT_BLINKING = 0, BLINK_CLOSING = 1, BLINK_OPENING = 2 } BlinkState;眨眼的触发机制采用了随机间隔,基础间隔为2-6秒,这个数值参考了人类的平均眨眼频率。眨眼动作的持续时间分为两个阶段:闭合阶段50-100毫秒,打开阶段是闭合阶段的2倍,这样可以模拟真实眨眼的不对称特性。参数化控制系统为了让系统更加灵活,设计了一套完整的参数化控制系统。用户可以通过命令行参数调整各种行为:--gazescale: 控制凝视时间,值越大眼睛移动频率越低--movespeed: 控制眼球移动速度,值越小移动越慢--blinkfreq: 控制眨眼频率,值越小眨眼越少--blinkspeed: 控制眨眼速度,值越小眨眼动作越慢这些参数通过乘法因子的方式应用到相应的计算中,比如移动速度的控制:uint32_t scaledDurationMs = static_cast<uint32_t>(state->moveDurationMs / moveSpeedFactor);串口通信协议系统使用JSON格式进行数据传输,这样既保证了数据的结构化,又具有很好的可读性和扩展性。数据包格式如下:{ "req": "c", "d": { "x": 0.25, "y": -0.33, "l": 0.95 } }其中req字段表示请求类型,d字段包含实际的眼睛数据。坐标系统使用了归一化的浮点数,范围从-1.0到1.0,中心点为(0,0)。这种设计的好处是与具体的硬件分辨率无关,接收端可以根据自己的需要进行缩放。串口配置采用了115200波特率,8位数据位,无校验位,1个停止位。为了提高通信的可靠性,在串口初始化时禁用了各种流控制和特殊字符处理:tty.c_cflag &= ~CRTSCTS; // 禁用硬件流控制 tty.c_iflag &= ~(IXON | IXOFF | IXANY); // 禁用软件流控制 tty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); // 禁用规范模式时间管理和同步系统使用了clock_gettime(CLOCK_MONOTONIC)来获取高精度的时间戳,这比gettimeofday()更适合做时间间隔计算,因为它不受系统时间调整的影响。帧率控制采用了固定时间步长的方式,设定为40fps(25ms每帧)。这个频率对于眼球动画来说已经足够流畅,同时也不会给系统带来过大的负担:#define FPS 40 #define FRAME_DURATION_MS (1000 / FPS) uint32_t frameDuration = get_time_ms() - frameStart; if (frameDuration < FRAME_DURATION_MS) { delay_ms(FRAME_DURATION_MS - frameDuration); }错误处理和容错机制在串口通信方面,实现了比较完善的错误处理机制。当写入失败时,系统会尝试重新建立连接,这对于长时间运行的设备来说非常重要:if (bytes_written < 0) { close(fd); fd = setup_serial_port(port_name, BAUD_RATE); if (fd < 0) { std::cerr << eye_name << "重新连接串口失败,线程退出" << std::endl; break; } }同时,系统还实现了优雅的退出机制,通过信号处理函数捕获Ctrl+C和SIGTERM信号,确保所有线程能够正常退出并释放资源。性能优化考虑在多线程设计中,为了减少锁竞争,动画更新线程和数据发送线程的工作频率是一致的,都是40fps。数据发送线程每次都会完整复制一份眼睛状态,这样可以最小化临界区的大小。另外,所有的浮点数计算都使用了单精度float,这在保证精度的同时也提供了更好的性能。对于眼球动画这样的应用场景,单精度的精度已经完全足够。扩展性设计系统的设计考虑了很好的扩展性。比如左右眼交换功能的实现,通过一个简单的布尔值就可以改变数据的发送目标,这对于一些特殊的硬件配置很有用。数据格式的设计也很灵活,JSON格式可以很容易地添加新的字段,比如瞳孔大小、眼睛颜色等。坐标系统的归一化设计也使得系统可以适应不同分辨率的显示设备。总结这个眼睛动画系统在技术上实现了几个关键点:基于生理学原理的运动模型、稳定的多线程架构、灵活的参数化控制、可靠的串口通信。整个系统的代码结构清晰,模块化程度高,既可以作为独立的眼睛动画服务使用,也可以很容易地集成到其他机器人项目中。从开发的角度来看,这个项目涉及了实时系统设计、数值计算、串口通信、多线程编程等多个技术领域,是一个很好的综合性项目。如果你对机器人动画或者实时系统开发感兴趣,可以从这个项目中学到不少东西。完整的源代码和详细的使用说明都可以在 GitHub仓库 中找到,欢迎大家fork和提issue讨论。
2025年07月11日
11 阅读
0 评论
0 点赞
2024-10-29
Raspi Zero2W USB+TF卡扩展板项目
对Raspi Zero2W进行多功能扩展,可使用emmc或tf卡二选一启动,也附带了usb扩展坞和ups功能,通过弹簧顶针和z2w上的测试点链接
2024年10月29日
141 阅读
0 评论
0 点赞
2021-09-30
兽装曲腿制作文档
设计图更新中:{x} 英文原版PDF下载:隐藏内容,请前往内页查看详情{ } 中文原版PDF下载:翻译制作中。。。。。bilibili视频页: {bilibili bvid="BV1Kq4y1P7VT" page=""/}
2021年09月30日
2,205 阅读
57 评论
23 点赞