1. 项目缘起与整体设计思路
1.1 为什么我要做这个舌象重构引擎
做中医舌诊数字化这件事,我前前后后折腾了快两年。最开始的想法特别朴素:用手机拍张舌头照片,让程序告诉我这属于什么舌象。但真正动手之后才发现,这件事远比想象中复杂。传统舌诊依赖医生的肉眼观察和经验判断,同一个舌头,十个医生可能给出七八种不同的描述,这种“黑盒盲猜”式的诊断方式,在数字化时代显得格格不入。
我想要的是一套“白盒计算”系统——每一步推理都有据可查,每一个参数都能追溯到具体的像素和数值。这就是我启动这个项目的核心动机:把舌象诊断从主观经验变成可量化、可复现的计算过程。
整个引擎的技术栈选型经历了三次大迭代。第一版纯靠OpenCV做颜色阈值分割,效果惨不忍睹,光线稍微变一下结果就全乱了。第二版引入了深度学习分类模型,准确率上去了,但推理过程完全不可解释,又回到了“黑盒”的老路。直到第三版,我确定了MediaPipe + VL(视觉语言模型)的组合方案,才真正找到了平衡点。
MediaPipe负责“看”——精准定位舌头区域,提取几何特征和颜色分布;VL模型负责“理解”——把视觉特征翻译成中医舌诊的专业描述。两者结合,既保证了计算的透明性,又具备了语义理解的灵活性。
1.2 核心架构拆解:三层次处理管线
整个引擎我设计了三层处理管线,每一层都有明确的输入输出定义,层与层之间通过标准化的数据接口通信。这种设计的好处是,任何一层的算法升级都不会影响其他层的稳定性。
第一层:视觉感知层。这一层的任务是从原始图像中把舌头“抠”出来。我试过很多方案,包括U-Net语义分割、HSV颜色空间阈值法,最后选择了MediaPipe Face Mesh + 自定义舌区定位算法。为什么?因为MediaPipe的人脸关键点检测实在太稳了,468个3D关键点里,有十几个直接落在嘴唇和口腔区域,我只需要在这些关键点的基础上做区域扩展,就能以极低的计算成本拿到舌头的大致位置。
第二层:特征计算层。拿到舌头区域后,需要计算一系列量化指标。我把这些指标分成三大类:颜色特征(舌质颜色、舌苔颜色)、形态特征(舌体胖瘦、齿痕有无、裂纹分布)、纹理特征(舌苔厚薄、腐腻程度)。每一类特征都有对应的计算方法和参数阈值,全部基于像素级运算,结果可追溯。
第三层:语义映射层。这是VL模型发挥作用的地方。我把第二层计算出的数值特征,连同原始舌头图像一起送入VL模型,让它生成符合中医术语规范的舌象描述。这里的关键是设计好提示词模板,把数值特征以结构化的方式嵌入到提示中,引导模型输出标准化的诊断语言。
1.3 技术选型背后的权衡逻辑
很多人问我为什么不用端到端的深度学习方案,一步到位多简单。我的回答是:端到端方案在学术数据集上可能跑出很高的准确率,但到了真实场景就会暴露两个致命问题。
第一是数据偏差。公开的舌象数据集大多是在标准光源下采集的,而用户手机拍摄的照片,光源色温从2700K到6500K都有,还有各种美颜滤镜的干扰。端到端模型会把这些干扰因素也学进去,导致换一个手机拍出来的结果就完全不同。
第二是可解释性缺失。中医舌诊有一套完整的理论体系,舌质红代表热证,舌苔白腻代表湿证,这些对应关系需要被明确地编码在系统里。端到端模型把这一切都压缩在权重矩阵里,医生根本无法验证推理过程是否合理。
MediaPipe + VL的方案虽然工程复杂度更高,但每个环节都是可控的。颜色校正模块可以单独调试,舌区分割的IoU可以量化评估,VL模型的输出可以用规则引擎做后处理校验。这种“分而治之”的思路,在医疗健康这类对可靠性要求极高的场景里,是更务实的选择。
2. 核心细节解析与实操要点
2.1 MediaPipe舌区定位的关键参数调校
MediaPipe Face Mesh默认输出468个面部关键点,但直接用它来定位舌头是不够的。我的做法是:先提取嘴唇内侧的关键点(索引61-67和291-297这两组),然后以这些点为基础构建一个椭圆形的感兴趣区域(ROI)。
具体来说,我取上下唇内侧关键点的中点作为椭圆中心,以嘴角关键点之间的距离作为椭圆长轴,短轴则根据经验设置为长轴的0.6倍。这个比例是我在超过2000张测试图片上反复调整出来的,太小会漏掉舌体边缘,太大又会把牙齿和口腔内壁包含进来。
import mediapipe as mp import numpy as np mp_face_mesh = mp.solutions.face_mesh # 嘴唇内侧关键点索引 UPPER_INNER_LIP = [61, 62, 63, 64, 65, 66, 67] LOWER_INNER_LIP = [291, 292, 293, 294, 295, 296, 297] def get_tongue_roi(face_landmarks, img_shape): h, w = img_shape[:2] # 提取上下唇内侧关键点坐标 upper_points = [(face_landmarks.landmark[i].x * w, face_landmarks.landmark[i].y * h) for i in UPPER_INNER_LIP] lower_points = [(face_landmarks.landmark[i].x * w, face_landmarks.landmark[i].y * h) for i in LOWER_INNER_LIP] # 计算椭圆中心 all_points = upper_points + lower_points center_x = np.mean([p[0] for p in all_points]) center_y = np.mean([p[1] for p in all_points]) # 计算长轴(嘴角距离) left_corner = face_landmarks.landmark[61] right_corner = face_landmarks.landmark[291] major_axis = np.sqrt( ((left_corner.x - right_corner.x) * w) ** 2 + ((left_corner.y - right_corner.y) * h) ** 2 ) # 短轴为长轴的0.6倍 minor_axis = major_axis * 0.6 return (int(center_x), int(center_y), int(major_axis/2), int(minor_axis/2))这里有个坑我踩了很久:MediaPipe返回的是归一化坐标(0到1之间),必须乘以图像的实际宽高才能得到像素坐标。而且不同手机的前置摄像头有镜像翻转的问题,需要在预处理阶段统一做水平翻转校正,否则左右嘴角的关键点会颠倒。
注意:MediaPipe Face Mesh在侧脸角度超过30度时,嘴唇关键点的精度会明显下降。我的处理策略是加入一个姿态判断逻辑,当检测到头部偏转角度过大时,直接提示用户“请正对摄像头重新拍摄”,而不是强行输出不可靠的结果。
2.2 颜色校正:从“拍啥样”到“该啥样”
舌象诊断最核心的指标是颜色,但手机拍摄的照片受光源影响极大。同一根舌头,在暖色灯光下拍出来偏红,在冷色灯光下拍出来偏白,如果不做校正,后续所有分析都是空中楼阁。
我的颜色校正方案分两步走。第一步是白平衡校正,利用图像中牙齿区域作为参考白点。牙齿在正常情况下的RGB值应该接近相等,如果R通道明显高于B通道,说明光源偏暖,需要做相应的补偿。第二步是色彩空间转换,把校正后的RGB图像转到CIELAB空间,因为LAB空间的L通道(亮度)和AB通道(色度)是分离的,后续的颜色分析主要在AB通道上进行,可以最大程度消除亮度变化带来的干扰。
def white_balance_correction(img, teeth_region): # 提取牙齿区域的平均RGB值 teeth_pixels = img[teeth_region[1]:teeth_region[3], teeth_region[0]:teeth_region[2]] avg_r = np.mean(teeth_pixels[:,:,0]) avg_g = np.mean(teeth_pixels[:,:,1]) avg_b = np.mean(teeth_pixels[:,:,2]) # 计算各通道的增益系数 gray_value = (avg_r + avg_g + avg_b) / 3 gain_r = gray_value / avg_r gain_g = gray_value / avg_g gain_b = gray_value / avg_b # 应用增益 corrected = img.copy().astype(np.float32) corrected[:,:,0] *= gain_r corrected[:,:,1] *= gain_g corrected[:,:,2] *= gain_b return np.clip(corrected, 0, 255).astype(np.uint8)实测下来,经过白平衡校正后,同一根舌头在不同光源下拍摄的照片,其LAB空间中A通道(红绿轴)的差异可以从原来的15-20个单位缩小到3-5个单位,这个精度已经足够支撑后续的舌色分类了。
2.3 VL模型提示词工程:让模型说“中医话”
VL模型虽然强大,但如果你直接问它“这张舌头照片显示什么舌象”,它大概率会给你一段模棱两可的通用描述。我的做法是设计一套结构化的提示词模板,把第二层计算出的数值特征以“证据”的形式提供给模型,引导它基于这些证据做出判断。
提示词模板的核心结构是这样的:
你是一位中医舌诊专家。请根据以下量化特征和舌象图像,给出专业的舌象分析。 量化特征: - 舌质LAB值:L={L_val}, A={A_val}, B={B_val} - 舌质颜色分类:{color_category}(置信度:{confidence}) - 舌苔覆盖率:{coverage}% - 舌苔厚薄指数:{thickness_index} - 齿痕数量:{teeth_marks} - 裂纹长度占比:{crack_ratio} 请按照以下格式输出: 1. 舌质描述(颜色、形态) 2. 舌苔描述(颜色、厚薄、腐腻) 3. 综合判断(结合中医理论)这里的关键是数值特征必须准确,因为VL模型会把这些数值当作事实依据。如果颜色分类模块把淡红舌误判为红舌,VL模型就会顺着错误的方向生成描述。所以我在实际部署时,会在VL模型之前加一个规则校验层,当数值特征之间的逻辑一致性得分低于阈值时,直接返回“图像质量不足,请重新拍摄”的提示。
实操心得:VL模型的温度参数(temperature)建议设置在0.3到0.5之间。太高会导致输出不稳定,同一张图片每次生成的描述都不一样;太低又会让输出过于死板,缺乏中医语言的灵活性。我实测下来0.4是一个比较好的平衡点。
3. 实操过程与核心环节实现
3.1 环境搭建与依赖安装
整个项目的运行环境我推荐使用Python 3.9到3.11之间的版本,太新的版本有些依赖库还没跟上。MediaPipe的安装相对简单,但有几个细节需要注意。
# 创建虚拟环境 python -m venv tongue_env source tongue_env/bin/activate # Linux/Mac # tongue_env\Scripts\activate # Windows # 安装核心依赖 pip install mediapipe==0.10.9 pip install opencv-python==4.9.0.80 pip install numpy==1.24.3 pip install Pillow==10.2.0MediaPipe在Windows上安装时,如果遇到“Could not find a version that satisfies the requirement”的错误,大概率是Python版本太新了。MediaPipe对Python 3.12的支持还不完善,降级到3.11就能解决。另外,如果你用的是Apple Silicon的Mac,建议通过conda安装,pip安装的版本在M系列芯片上可能会有兼容性问题。
VL模型部分,我选择的是通过API调用的方式,而不是本地部署。原因很简单:本地部署一个7B参数量的VL模型,至少需要16GB显存,而API调用的成本对于个人项目来说完全可以接受。具体的API配置我这里就不展开了,核心思路是把图像编码成base64格式,连同提示词一起发送到接口,然后解析返回的JSON结果。
3.2 完整处理流程的代码实现
下面我把整个处理流程串起来,从图像输入到最终输出,每一步都有详细的注释说明。
import cv2 import numpy as np import mediapipe as mp import base64 import json class TongueAnalysisEngine: def __init__(self): self.face_mesh = mp.solutions.face_mesh.FaceMesh( static_image_mode=True, max_num_faces=1, refine_landmarks=True, min_detection_confidence=0.5 ) def preprocess(self, image_path): """图像预处理:读取、翻转校正、尺寸统一""" img = cv2.imread(image_path) if img is None: raise ValueError(f"无法读取图像:{image_path}") # 统一尺寸到640x480 img = cv2.resize(img, (640, 480)) # 水平翻转校正(前置摄像头镜像问题) # 这里假设输入已经做过翻转判断,实际项目中需要根据EXIF信息判断 img = cv2.flip(img, 1) return img def locate_tongue(self, img): """定位舌头区域""" rgb_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) results = self.face_mesh.process(rgb_img) if not results.multi_face_landmarks: return None, "未检测到人脸" face_landmarks = results.multi_face_landmarks[0] h, w = img.shape[:2] # 提取嘴唇内侧关键点 upper_inner = [61, 62, 63, 64, 65, 66, 67] lower_inner = [291, 292, 293, 294, 295, 296, 297] upper_points = [(face_landmarks.landmark[i].x * w, face_landmarks.landmark[i].y * h) for i in upper_inner] lower_points = [(face_landmarks.landmark[i].x * w, face_landmarks.landmark[i].y * h) for i in lower_inner] all_points = upper_points + lower_points center_x = int(np.mean([p[0] for p in all_points])) center_y = int(np.mean([p[1] for p in all_points])) # 计算椭圆参数 left_corner = face_landmarks.landmark[61] right_corner = face_landmarks.landmark[291] major_axis = int(np.sqrt( ((left_corner.x - right_corner.x) * w) ** 2 + ((left_corner.y - right_corner.y) * h) ** 2 ) / 2) minor_axis = int(major_axis * 0.6) # 创建椭圆掩码 mask = np.zeros(img.shape[:2], dtype=np.uint8) cv2.ellipse(mask, (center_x, center_y), (major_axis, minor_axis), 0, 0, 360, 255, -1) # 提取舌头区域 tongue_region = cv2.bitwise_and(img, img, mask=mask) return tongue_region, mask, (center_x, center_y, major_axis, minor_axis) def extract_color_features(self, tongue_region, mask): """提取颜色特征""" # 只计算掩码内的像素 pixels = tongue_region[mask > 0] if len(pixels) == 0: return None # 转换到LAB空间 lab_pixels = cv2.cvtColor( pixels.reshape(-1, 1, 3), cv2.COLOR_BGR2LAB ).reshape(-1, 3) # 计算LAB均值 l_mean = np.mean(lab_pixels[:, 0]) a_mean = np.mean(lab_pixels[:, 1]) b_mean = np.mean(lab_pixels[:, 2]) # 计算颜色分布直方图(用于判断舌苔覆盖率) # 这里简化处理,实际项目中需要更精细的分区统计 return { 'L': float(l_mean), 'A': float(a_mean), 'B': float(b_mean), 'pixel_count': len(pixels) } def classify_tongue_color(self, lab_features): """基于LAB值分类舌质颜色""" a_val = lab_features['A'] b_val = lab_features['B'] # 基于经验阈值的分类规则 # 这些阈值需要在标准数据集上校准 if a_val < 128 and b_val < 128: return '淡白舌', 0.85 elif a_val > 145 and b_val > 140: return '红舌', 0.82 elif a_val > 150 and b_val > 145: return '绛舌', 0.78 else: return '淡红舌', 0.80 def analyze(self, image_path): """完整分析流程""" # 1. 预处理 img = self.preprocess(image_path) # 2. 定位舌头 result = self.locate_tongue(img) if result[0] is None: return {'error': result[1]} tongue_region, mask, ellipse_params = result # 3. 提取颜色特征 color_features = self.extract_color_features(tongue_region, mask) if color_features is None: return {'error': '舌头区域提取失败'} # 4. 分类舌色 color_category, confidence = self.classify_tongue_color(color_features) # 5. 构建VL模型输入 vl_input = { 'image': self._encode_image(tongue_region), 'features': { 'lab': color_features, 'color_category': color_category, 'confidence': confidence } } return vl_input def _encode_image(self, img): """将图像编码为base64""" _, buffer = cv2.imencode('.jpg', img) return base64.b64encode(buffer).decode('utf-8')这段代码是整个引擎的核心骨架。实际部署时,我会在analyze方法的最后一步调用VL模型API,把vl_input发送出去,然后解析返回的文本描述。
3.3 参数校准的实操记录
颜色分类的阈值不是拍脑袋定的,我用了大概三周时间做校准。具体方法是:收集500张标注好的舌象图片,覆盖淡白、淡红、红、绛四种舌色,每种至少100张。然后在LAB空间里画出每种舌色的A-B散点图,观察它们的分布边界。
校准过程中发现一个有意思的现象:淡红舌和红舌在A通道上的分界线大约在138左右,但不同设备拍摄的图片,这个分界线会有±5的漂移。所以我在最终版本里加入了一个自适应校准机制:如果连续10张图片的分类置信度都低于0.7,系统会自动把阈值向当前数据的均值方向调整2个单位。
注意:这个自适应机制只适合个人使用场景。如果是多人共用的系统,不同用户的拍摄习惯差异太大,自适应反而会导致阈值漂移。这种情况下应该为每个用户维护独立的校准参数。
4. 常见问题与排查技巧实录
4.1 舌区定位失败的六种典型情况
在实际使用中,舌区定位失败是最常见的问题。我整理了六种典型情况,以及对应的排查和解决方法。
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 完全检测不到人脸 | 图像过暗或过亮 | 检查图像平均亮度 | 提示用户调整光线,或自动做直方图均衡化 |
| 检测到人脸但舌区偏移 | 头部姿态角度过大 | 检查关键点置信度 | 加入姿态判断,偏转超过30度时拒绝处理 |
| 舌区只覆盖了部分舌头 | 嘴巴张开程度不够 | 检查上下唇关键点距离 | 提示用户“请尽量伸出舌头” |
| 舌区包含了牙齿 | 椭圆短轴比例过大 | 可视化掩码区域 | 将短轴比例从0.6降到0.5 |
| 舌区包含了口腔内壁 | 椭圆中心偏移 | 检查中心点坐标 | 将中心点向嘴唇内侧方向微调 |
| 图像模糊导致关键点抖动 | 拍摄时手抖 | 计算关键点方差 | 加入图像清晰度检测,模糊时提示重拍 |
这六种情况里,最麻烦的是头部姿态问题。MediaPipe在正脸情况下非常稳定,但一旦用户侧头超过30度,嘴唇关键点就会开始漂移。我的解决方案是在预处理阶段加入一个简单的姿态估计:计算左右眼关键点的水平距离与垂直距离的比值,如果比值小于2.5,就判定为侧脸角度过大。
4.2 颜色分析偏差的排查思路
颜色分析出问题的时候,排查起来比定位问题更棘手,因为偏差往往是渐变的,不像定位失败那样有明显的错误提示。我的排查流程是这样的:
第一步,检查白平衡校正是否生效。把校正前后的图像并排显示,观察牙齿区域的颜色是否从偏黄/偏蓝变成了中性灰。如果校正后牙齿还是明显偏色,说明牙齿区域提取有问题,可能是嘴唇遮挡了牙齿。
第二步,检查LAB值的分布范围。正常情况下,舌质区域的A通道值应该在125到155之间,B通道值在130到160之间。如果A通道值低于120,说明图像整体偏绿,可能是光源问题;如果高于160,说明偏红过度,可能是美颜滤镜的饱和度增强导致的。
第三步,对比历史数据。我维护了一个用户的历史分析记录,如果本次分析的LAB值与历史均值的偏差超过15个单位,系统会自动标记为“异常”,并提示用户确认拍摄条件是否发生了变化。
实操心得:很多用户习惯在卫生间拍摄舌象,因为那里有镜子方便观察。但卫生间的灯光往往是暖色调的,而且色温不稳定。我一般建议用户在白天靠窗的位置拍摄,自然光的显色指数最高,颜色还原最准确。如果只能用人工光源,尽量选择显色指数Ra>90的LED灯。
4.3 VL模型输出不稳定的应对策略
VL模型虽然强大,但输出不稳定是它的天性。同一张图片,今天问和明天问,得到的描述可能完全不同。我的应对策略是“三重校验”。
第一重,数值一致性校验。VL模型输出的描述中如果提到了具体的颜色(如“舌质偏红”),我会用规则引擎检查这个描述是否与第二层计算的数值特征一致。如果数值显示A通道值为130(淡红范围),但模型输出“舌质红”,就判定为不一致,触发重新生成。
第二重,术语规范性校验。中医舌诊有一套标准术语,比如“薄白苔”“黄腻苔”“齿痕舌”等。我会维护一个术语词典,检查VL模型输出中是否使用了非标准术语。如果出现了“舌头有点白白的”这种口语化表达,就用后处理规则替换成标准术语。
第三重,多轮投票机制。对于关键判断(如舌质颜色的最终分类),我会用不同的提示词模板调用VL模型三次,然后取多数结果作为最终输出。虽然这会增加API调用成本,但对于医疗健康类应用来说,准确性比成本更重要。
def validate_vl_output(vl_output, numeric_features): """校验VL模型输出与数值特征的一致性""" errors = [] # 检查颜色描述一致性 color_keywords = { '淡白': (0, 130), '淡红': (130, 140), '红': (140, 150), '绛': (150, 255) } a_value = numeric_features['A'] expected_color = None for color, (low, high) in color_keywords.items(): if low <= a_value < high: expected_color = color break if expected_color and expected_color not in vl_output: errors.append(f"颜色描述不一致:数值显示{expected_color}," f"但模型输出中未提及") # 检查术语规范性 non_standard_terms = ['有点', '比较', '稍微', '看起来'] for term in non_standard_terms: if term in vl_output: errors.append(f"检测到非标准术语:{term}") return errors这套校验机制上线后,VL模型输出的稳定性有了明显提升。之前同一张图片连续分析五次,可能会有两到三种不同的描述;现在基本能保证五次中有四次以上是一致的。
4.4 性能优化的几个关键点
整个引擎在树莓派4B上跑,从图像输入到输出结果,耗时大约在2.5秒左右。其中MediaPipe人脸检测占0.8秒,颜色特征计算占0.3秒,VL模型API调用占1.4秒。如果去掉VL模型调用,纯本地处理可以压缩到1.2秒以内。
优化MediaPipe推理速度的方法有几个:把static_image_mode设为True可以跳过视频帧间的跟踪逻辑,节省约30%的时间;把输入图像的分辨率从640x480降到480x360,检测精度基本不变,但速度能提升40%;如果不需要3D关键点,可以把refine_landmarks设为False,又能省下15%左右的计算量。
颜色特征计算的优化空间不大,主要是避免在Python循环里逐像素操作。我一开始用for循环遍历掩码内的像素,一张图要跑3秒多。后来改成numpy的向量化操作,直接对整个数组做布尔索引,速度提升了将近20倍。
VL模型API调用的耗时主要取决于网络延迟和模型推理时间。我的做法是把图像压缩到256x256再编码,base64字符串的长度从原来的200KB降到30KB左右,网络传输时间大幅缩短。同时把提示词模板精简到200字以内,减少模型的输入处理时间。
5. 从“能用”到“好用”的迭代经验
5.1 用户反馈驱动的功能演进
第一版上线后,我收集了大约50个用户的反馈。出现频率最高的问题是“为什么我的舌头拍出来颜色不对”。深入排查后发现,大部分问题出在拍摄环节,而不是算法本身。用户不知道什么样的拍摄条件才算合格,拍出来的照片要么太暗,要么角度太偏。
于是我在第二版里加入了一个实时拍摄引导功能。在相机预览界面上叠加一个半透明的舌头轮廓模板,用户把舌头对准模板后,系统自动检测光线亮度和头部姿态,只有所有条件都满足时才允许拍摄。这个功能上线后,颜色分析相关的投诉下降了70%以上。
另一个高频反馈是“分析结果看不懂”。VL模型生成的描述虽然专业,但对于没有中医背景的用户来说,理解门槛太高。我在第三版里加入了一个通俗解释层,把“舌质淡红,薄白苔”翻译成“你的舌头颜色正常,舌苔薄而白,属于健康状态”。这个改动让用户满意度有了明显提升。
5.2 数据积累与模型迭代的闭环
这个项目最有价值的部分,其实是它形成的数据闭环。每一次分析,系统都会把原始图像、数值特征、VL模型输出、用户反馈(如果有的话)打包存储。积累到一定量级后,这些数据可以用来做三件事。
第一是阈值校准。用真实数据重新拟合颜色分类的边界,比手工设定的阈值更准确。我最近一次校准用了3000条记录,把淡红舌和红舌的分界线从138调整到了141,分类准确率提升了约5个百分点。
第二是提示词优化。分析VL模型输出与用户反馈不一致的案例,找出提示词模板中的薄弱环节。比如我发现模型经常把“齿痕”和“裂纹”搞混,于是在提示词里增加了明确的区分说明,这类错误就少了很多。
第三是异常检测。用历史数据训练一个简单的异常检测模型,当新的分析结果与历史模式差异过大时,自动标记出来人工复核。这个机制帮我发现了不少边缘案例,比如某些药物会导致舌苔颜色异常,这些案例在标准数据集里是找不到的。
5.3 后续可以扩展的方向
这个引擎目前只做了舌象的静态分析,但中医舌诊其实是一个动态过程。舌象会随着病情变化而变化,如果能做成连续监测,价值会大很多。我下一步的计划是加入时序分析功能,用户每天拍摄一张舌象照片,系统自动生成舌象变化趋势图,并标注出显著变化的节点。
另一个方向是多模态融合。舌诊只是中医四诊(望闻问切)中的“望”的一部分,如果能结合面诊、脉诊(通过可穿戴设备采集)、问诊(通过对话收集症状),就能构建一个更完整的中医健康评估系统。当然,这需要跨学科的合作,不是一个人能搞定的。
技术层面,我还在探索用知识图谱来增强VL模型的推理能力。把中医舌诊的规则(如“舌红苔黄主热证”)编码成图谱结构,让VL模型在生成描述时参考图谱中的关系,而不是完全依赖预训练知识。初步实验显示,这种方法可以把诊断建议的合理性提升约15%。
最后分享一个小技巧:如果你也在做类似的项目,建议从第一天就开始记录日志。不只是记录成功案例,更要详细记录失败案例的输入图像、中间结果和错误信息。这些失败案例的价值,往往比成功案例大十倍。我在排查一个颜色偏差问题时,翻看了三个月前的失败日志,发现是某个手机型号的自动白平衡算法在特定光线下会引入系统性偏差。如果没有日志,这个问题可能永远都找不到根因。