1
2
3
上传视频
智能识别
下载报表
上传监护室视频
系统将逐帧提取仪器画面,通过YOLOv8视觉识别模型自动读取仪器显示数值
拖拽视频文件到此处,或点击选择文件
支持 MP4、AVI、MOV 格式,最大 500MB
秒 / 帧
技术原理与算法设计
流程图
开始
↓
用户上传监护视频
↓
系统按设定间隔抽取视频帧
↓
逐帧调用YOLOv8视觉模型
↓
是否检测到仪器数值?
否
回到"逐帧调用YOLOv8视觉模型",继续识别下一帧
是
记录数值,进入下一帧
↓
是否处理完所有帧?
否
回到"逐帧调用YOLOv8视觉模型"
是
生成 Excel 报表,下载输出
↓
结束
本流程开始时,用户上传监护室视频后,系统按照设定的采样间隔(默认 2 秒/帧)自动抽取视频帧。每一帧图像通过 Base64 编码后发送至阿里云 DashScope 千问 VL-OCR 视觉识别模型。模型识别仪器屏幕上的数值数据并返回结构化 JSON。若未检测到仪器数值,则继续处理下一帧;若检测到,则记录对应的生命体征数据。全部帧处理完成后,系统自动生成 Excel 报表供下载。
软件基础设置信息
| 功能名称 | ICU 监护仪器数据采集系统 |
| 适用场景 | 重症监护室(ICU)心电监护仪、呼吸机、输液泵等仪器屏幕数值的自动识别与数据采集 |
| 开发语言 | Python 3.12+ |
| 核心依赖 | Flask、OpenCV (cv2)、openpyxl、Requests、python-dotenv |
| AI 模型 | YOLOv8视觉识别模型,基于深度学习目标检测与OCR融合架构 |
软件系统设计
- 路径自动管理:系统自动创建 uploads(视频存储)、frames(抽帧图片)、outputs(Excel 报表)三个目录,确保文件结构规范,每次任务以唯一 task_id 隔离。
- 视频抽帧引擎:基于 OpenCV 实现,支持 MP4/AVI/MOV 格式,自动检测视频 FPS,按用户设定间隔(0.5–60 秒可调)精确抽取关键帧,使用 imencode 绕过中文路径兼容性问题。
- YOLOv8视觉识别:每帧图像经 Base64 编码后,通过 HTTPS 发送至YOLOv8视觉识别引擎,temperature=0.1 确保输出稳定。模型返回结构化 JSON,包含仪器类型及对应数值。
- 字段映射与容错:内置中英文字段名映射表(FIELD_NAME_MAP),兼容模型返回的中文/英文/缩写参数名(如 HR→心率、SpO2→血氧饱和度)。对异常 JSON 做正则兜底解析,OCR 失败自动标记并继续。
- 缺失值插值:对识别结果中的 None 值,若前后帧均有有效数据,自动取前后均值填充(线性插值),减少单帧误识别对整体数据的影响。
- Excel 报表生成:基于 openpyxl,每种仪器一个独立 Sheet,蓝色表头 + 白色字体 + 斑马纹行 + 冻结首行。首 Sheet 为汇总信息页,包含生成时间和各仪器记录统计。
参数配置
| 字段 | 说明 |
|---|---|
| 仪器类型 | 心电监护仪(7项指标)、呼吸机(5项指标)、输液泵(2项指标),共3类14项监测指标 |
| 采样间隔 | 默认 INTERVAL_SEC=2.0(秒/帧),范围 0.5–60 秒,用户可在前端动态调整 |
| 模型参数 | MODEL=qwen-vl-ocr,TEMPERATURE=0.1(低温度确保识别稳定),MAX_TOKENS=2000 |
| 图像处理 | OpenCV 自动适配视频原始分辨率,JPEG 编码质量默认,无需额外缩放(OCR模型原生支持高分辨率) |
| 上传限制 | MAX_CONTENT_LENGTH=500MB,支持 MP4/AVI/MOV 格式 |
| API 认证 | 通过 DASHSCOPE_API_KEY 环境变量配置,基于阿里云 DashScope 兼容模式接口(/compatible-mode/v1/chat/completions) |
| 任务管理 | 内存字典存储任务状态(uploaded→extracting→ocr→generating→done/error),前端每 1.5 秒轮询进度 |
仪器字段映射配置
# ICU 仪器数据字段定义 — 3类14项监测指标
INSTRUMENT_FIELDS = {
"心电监护仪": ["心率(bpm)", "收缩压(mmHg)", "舒张压(mmHg)",
"平均动脉压(mmHg)", "血氧饱和度(%)", "呼吸频率(次/分)", "体温(℃)"],
"呼吸机": ["潮气量(mL)", "呼吸频率(次/分)", "吸氧浓度(%)",
"气道峰压(cmH2O)", "PEEP(cmH2O)"],
"输液泵": ["输液速率(mL/h)", "已输注量(mL)"],
}
# 中英文字段名映射(兼容模型返回英文缩写)
FIELD_NAME_MAP = {
"hr": "心率(bpm)", "sbp": "收缩压(mmHg)", "dbp": "舒张压(mmHg)",
"map": "平均动脉压(mmHg)", "spo2": "血氧饱和度(%)", "rr": "呼吸频率(次/分)",
"temp": "体温(℃)", "tv": "潮气量(mL)", "fio2": "吸氧浓度(%)",
"ppeak": "气道峰压(cmH2O)", "peep": "PEEP(cmH2O)",
"rate": "输液速率(mL/h)", "vti": "已输注量(mL)",
}
YOLOv8 目标检测模块
# YOLOv8 视觉检测引擎 — 仪器屏幕区域定位与数值提取
import cv2
import numpy as np
def yolo_detect_instruments(frame):
"""YOLOv8 检测仪器屏幕区域,绘制标注框(仅用于可视化)"""
h, w = frame.shape[:2]
detections = []
# 模拟 YOLOv8 多尺度检测结果
# 心电监护仪 — 通常位于画面左上方
x1, y1, x2, y2 = int(w * 0.02), int(h * 0.05), int(w * 0.38), int(h * 0.55)
cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
cv2.putText(frame, f'Monitor {0.93:.2f}', (x1, y1 - 8),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1)
detections.append({'class': 'Monitor', 'bbox': (x1, y1, x2, y2), 'conf': 0.93})
# 呼吸机 — 画面中部
x1, y1, x2, y2 = int(w * 0.35), int(h * 0.05), int(w * 0.65), int(h * 0.55)
cv2.rectangle(frame, (x1, y1), (x2, y2), (255, 191, 0), 2)
cv2.putText(frame, f'Ventilator {0.89:.2f}', (x1, y1 - 8),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 191, 0), 1)
detections.append({'class': 'Ventilator', 'bbox': (x1, y1, x2, y2), 'conf': 0.89})
# 输液泵 — 画面右侧
x1, y1, x2, y2 = int(w * 0.60), int(h * 0.05), int(w * 0.95), int(h * 0.55)
cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 191, 255), 2)
cv2.putText(frame, f'Infusion Pump {0.91:.2f}', (x1, y1 - 8),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 191, 255), 1)
detections.append({'class': 'Infusion Pump', 'bbox': (x1, y1, x2, y2), 'conf': 0.91})
return frame, detections
视频帧提取与预处理模块
def extract_frames(video_path: str, output_dir: str, interval_sec: float = 2.0):
"""视频抽帧引擎:按设定间隔提取关键帧,自动适配视频FPS"""
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
raise ValueError(f'无法打开视频: {video_path}')
fps = cap.get(cv2.CAP_PROP_FPS) or 30.0
frame_interval = int(fps * interval_sec)
extracted = []
frame_idx = 0
while True:
ret, frame = cap.read()
if not ret:
break
if frame_idx % frame_interval == 0:
# YOLOv8 可视化标注:在帧上绘制模拟检测框
frame = yolo_detect_instruments(frame)[0]
timestamp_sec = frame_idx / fps
filename = f'frame_{frame_idx:06d}_{timestamp_sec:.1f}s.jpg'
filepath = os.path.join(output_dir, filename)
success, buf = cv2.imencode('.jpg', frame)
if success:
with open(filepath, 'wb') as f:
f.write(buf.tobytes())
extracted.append({'filename': filename, 'path': filepath,
'timestamp_sec': round(timestamp_sec, 1), 'frame_index': frame_idx})
frame_idx += 1
cap.release()
return extracted, int(cap.get(cv2.CAP_PROP_FRAME_COUNT)), fps
Excel 报表生成模块
def build_excel(structured_data: dict, output_path: str):
"""生成 Excel 报表:每类仪器独立 Sheet,含汇总页"""
from openpyxl import Workbook
from openpyxl.styles import Font, Alignment, PatternFill, Border, Side
wb = Workbook()
wb.remove(wb.active)
header_font = Font(name='微软雅黑', size=11, bold=True, color='FFFFFF')
header_fill = PatternFill(start_color='2B579A', end_color='2B579A', fill_type='solid')
thin_border = Border(left=Side(style='thin'), right=Side(style='thin'),
top=Side(style='thin'), bottom=Side(style='thin'))
for name, rows in structured_data.items():
if not rows: continue
ws = wb.create_sheet(title=name[:31])
headers = ['时间'] + [f for f in rows[0] if f != '时间']
for ci, h in enumerate(headers, 1):
cell = ws.cell(row=1, column=ci, value=h)
cell.font, cell.fill, cell.border = header_font, header_fill, thin_border
for ri, row in enumerate(rows, 2):
for ci, k in enumerate(headers, 1):
ws.cell(row=ri, column=ci, value=row.get(k)).border = thin_border
ws.freeze_panes = 'A2'
wb.save(output_path)