识别引擎未连接
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融合架构

软件系统设计

  1. 路径自动管理:系统自动创建 uploads(视频存储)、frames(抽帧图片)、outputs(Excel 报表)三个目录,确保文件结构规范,每次任务以唯一 task_id 隔离。
  2. 视频抽帧引擎:基于 OpenCV 实现,支持 MP4/AVI/MOV 格式,自动检测视频 FPS,按用户设定间隔(0.5–60 秒可调)精确抽取关键帧,使用 imencode 绕过中文路径兼容性问题。
  3. YOLOv8视觉识别:每帧图像经 Base64 编码后,通过 HTTPS 发送至YOLOv8视觉识别引擎,temperature=0.1 确保输出稳定。模型返回结构化 JSON,包含仪器类型及对应数值。
  4. 字段映射与容错:内置中英文字段名映射表(FIELD_NAME_MAP),兼容模型返回的中文/英文/缩写参数名(如 HR→心率、SpO2→血氧饱和度)。对异常 JSON 做正则兜底解析,OCR 失败自动标记并继续。
  5. 缺失值插值:对识别结果中的 None 值,若前后帧均有有效数据,自动取前后均值填充(线性插值),减少单帧误识别对整体数据的影响。
  6. 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)