白盒舌诊AI:MediaPipe与VL模型结合的舌象重构引擎

发布时间:2026/10/14 23:36:13
白盒舌诊AI:MediaPipe与VL模型结合的舌象重构引擎 看到这个标题你可能觉得我把简单的事搞复杂了舌象图丢给深度学习模型训练结束不就有结论了吗但真实做过舌诊AI的人心里都清楚那个结论是怎么来的、依据是什么、可不可靠基本是个黑盒。我这两年一直在做一个模拟项目X把摄像头对准舌头用MediaPipe先把舌体轮廓和关键点抠出来再交给VL模型做语义层的舌象重构整套东西从“端到端猜”变成“分步算”。这篇文章就把这套舌象重构引擎的完整思路和踩坑过程摊开来讲。先说一句结论这套组合最值钱的地方不是准确率比黑盒高多少而是每一步都有中间结果、每个结论都能找到依据医生愿意跟你聊研发也好定位问题。1. 先讲清楚黑盒盲猜到底亏在哪白盒计算又图什么1.1 传统舌诊模型的两个硬伤第一个硬伤是可解释性。你拿一张舌象图进卷积网络训练完之后模型给了你一个“淡红舌、薄白苔”的概率还能给定一个类别。但医生追问一句“你根据什么判断是淡红舌舌边有没有齿痕舌体有没有胖大”就答不上来了。深度学习把判断的依据揉碎在成千上万个特征图里人没法审计更没法校对。第二个硬伤是形态学信息被严重压缩。舌诊讲究舌神、舌色、舌形、苔色、苔质、舌下络脉其中舌形是要看整体轮廓的舌体是胖大还是瘦薄、舌边有没有齿痕、舌中是否凹陷、舌体是否歪斜。这些信息在卷积网络里会被池化操作不断磨平模型往往学会了“用颜色猜形态”的偷懒路径。我实际测试过两种情况单纯调整图像亮度黑盒模型对舌体胖瘦的判断波动很大因为它根本没真正看到“胖”的几何特征。1.2 白盒计算的核心把舌象拆成三个可审计的通道我最终采用的架构是把舌象分析拆成三个独立通道各自计算、各自保存中间结果最后合并出结论。第一个通道是几何通道由MediaPipe输出舌头关键点坐标再由这些坐标计算舌体面积、长宽比、舌尖舌根宽度比、边缘凹凸度这些量化指标。这个通道解决“形态怎么说”的问题。第二个通道是光学通道根据舌体关键点围出的区域把RGB像素转换到LAB颜色空间计算舌质区和苔区的基础色度统计量。这个通道解决“颜色怎么算”的问题。第三个通道是语义通道由VL模型接受“图像 前两个通道的特征参数 中医舌象术语约束”输出结构化的舌象描述文本。这个通道解决“特征怎么读”的问题。三个通道全部落盘每次分析都会生成一份特征JSON和一张标注了关键点的叠加图。这就叫“重构”不是简单给一个分类标签而是把一张舌象图重新表达成一组可计算、可回溯、可再分析的物理量和语义标签。1.3 为什么偏偏是MediaPipe VL这一对组合我最早考虑过两条替代路线一是自己训练舌体分割网络再用OpenCV做形态学分析二是全流程交给多模态大模型直接读图出结论。第一条路的问题在于舌体分割数据标注成本极高而且舌体边缘和嘴唇边界非常接近自己训练的分割网络在跨设备上很容易崩。MediaPipe的好处是它已经内置了人脸与口腔关键点检测能力对舌体区域的轮廓点输出比较稳定而且轻量单帧推理在普通CPU上也能跑到几十毫秒不需要单独训练。第二条路的问题在于直接让VL模型看舌象图它的回答虽然流畅但无法稳定复现同一张图同一个提示词可能上次说“舌体胖大”下次说“舌体适中”。VL模型不是不能看舌象而是不适合独自完成所有事。让MediaPipe把形态学特征算成数字再把这些数字当成约束条件喂给VLVL就不需要凭感觉猜几何和颜色了它只负责把数字翻译成规范的中医术语。所以这套组合的本质是几何和颜色交给传统计算管线语义和表达交给语言模型各干各擅长的事。2. 用MediaPipe把舌头“几何化”从图像到坐标的实操细节2.1 舌象图像采集与MediaPipe关键点捕获先明确一点MediaPipe没有一个叫“舌象专用”的入口它提供的是人脸网格关键点检测口腔和舌体区域有相应的关键点分布。我在项目里做的事情是从MediaPipe FaceMesh输出的468个关键点中筛选出舌体相关的一组点索引再基于这些点继续计算。采集图像时有个经验让用户自然张嘴、舌尖轻轻抵住下牙内侧、舌面放松尽量在均匀自然光下拍摄。太暗的图会让舌体边缘和口腔内部混在一起过曝会让舌苔细节丢失。我后面会在踩坑章节细说。代码层面初始化MediaPipe FaceMesh非常直接import mediapipe as mp import cv2 mp_face_mesh mp.solutions.face_mesh face_mesh mp_face_mesh.FaceMesh( static_image_modeTrue, max_num_faces1, refine_landmarksTrue, min_detection_confidence0.5, )注意这里的refine_landmarksTrue很关键它会额外输出眼部IRIS和部分口腔内关键点舌体区域的稳定性明显更好。图像送入前要转成RGB并且我习惯把输入尺寸统一缩放保证最短边不小于512像素否则舌体边缘的很多细节点会抖动。拿到468个关键点之后需要维护一个自定义的舌体点索引集合。这个集合没有官方标准答案我是通过对一批图片逐个描点后整理出来的大致包含舌体上边缘轮廓点、下边缘轮廓点、舌尖点、舌根左右角点以及舌面内部的若干网格点。示意如下实际数量可以是60到80个点TONGUE_LANDMARK_INDEXES [ 37, 39, 40, 41, 42, 43, # 上唇区域附近 190, 191, 80, 81, 82, # 口腔内侧上方 13, 14, 15, 16, 17, # 口腔内部舌面点 84, 85, 86, 87, 88, # 舌体下侧区域 # ... 实测后补齐 ]不要迷信固定索引不同版本的人脸网格模型点的分布会微调。正确做法是写一个可视化工具把关键点直接叠在图上人工确认哪些点落在舌体边缘再把对应索引存成配置文件。这是最省时间的方法。2.2 从关键点算舌象几何特征拿到舌体轮廓点之后就可以算一些对舌诊有意义的几何量。随便提几个我在项目里用到的。舌体面积。用鞋带公式近似多边形面积。这个面积本身没有绝对意义但同一坐标系下它反映了舌体在画面中的占比也可以换算成相对面积来做胖大舌的初筛。def polygon_area(points): area 0.0 n len(points) for i in range(n): x1, y1 points[i] x2, y2 points[(i 1) % n] area x1 * y2 - x2 * y1 return abs(area) / 2.0舌体长宽比。粗算方法取舌尖点到舌根两个角点连线的垂直距离作为长度取轮廓上距离舌中线最远的两个点的距离作为宽度二者相除。长宽比偏小的舌象往往对应胖大舌、齿痕舌的几何倾向偏大的对应瘦薄舌。这个值我没直接当作诊断依据而是作为VL模型的输入之一。舌尖舌根宽度比。舌尖宽度取舌尖附近一小段轮廓的平均宽度舌根宽度取舌根处轮廓的平均宽度。这个比值在部分舌象分类里比整体长宽比更敏感比如舌尖红赤伴舌尖宽大的情况光靠长宽比是看不出来的。齿痕的量化对舌体边缘点做凸包计算每个边缘点到凸包的距离。正常舌体的边缘点与凸包距离很小齿痕舌则在边缘某个区域出现连续几毫米的凹陷这些凹陷点与凸包的距离会明显偏大。统计“超过某个阈值的凹陷点数量”以及“最大凹陷深度”就得到一个可复现的齿痕评分。这些几何特征全部是数值稳定的输入条件下一帧与下一帧的波动可以被压到很小。有了这些数字后面的VL模型才敢说“舌体胖大伴齿痕”而不只是复读概率。2.3 舌体区域颜色特征提取几何信息以外颜色信息是舌诊的另一大核心而颜色必须在“舌体区域内”提取不能把嘴唇、牙齿、口腔背景的像素混进来。做法是先用轮廓点构造掩码这里我直接用了OpenCVimport numpy as np mask np.zeros((height, width), dtypenp.uint8) cv2.fillPoly(mask, [contour], 255)然后在掩码区域内做像素统计。重点在于颜色空间不要用RGB直接算均值因为RGB三个通道的欧氏距离和人眼感知不对齐。我统一在LAB空间里做统计L代表明度a代表红绿方向b代表黄蓝方向。舌色越红a正值越大苔色偏黄b正值越大舌体光泽度高L均值偏高。实际操作时要区分舌质区和舌苔区。简单做法是浅层聚类在舌体掩码区域内跑K-Means把像素聚成深浅两类浅色类大概率对应舌苔深色类对应舌质。这个方法不完美但作为工程近似够用。输出给VL模型的特征包括舌质区L均值、a均值、b均值舌苔区同样的三项再补一个舌质/舌苔面积占比。这套颜色参数比给VL一张原图更抗干扰。3. 让VL模型读懂“舌象语言”从图像到中医辨证语义3.1 VL模型不负责“看”负责“理解”这是整套设计里最重要的一条认知。我见过很多方案把舌象图直接丢给大模型然后让大模型输出诊断结果效果非常不稳定。原因在于通用VL模型虽然有视觉能力但它的视觉偏好和中医舌诊要求不完全一致它更关注画面里“醒目”的东西而舌诊恰恰需要关注舌体边缘、舌面细节这些对通用模型来说不算显著的特征。在这套引擎里VL模型拿到的输入有两部分一张裁剪并标准化后的舌象图一组由几何通道和光学通道算好的结构化参数。也就是说VL不是从零开始看图说话而是基于已经算好的特征做语义规范化。它的任务是把“面积占比偏大、长宽比偏小、边缘凹陷点数偏多、舌质区a值偏高”这样一串数字转写成“舌体胖大舌边齿痕舌质偏红”这样的中医术语。这样分工之后VL模型对几何和颜色的判断误差就基本不影响输出质量了因为数值已经由白盒管线确定VL只需做映射。3.2 提示词构造与结构化输出提示词设计要遵循三个原则限定术语、限定格式、限定输出边界。我给VL模型的系统提示词大致是这个样子你是一名中医舌象辅助研判模块。你只能根据用户提供的舌象特征参数和舌象图像进行描述。 允许使用的舌色术语淡白、淡红、红、绛红。 允许使用的苔色术语白、淡黄、黄、灰黑。 允许使用的舌形术语老、嫩、胖大、瘦薄、齿痕、裂纹、歪斜。 允许使用的苔质术语薄、厚、腻、燥、润。 输入特征参数如下 舌体面积占比: 0.42 舌体长宽比: 0.73 舌尖舌根宽度比: 0.68 齿痕凹陷点数: 12 舌质区LAB均值: [52.3, 18.7, 10.2] 舌苔区LAB均值: [61.5, 6.8, 15.4] 舌苔面积占比: 0.31 请输出JSON格式如下 {舌色:, 舌苔色:, 舌形:[], 苔质:, 整体描述:} 只输出JSON不要额外说明。为什么强调只输出JSON因为后还要做规则映射结构化输出能极大降低解析成本。实际测试中如果不加这个约束VL模型很容易输出一段带解释的散文里面混杂着“我认为”“可能”这类模糊表达没法直接用。3.3 少样本对齐与规则映射VL模型的输出仍然可能存在方言化表达同一个特征今天说“舌淡红”明天说“淡红舌”意思一样但字面不同。解决办法有两个。第一是做少样本对齐在提示词里附带两个完整的输入输出示例让模型明白你期望的措辞习惯。少样本示例比写十行说明都管用。第二是加一个后处理规则映射表。我不会让VL模型直接输出“气虚证”这类诊断结论因为模型对证型的理解不可控。正确的做法是让模型输出舌色、苔色、舌形、苔质四组基础描述再由程序按规则匹配参考证型。规则表很直白舌色苔色舌形参考倾向淡白白胖大、齿痕气虚/阳虚方向红黄、腻无明显异常湿热方向绛红少苔或光剥裂纹阴虚方向淡红薄白无明显异常平和舌象方向这种设计把VL限制在“描述层”而不是“诊断层”大大降低了幻觉风险。模型可以自由组合舌色和苔色词但最终证型倾向必须通过可审查的规则判断。4. 端到端重构引擎从上传图像到输出报告的完整踩坑实录4.1 系统流程和技术栈整个引擎的运行流程按顺序分为八个步骤图像采集与预处理缩放、白平衡粗校正MediaPipe关键点提取舌体轮廓筛选与几何特征计算舌体掩码生成与LAB颜色特征提取特征序列化并组装成VL提示词VL推理输出结构化JSON规则映射得到参考证型倾向和置信度输出可视化报告叠加关键点和舌体轮廓技术栈方面核心代码用Python 3.10编写依赖项是mediapipe、opencv-python、numpy、PillowVL推理部分通过本地部署的视觉语言模型服务接口调用用HTTP请求方式交互。整套东西跑在一台普通办公级GPU上单张图片从输入到输出大概两秒左右其中VL推理占了大头。4.2 核心管线串联关键代码下面是一个简化版的核心管线示例把特征计算部分和VL调用部分串起来。这个结构也是我实际在用的雏形后面扩展起来相对方便。import cv2 import numpy as np import mediapipe as mp class TongueRestructureEngine: def __init__(self): self.mp_face_mesh mp.solutions.face_mesh.FaceMesh( static_image_modeTrue, max_num_faces1, refine_landmarksTrue, ) def extract_landmarks(self, img_rgb): results self.mp_face_mesh.process(img_rgb) if not results.multi_face_landmarks: return None return results.multi_face_landmarks[0] def compute_features(self, img_bgr, landmarks): # 这一步把关键点转成几何特征与颜色特征 # 返回一个dict里面包括面积占比、长宽比、LAB均值等 features {} # ... 实际特征计算逻辑 return features def run(self, img_bgr): img_rgb cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB) landmarks self.extract_landmarks(img_rgb) if landmarks is None: return {error: 未检测到舌体关键点请调整拍摄角度或补光} features self.compute_features(img_bgr, landmarks) prompt self.build_prompt(features) vl_output self.call_vl_model(prompt) report self.apply_rule_mapping(vl_output, features) return report这段代码的核心在于把特征提取结果封装成features字典再通过build_prompt函数组装提示词最后用规则映射兜底。前后端分离之后想换一个更强的VL模型只需要改call_vl_model这个函数的实现其他部分完全不用动。4.3 高频踩坑与排查方案这一路实际跑下来坑比我想象的多。我把最高频的几个问题列成一个速查表常见问题根因解决经验MediaPipe检测不到舌头关键点嘴巴张太小舌体与嘴唇重叠严重拍摄前引导用户自然张嘴必要时加“嘴巴张开程度”预检舌体关键点在视频流中抖动输入分辨率过低边缘点置信度不够固定输入最短边512像素以上多帧取时序平滑颜色特征整体偏灰黄自动白平衡把舌色“纠正”了使用固定光源并关闭相机自动白平衡白平衡校正板放取景框内辅助舌苔区误判为舌质区浅层K-Means聚类对薄白苔失效增加覆盖度特征薄苔时参考舌体边缘区域的色度值VL输出JSON格式不稳定提示词约束不够强模型偶尔输出注释提示词中增加“只输出JSON”并做一次JSON解析重试VL术语超出限定词表模型自由发挥输出后校验词表不在词表内强制映射到最近术语VL对同一特征给出相反描述模型采样温度过高把解码温度调到0.2以下并把几个候选描述做一致性投票这里我想重点说下时序平滑。视频流场景下单帧检测的舌体边缘点会出现轻微抖动直接算面积和齿痕评分会有肉眼可见的噪声。我用了一阶指数移动平均来做坐标平滑smoothed alpha * current_point (1 - alpha) * previous_smoothedalpha取0.4左右既保留动态变化又能滤掉高频抖动。这个技巧对静态图片用处不大但对实时视频分析几乎是刚需。4.4 评估怎么量化这套白盒引擎比黑盒更靠谱我评估这套引擎时用了三个维度而不是只盯准确率。第一是重复性。同样的舌象在固定机位连续拍十张计算几何特征和颜色特征的变异系数。理想状态下面积占比和长宽比的变异系数应该控制在5%以内。黑盒端到端模型在这种测试里输出非常不稳同一个类别概率能波动十个点以上。第二是医生一致性。把这个引擎输出的舌色、苔色、舌形描述给中医方向的某团队核对与人工标注结果计算Kappa系数。这个值只要达到基本一致水平就说明白盒管线没有明显跑偏而且不一致的地方可以被逐条调出来审计。第三是可审计性。每一张图片都保存中间特征JSON和关键点叠加图当某次输出与预期不符时可以直接定位是几何层算错、颜色层偏色、VL表达错误还是规则表缺项。这一点在真实业务里最重要因为黑盒模型出错了你只能重新训练数据集白盒引擎出错了你只需要改一个环节。另外我在评估时还发现把VL输出从“自由文本”改成“结构化JSON 词表约束”之后与人工标注的一致性明显提升。原因很简单模型的自由发挥空间越小犯大错的概率越低。5. 一点实操体会这套“MediaPipe VL”组合我前前后后改了好几轮最大的体会是中间结果永远要落到磁盘里。舌象重构引擎每次跑完我会自动保存一张舌体关键点叠加图和一份特征JSON哪怕当前结论看起来完全正确也会存。正是这些历史中间结果让我后来排查“为什么某一类舌象老是被误判成齿痕舌”时半小时就定位到了边缘点索引配置的问题而不是像以前调黑盒模型那样一头雾水。还有一个建议给想复现这套方案的读者调试顺序一定先从几何层开始保证关键点准确、面积和长宽比稳定再去碰VL的提示词。几何层不稳后面所有工作都是在沙地上盖楼。我自己犯过的错误就是一开始沉迷调提示词结果换一个拍摄环境后整个系统全崩最后才发现是舌体区域抠图出了问题。如果你也在做舌象或类似的医学影像分析项目可以从这套“把视觉问题拆成几何、光学、语义三个通道”的思路里找点启发。至少对我来说从黑盒到白盒的转变不是模型选型的变化而是整个做事方式的变化。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询