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

【仿生人形机器头】唇形同步

我现在在做视位素的归类,我会把口型相似的合并到一起。

其实只有嘴唇和下颚的事情。


其中下颚只有开关角度。

L0,L1,L2,L3 代表下颚张开的幅度,L0是下颚闭合,L1是小角度张开,L2是正常张开,L3是大幅度张开。

而唇部有上唇,下唇,两侧。其中两侧的舵机可以控制咧嘴,参与嘟嘴。

P0 是上下唇闭合,P1是上下唇小角度张开,P2是上下唇大角度张开,

C0是两侧舵机初始中间状态;

C1是两侧舵机小幅度夹,形成小幅度的轻嘟嘴;—— 轻幅度嘟嘴,“o”

C2是两侧舵机中幅度夹,形成中幅度的嘟嘴;—— 中幅度嘟嘴,“w”,“u”

C3是两侧舵机大幅度夹,形成大幅度的嘟嘴;—— 嘟嘴到闭嘴,“亲吻”

C9是两侧舵机反方向大幅度拉,形成大幅度的咧嘴;

特别的:在P的基础上加的偏移量

SXC0 代表轻升下唇,SXC1 代表中幅度升下唇,SXC2 代表大幅度升下唇

JXC0 代表轻降下唇,JXC1  代表中幅度降下唇,JXC2 代表大幅度降下唇

SSC0 代表轻升上唇,SSC1 代表中幅度升上唇,SSC2 代表大幅度升上唇

JSC0 代表轻降上唇,JSC1  代表中幅度降上唇,JSC2 代表大幅度降上唇

首先是声母:除了嘟嘴,全是咧嘴;除了双颚闭合,全是双颚张开

b,p,m;(双颚张开L1,嘴唇闭合P0 ,咧嘴C9)到(双颚张开L2,嘴唇小张P1 ,咧嘴C9

f;(不嘟嘴,有上牙咬下唇的感觉)双颚张开L1,嘴唇小张P1 + 升下唇SXC0,咧嘴C9

d,t,n,l,g,k,h;(微张嘴,不嘟嘴,且下唇比较靠下 双颚张开L1,嘴唇小张P1,咧嘴C9

j,q,x,z,c,s;(张口,咧嘴)双颚张开L1,嘴唇小张P1,咧嘴C9

zh,ch,sh,r;(上下唇张开呈圆形,上唇往上,下唇往下,露出牙齿,轻嘟嘴,闭嘴,上下颚牙齿闭合双颚闭合L0 ,嘴唇大张P2,嘟嘴C1

y;(双颚张开L1嘴唇小张P1,咧嘴C9

w;(双颚张开L2嘴唇小张P1,嘟嘴C2

然后是韵母:

a,e,ai,ei,er;(双颚张开L3嘴唇大张P2,唇部C0

o,ong;(双颚张开L3嘴唇大张P2,嘟嘴C1

u,ü;(双颚张开L2嘴唇小张P1,嘟嘴C2

i,in,ing;(双颚张开L1嘴唇小张P1,咧嘴C9

ui(微);(双颚张开L1嘴唇小张P1,咧嘴C9

ao,ou;(双颚张开L3嘴唇大张P2,嘟嘴C1

iu(优);(双颚张开L2嘴唇小张P1,嘟嘴C2

ie(椰);(双颚张开L3嘴唇大张P2,唇部C0

ue;(双颚张开L3嘴唇大张P2,唇部C0

an,en双颚张开L2,嘴唇大张P2,咧嘴C9

ang,eng;双颚张开L3,嘴唇大张P2,咧嘴C9

un,ün;(比较特殊,必须是个过程,就是嘟一下嘴,再复位

最终映射表(清晰版)

对于从Azure得到的每一个英文视位素ID,使用下表直接找到对应的中文口型参数:

英文视素ID->中文参数 (L/P/C)备注
0->L1, P1, C9静音/中性
1, 2->L3, P2, C0大口展唇 (a)
3->L3, P2, C1大口圆唇 (o)
4, 6, 9, 10, 11, 12, 13, 14, 15, 17, 19, 20->L1, P1, C9所有小口展唇音 (默认状态)
7, 8->L2, P1, C2小口圆唇 (u)
5, 16->L0, P2, C1突出圆唇 (zh)
18->L1, P1, C9 + SXC0咬唇 (f) 【特殊动作】
21->L0, P0, C9闭唇 (b)

总结:
您现在只需要关心 7种中文口型状态1个特殊咬唇动作
这个方案完全消除了所有歧义,建立了一个清晰、稳定、高效的从英文音标到中文视位参数的映射管道。您的机器人现在可以用最自然的中文口型来说英文了!

视素 IDIPA嘴部位置
0静音

视素 ID 为 0 时的嘴部位置

1æəʌ

视素 ID 为 1 时的嘴部位置

2ɑ

视素 ID 为 2 时的嘴部位置

3ɔ

视素 ID 为 3 时的嘴部位置

4ɛʊ

视素 ID 为 4 时的嘴部位置

5ɝ

视素 ID 为 5 时的嘴部位置

6jiɪ

视素 ID 为 6 时的嘴部位置

7wu

视素 ID 为 7 时的嘴部位置

8o

视素 ID 为 8 时的嘴部位置

9

视素 ID 为 9 时的嘴部位置

10ɔɪ

视素 ID 为 10 时的嘴部位置

11

视素 ID 为 11 时的嘴部位置

12h

视素 ID 为 12 时的嘴部位置

13ɹ

视素 ID 为 13 时的嘴部位置

14l

视素 ID 为 14 时的嘴部位置

15sz

视素 ID 为 15 时的嘴部位置

16ʃʒ

视素 ID 为 16 时的嘴部位置

十七ð

视素 ID 为 17 时的嘴部位置

18fv

视素 ID 为 18 时的嘴部位置

19dtnθ

视素 ID 为 19 时的嘴部位置

20kgŋ

视素 ID 为 20 时的嘴部位置

21pbm

视素 ID 为 21 时的嘴部位置

下面给出一个分阶段实施规划(从“能打印正确口型序列”到“平滑驱动舵机”),以及最小可行代码骨架。你可以按阶段验证,失败点容易定位。

阶段 0:准备  
- 确认:audio_offset = 毫秒;TTS 播放方式(扬声器播放 or 直接不播放只做口型)。  
- 定义:servo_ids + neutral(各嘴相关舵机中值)。  
- 决定抽象参数 L/P/C 与具体舵机映射规则(标出每个 L/P/C 数值对应哪几组舵机的相对偏移)。

阶段 1:数据压缩与映射  
- 输入:原始 viseme 列表(可能 100+)。  
- 操作:去掉连续重复 viseme;统计区间时长 = next.offset - cur.offset。  
- 丢弃:区间 < 25ms(或合并到下一个)。  

  • 短 & 不是第一段 → 向前吸收
  • 短 & 是第一段 & 后面还有 → 向后吸收(调整下一段起点)
  • 短 & 只有它一个 → 时长扩展
  • 不短 → 原样保留

我的逻辑是,首先有个被保护的几个id

- 输出:有效口型序列(~2×汉字数)。  
- 验证:仅打印映射后的 L/P/C,不驱动舵机。

阶段 2:参数到舵机目标生成 (build_servo_targets)  
- 输入:L/P/C(0/1/2/3 等级),special。  
- 计算:jaw、左右上唇、左右下唇、左右嘴角(水平/垂直)偏移。  
- Clamp:按 servo_safe_ranges。  
- 输出:[(id,pos), ...]。

阶段 3:时序调度(独立口型播放引擎)  
- 将每个压缩后的 viseme 区间转为 (targets, duration_ms)。  
- duration_ms 再减去过渡时间 transition_ms(如 50ms),形成:  
1) 过渡帧:从上一个口型到新口型(插值)  
2) 保持帧:保持剩余时间(≥0 则生成一条保持动作)。  
- 推入你的 action_queue(可以单独一个 lip_queue,消费时合并到全局串口写操作)。  

阶段 4:与音频同步  
- 如果音频实时播放:记录 start_time;队列调度时加一个 global_audio_latency_ms(测量得到)。  
- 如果只做“假定同步”:直接按偏移依次排队即可。  
- 可先忽略精确同步,验证视觉节奏。

阶段 5:平滑 & 优化  
- 低通:对 jaw/lip_open 连续值做 3 点滑动平均。  
- 帧率限制:强制 >= 40ms 一帧(25FPS);过短区间合并。  
- 预热:播放前发送 neutral 口型,避免第一帧突变。

阶段 6:特殊动作扩展  
- special == 'SXC0'(咬唇/上齿触下唇)触发附加舵机(或调低下唇)。  
- 未来可加入表情叠加(微笑 + 口型),采用 additive blend:final = base_expression + viseme_delta(再 clamp)。

阶段 7:校准与参数表固化  
- 交互脚本:输入 L/P/C 值 → 实时发送 → 人工微调 → 输出 JSON。  
- 保存 mapping_config.json,运行时加载。

阶段 8:集成 UI  
- UI 增加“播放 TTS + 口型测试”按钮:调用 TTS→viseme→构建队列。  
- 日志区域显示:时间(ms) VisemeID L/P/C special clamp 信息。

--------------------------------------------------
核心代码骨架(新增 lip_sync.py)  
````python
from dataclasses import dataclass
from typing import List, Dict, Tuple

# 完整映射
VISEME_TO_LPC = {
0:{'L':1,'P':1,'C':9,'special':None},
1:{'L':3,'P':2,'C':0,'special':None},
2:{'L':3,'P':2,'C':0,'special':None},
3:{'L':3,'P':2,'C':1,'special':None},
4:{'L':1,'P':1,'C':9,'special':None},
5:{'L':0,'P':2,'C':1,'special':None},
6:{'L':1,'P':1,'C':9,'special':None},
7:{'L':2,'P':1,'C':2,'special':None},
8:{'L':2,'P':1,'C':2,'special':None},
9:{'L':1,'P':1,'C':9,'special':None},
10:{'L':1,'P':1,'C':9,'special':None},
11:{'L':1,'P':1,'C':9,'special':None},
12:{'L':1,'P':1,'C':9,'special':None},
13:{'L':1,'P':1,'C':9,'special':None},
14:{'L':1,'P':1,'C':9,'special':None},
15:{'L':1,'P':1,'C':9,'special':None},
16:{'L':0,'P':2,'C':1,'special':None},
17:{'L':1,'P':1,'C':9,'special':None},
18:{'L':1,'P':1,'C':9,'special':'SXC0'},
19:{'L':1,'P':1,'C':9,'special':None},
20:{'L':1,'P':1,'C':9,'special':None},
21:{'L':0,'P':0,'C':9,'special':None},
}
DEFAULT_LPC = {'L':1,'P':1,'C':9,'special':None}

@dataclass
class VisemeEvent:
viseme_id: int
offset_ms: float

def compress_visemes(raw: List[VisemeEvent], min_hold_ms=25) -> List[VisemeEvent]:
if not raw:
return []
out = [raw[0]]
for v in raw[1:]:
if v.viseme_id != out[-1].viseme_id:
out.append(v)
# 过滤太短区间(回看长度)
filtered = []
for i,v in enumerate(out):
end = out[i+1].offset_ms if i+1 < len(out) else v.offset_ms + 80
if (end - v.offset_ms) >= min_hold_ms:
filtered.append(v)
return filtered

def lpc_to_servo_targets(lpc: Dict, servo_ids: Dict, neutral: Dict) -> List[Tuple[int,int]]:
# 需要你定义的中值与步长
# 示例权重(占位):需实测调整
L = lpc['L']; P = lpc['P']; C = lpc['C']
# 下颌开度:映射 L (0~3) → jaw delta
jaw_delta_table = {0:-20,1:0,2:40,3:80}
jaw_base_left  = neutral['jaw_left']
jaw_base_right = neutral['jaw_right']
jaw_left_pos  = jaw_base_left  + jaw_delta_table.get(L,0)
jaw_right_pos = jaw_base_right + int(jaw_delta_table.get(L,0)*0.9)  # 轻微差异
# 圆唇/突出:P 控制嘴角水平收敛;C 控制唇形分类(此处简单用作竖直张开微调)
protrude_scale = {0:0,1:60,2:120}  # 水平内收
open_delta_table = {0:0,1:8,2:16,9:0}  # C 仅示例
mouth_w_in = protrude_scale.get(P,0)
open_delta = open_delta_table.get(C,0)
# 计算嘴角 / 上下唇(示例简化)
targets = [
(servo_ids['jaw_left'], jaw_left_pos),
(servo_ids['jaw_right'], jaw_right_pos),
(servo_ids['upper_lip_left'],  neutral['upper_lip_left']  + open_delta),
(servo_ids['upper_lip_right'], neutral['upper_lip_right'] + open_delta),
(servo_ids['lower_lip_left'],  neutral['lower_lip_left']  - open_delta),
(servo_ids['lower_lip_right'], neutral['lower_lip_right'] - open_delta),
(servo_ids['left_corner_upper'],  neutral['left_corner_upper']  - mouth_w_in),
(servo_ids['right_corner_upper'], neutral['right_corner_upper'] + mouth_w_in),
]
# TODO: clamp by external servo_safe_ranges
return targets

def viseme_sequence_to_actions(raw: List[Dict], servo_ids: Dict, neutral: Dict,
transition_ms=50, min_frame_ms=40) -> List[Tuple[List[Tuple[int,int]], int]]:
raw_events = [VisemeEvent(v['viseme_id'], v['audio_offset']) for v in raw]
comp = compress_visemes(raw_events)
actions = []
prev_targets = None
for i, ev in enumerate(comp):
end = comp[i+1].offset_ms if i+1 < len(comp) else ev.offset_ms + 120
span = max(min_frame_ms, end - ev.offset_ms)
lpc = VISEME_TO_LPC.get(ev.viseme_id, DEFAULT_LPC)
cur_targets = lpc_to_servo_targets(lpc, servo_ids, neutral)
if prev_targets is None:
actions.append((cur_targets, int(span)))
else:
# 过渡:transition_ms
actions.append((cur_targets, int(span)))  # 简化:可拆成过渡+保持
prev_targets = cur_targets
return actions
````

在 UI / 主控制里调用示例  
````python
# 假设你已有 viseme_list, servo_ids, neutral, enqueue_actions()
from lip_sync import viseme_sequence_to_actions
lip_actions = viseme_sequence_to_actions(viseme_list, servo_ids, neutral)
enqueue_actions(lip_actions, clear_existing=False)  # 或独立 lip 队列
````

下一步建议执行顺序  
1. 实现 lip_sync.py 并仅打印 actions 长度与第一条 targets。  
2. 手动填 neutral 与真实 servo_ids;调 lpc_to_servo_targets 里的 delta。  
3. 打一段 2 秒短句,看口型是否跟随(慢放 / 拍视频)。  
4. 调整 jaw_delta_table / protrude_scale / open_delta_table。  
5. 引入 clamp + 平滑(滑动平均)。  
6. 与其他表情叠加(策略:先表情后口型或加权混合)。  

告知我:需要我进一步完善哪部分(A 数值标定脚本 B 叠加算法 C 实时音频同步回调)。直接回复选项即可。以上是chatgpt5.0对我们项目做出的规划,你觉得chatgpt5.0说的对吗?它的计划怎么样?你觉得好不好,是否需要改进?

http://www.dtcms.com/a/416244.html

相关文章:

  • 成都企业网站监测网站定制
  • 郑州平台类网站关于网页设计
  • 西安响应式网站深圳市注册公司流程图
  • 常州网站建设企业网上怎么做网站
  • 如何建立淘宝客网站wordpress改了固定链接
  • 宠物社区网站开发设计文档企业网站开发 流程
  • Docker镜像与容器:轻松理解与实战
  • 统计局网站建设如果在各大网站做免费的网络推广
  • 网站大全免黄手机网站怎么建设
  • 如何构建网站新手如何搭建网站
  • c++ 之 基类与派生类的转换
  • 一次跨界学习的探索:用流水线的方式攻克行业知识的尝试
  • 免费建立属于自己的网站做网站成都哪家公司最好
  • 网页设计制作网站开发建设新手建站基础入门到精通视频教程李光辉:营销型企业网站建设的指导思想是什么?
  • 二手表网站wordpress4.8漏洞
  • 电脑防护一键关闭工具V1.0
  • 手机网站开发流程网页传奇私
  • 【贪心 树状数组】P9310 [EGOI 2021] Luna likes Love / 卢娜爱磕 cp|普及+
  • 百度一下官方网站wordpress第三方订阅地址
  • Windows 已占 VT-x 的终极排查与根治手册
  • leetcode解题思路分析(一百六十七)1445 - 1451 题
  • 网站建设中的端口wordpress增加友情链接
  • Python实现SQL语句自动转换工具(UPDATE到INSERT)
  • 找网站建设公司好php制作网站
  • 建设银行网银官方网站通州企业网站建设
  • 《Python中的适配器模式实战:让第三方库优雅融入你的系统》
  • 深圳私人做网站做venn图的网站
  • 网站搭建设计 是什么中国建设银行网站首页旧版
  • 做网站vpn多大内存网站策划资料方案
  • 注册网站域名平台南通外贸建站