ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于YOLOv8与Flask的牙齿龋齿检测系统:从数据准备到Web部署全流程

基于YOLOv8与Flask的牙齿龋齿检测系统:从数据准备到Web部署全流程 简介本资源是一套面向计算机视觉初学者与毕业设计学生的牙齿龋齿智能检测完整实现方案基于DentalAI医学数据集与YOLOv8目标检测模型解决口腔影像中龋齿病灶的自动化定位与可视化诊断问题适用于深度学习课程实践、医学AI课题开发及Web端轻量化部署场景。压缩包共9个文件含3个Jupyter Notebook涵盖数据格式转换、模型训练与预测全流程、1个PyTorch模型文件best.pt、1个Web前端HTML页面index.html、1个核心检测脚本object_detector.py、1张示例图像caries.jpg及requirements.txt等支撑文件整体大小45.9MB结构紧凑、模块职责清晰。已有134人学习下载提供开箱即用的Web交互界面用户可直接上传牙片图像获取带置信度标注的龋齿检测结果配套说明文档与可执行脚本显著降低部署门槛是少有的融合医学数据处理、YOLOv8训练调优与Flask/Dash类轻量Web集成的垂直领域实践案例。1. 项目概述从零构建一个牙齿龋齿智能检测系统最近在整理过去的项目资料翻到了我研究生时期做的一个毕业设计一个基于DentalAI数据集和YOLOv8的牙齿龋齿检测系统。这个项目在当时获得了不错的评价因为它不仅是一个纯粹的算法模型更是一个从数据准备、模型训练到最终部署成Web应用的完整闭环。今天我想把这个项目的完整实现思路、踩过的坑以及一些实用的技巧分享出来希望能给正在做计算机视觉、医学图像分析或者毕业设计的同学一些参考。这个项目本质上是一个目标检测任务核心是利用YOLOv8模型在牙齿X光片上自动定位并识别出龋齿也就是我们常说的蛀牙区域。最终我们通过一个简洁的Python Web界面让牙医或者研究人员可以上传图片实时看到检测结果包括龋齿的位置和置信度。对于刚接触深度学习和Web开发的同学来说这是一个非常好的练手项目能让你系统地走完AI项目落地的全流程。2. 项目整体设计与技术选型思路2.1 为什么选择YOLOv8与DentalAI数据集在做这个项目之初技术选型是第一个要解决的问题。在目标检测领域YOLO系列一直是“快准狠”的代表。我选择YOLOv8主要是基于以下几点考量。首先YOLOv8是Ultralytics公司在2023年初推出的最新版本它在保持YOLO系列实时性优势的同时在精度和易用性上有了显著提升。其提供的Python接口非常友好几行代码就能完成模型的训练、验证和预测这对于毕业设计这种时间有限的项目来说至关重要能让我把更多精力放在业务逻辑和效果优化上。其次YOLOv8社区活跃文档和预训练模型丰富遇到问题很容易找到解决方案。最后从性能上看YOLOv8在COCO等通用数据集上的表现已经非常出色将其迁移到牙齿X光这样的专业领域有一个很高的起点。至于数据集DentalAI是一个公开的、专注于口腔医学图像分析的数据集其中包含了标注好的牙齿X光图像龋齿区域都用边界框Bounding Box精确标出。使用一个规范、高质量的数据集能避免自己从头标注的巨大工作量并且数据格式相对统一减少了数据清洗和整理的麻烦。这让我能快速进入模型训练和调优的核心环节。当然公开数据集也可能存在样本分布不均、标注标准差异等问题这需要在后续的训练策略中加以应对。2.2 系统架构从模型到Web应用的全链路设计整个系统的架构可以清晰地分为三个层次后端深度学习模型、中间业务逻辑层以及前端Web展示层。后端核心是训练好的YOLOv8模型文件通常是.pt格式它负责接收图像并输出检测结果。中间业务逻辑层使用Python的Flask或FastAPI框架搭建它扮演了桥梁的角色负责接收前端上传的图片调用后端模型进行推理然后将模型返回的检测框、类别和置信度信息处理成前端易于展示的格式比如生成带标注框的新图片或者结构化的JSON数据。前端Web界面则相对轻量主要是一个上传图片的按钮和一个展示检测结果的区域可以使用简单的HTML、CSS和JavaScript实现也可以借助Bootstrap等框架快速搭建一个美观的界面。这种分层架构的好处是解耦模型迭代、业务逻辑调整和界面美化可以相对独立地进行。3. 核心环节实现与实操要点3.1 环境配置与YOLOv8安装避坑指南万事开头难一个稳定、兼容的环境是项目成功的基础。我强烈建议使用Anaconda来创建独立的Python环境避免包版本冲突。我的环境基础是Python 3.8PyTorch 1.12。安装YOLOv8非常简单直接使用pippip install ultralytics。但这里有几个坑需要特别注意。第一PyTorch的安装。务必去PyTorch官网根据你的CUDA版本如果你有NVIDIA显卡且打算用GPU加速或选择CPU版本生成对应的安装命令。很多人直接pip install torch可能会安装成CPU版本导致后续无法利用GPU训练速度慢上百倍。你可以通过运行python -c “import torch; print(torch.cuda.is_available())”来验证GPU是否可用。第二其他依赖库。除了ultralytics我们还需要opencv-python用于图像处理、flask或fastapi用于构建Web服务、pillow图像处理等。建议创建一个requirements.txt文件一次性安装。ultralytics8.0.0 torch1.12.0 torchvision0.13.0 opencv-python-headless4.5.0 flask2.0.0 pillow9.0.0 numpy1.20.0使用命令pip install -r requirements.txt进行安装。这里选择opencv-python-headless是为了避免在服务器通常无图形界面上安装完整的OpenCV可能带来的依赖问题。注意如果你的训练机器是GTX 1660 Ti这类显卡确保安装的CUDA版本与PyTorch版本匹配。例如PyTorch 1.12可能对应CUDA 11.6。不匹配会导致无法识别GPU。3.2 DentalAI数据集的准备与预处理拿到DentalAI数据集后第一步不是直接开始训练而是“读懂”它。通常公开数据集会提供图像文件如images/目录和标注文件如labels/目录或一个单独的annotations.json。YOLOv8训练需要特定格式的标注每个图像对应一个.txt文件每行表示一个目标格式为class_id x_center y_center width height其中坐标是归一化后的即除以图像宽高。你需要检查DentalAI的标注格式并可能需要进行转换。假设原始标注是COCO的JSON格式你需要写一个转换脚本。这个脚本的核心逻辑是遍历JSON文件中的每个标注找到对应的图像尺寸然后将边界框的绝对坐标转换为YOLO格式的相对坐标。import json from pathlib import Path def convert_coco_to_yolo(coco_json_path, output_label_dir): with open(coco_json_path, ‘r’) as f: data json.load(f) # 创建图像id到文件名的映射 images {img[‘id’]: img for img in data[‘images’]} # 创建类别id到连续id的映射YOLO要求从0开始 categories {cat[‘id’]: idx for idx, cat in enumerate(data[‘categories’])} for ann in data[‘annotations’]: image_info images[ann[‘image_id’]] img_w, img_h image_info[‘width’], image_info[‘height’] # 获取COCO格式的bbox: [x_min, y_min, width, height] x_min, y_min, bbox_w, bbox_h ann[‘bbox’] # 计算中心点和归一化 x_center (x_min bbox_w / 2) / img_w y_center (y_min bbox_h / 2) / img_h w_norm bbox_w / img_w h_norm bbox_h / img_h class_id categories[ann[‘category_id’]] # 准备写入的文本行 label_line f“{class_id} {x_center:.6f} {y_center:.6f} {w_norm:.6f} {h_norm:.6f}\n” # 生成对应的label文件路径与图片同名后缀为.txt label_file_path Path(output_label_dir) / f“{Path(image_info[‘file_name’]).stem}.txt” with open(label_file_path, ‘a’) as label_file: label_file.write(label_line) # 调用示例 convert_coco_to_yolo(‘annotations/train.json’, ‘labels/train’)预处理还包括数据集划分。你需要将数据分为训练集、验证集和测试集例如70%15%15%。划分时要注意类别平衡确保每个集合中都有各类别的样本。划分好后创建一个dataset.yaml配置文件这是YOLOv8训练所必需的。# dataset.yaml path: /path/to/your/DentalAI_dataset # 数据集根目录 train: images/train # 训练集图片相对路径 val: images/val # 验证集图片相对路径 test: images/test # 测试集图片相对路径可选 nc: 1 # 类别数量这里只有‘龋齿’一类 names: [‘caries’] # 类别名称列表3.3 YOLOv8模型训练与调优实战配置好数据和环境就可以开始训练了。YOLOv8的训练API非常简洁。我通常从一个预训练模型开始这能加速收敛并提升最终性能。YOLOv8提供了不同大小的模型如n, s, m, l, x权衡速度和精度。对于牙齿X光图像细节很重要我选择了YOLOv8m中等规模作为起点。from ultralytics import YOLO # 加载预训练模型 model YOLO(‘yolov8m.pt’) # 开始训练 results model.train( data‘dataset.yaml’, epochs100, # 训练轮数 imgsz640, # 输入图像尺寸 batch16, # 批次大小根据GPU内存调整 device‘0’, # 使用GPU 0如果是CPU则设为‘cpu’ workers4, # 数据加载线程数 project‘runs/train’, # 结果保存目录 name‘exp_name’, # 实验名称 pretrainedTrue, # 使用预训练权重 optimizer‘AdamW’, # 优化器 lr00.01, # 初始学习率 cos_lrTrue, # 使用余弦退火学习率调度 )训练过程中的关键监控点是损失函数曲线和验证集上的指标如mAP0.5。你可以在runs/train/exp_name目录下找到这些可视化结果。如果训练损失下降但验证集mAP不升反降很可能出现了过拟合。这时需要采取对策一是增加数据增强的强度YOLOv8内置了Mosaic、MixUp等增强可以通过augmentTrue开启并调整相关参数二是使用早停Early Stopping和模型权重衰减三是尝试更小的模型如YOLOv8s或减少网络深度/宽度的缩放因子。实操心得学习率lr0是最重要的超参数之一。对于小数据集建议从更小的值开始如0.001并使用cos_lr让学习率平滑下降。批量大小batch尽可能设大直到占满GPU内存这有助于训练稳定。我的GTX 1660 Ti上imgsz640, batch16是可行的。3.4 模型验证与性能评估解读训练完成后不能只看最后的训练日志必须对模型进行系统的评估。使用YOLOv8内置的验证功能可以快速得到关键指标。from ultralytics import YOLO model YOLO(‘runs/train/exp_name/weights/best.pt’) # 加载最佳权重 metrics model.val(data‘dataset.yaml’, split‘val’) print(metrics.box.map) # 打印mAP0.5:0.95 print(metrics.box.map50) # 打印mAP0.5对于医学图像检测mAP0.5即IoU阈值为0.5时的平均精度是一个核心指标它综合衡量了模型定位和分类的准确性。但仅此还不够。我还会特别关注召回率Recall因为在医疗场景中漏检False Negative的代价通常比误检False Positive更高——我们宁愿系统多提示几个可疑区域让医生复核也不希望它漏掉一个真实的龋齿。因此我会在验证集上运行模型并手动检查一些困难样本如龋齿早期、边缘模糊、与正常牙缝混淆的情况分析模型失败的原因。这些分析会反馈到数据层面比如是否需要针对这些困难样本进行数据增强或补充标注。3.5 基于Flask的Web界面开发与集成模型训练好之后我们要给它一个“用武之地”。我选择了Flask来构建Web后端因为它轻量、灵活学习曲线平缓。整个后端应用的核心是一个接收图片、调用模型、返回结果的接口。首先构建一个简单的Flask应用结构web_app/ ├── app.py # Flask主应用 ├── static/ │ ├── uploads/ # 临时存放上传的图片 │ └── results/ # 存放生成的结果图片可选 ├── templates/ │ └── index.html # 前端页面 └── weights/ └── best.pt # 训练好的YOLOv8模型app.py的核心代码如下from flask import Flask, render_template, request, jsonify, send_from_directory from werkzeug.utils import secure_filename from ultralytics import YOLO import os import cv2 app Flask(__name__) app.config[‘UPLOAD_FOLDER’] ‘static/uploads/’ app.config[‘RESULT_FOLDER’] ‘static/results/’ ALLOWED_EXTENSIONS {‘png’, ‘jpg’, ‘jpeg’, ‘bmp’} # 加载训练好的模型全局加载一次避免每次请求重复加载 model YOLO(‘weights/best.pt’) def allowed_file(filename): return ‘.’ in filename and filename.rsplit(‘.’, 1)[1].lower() in ALLOWED_EXTENSIONS app.route(‘/‘, methods[‘GET’]) def index(): return render_template(‘index.html’) app.route(‘/predict’, methods[‘POST’]) def predict(): if ‘file’ not in request.files: return jsonify({‘error’: ‘No file part’}) file request.files[‘file’] if file.filename ‘’: return jsonify({‘error’: ‘No selected file’}) if file and allowed_file(file.filename): filename secure_filename(file.filename) upload_path os.path.join(app.config[‘UPLOAD_FOLDER’], filename) file.save(upload_path) # 使用模型进行预测 results model(upload_path) # 处理预测结果 result results[0] # 假设单张图片 # 获取检测信息框、置信度、类别 boxes result.boxes.xyxy.cpu().numpy() # 边界框坐标 (x1, y1, x2, y2) confidences result.boxes.conf.cpu().numpy() # 置信度 class_ids result.boxes.cls.cpu().numpy().astype(int) # 类别ID # 在原始图像上绘制检测框 img_with_boxes result.plot() # Ultralytics提供的便捷方法 result_path os.path.join(app.config[‘RESULT_FOLDER’], f‘detected_{filename}’) cv2.imwrite(result_path, cv2.cvtColor(img_with_boxes, cv2.COLOR_RGB2BGR)) # 组织返回给前端的数据 detections [] for box, conf, cls_id in zip(boxes, confidences, class_ids): detections.append({ ‘bbox’: box.tolist(), ‘confidence’: float(conf), ‘class’: result.names[cls_id] }) return jsonify({ ‘success’: True, ‘detections’: detections, ‘result_image_url’: f‘/static/results/detected_{filename}’ }) else: return jsonify({‘error’: ‘File type not allowed’}) if __name__ ‘__main__’: os.makedirs(app.config[‘UPLOAD_FOLDER’], exist_okTrue) os.makedirs(app.config[‘RESULT_FOLDER’], exist_okTrue) app.run(host‘0.0.0.0’, port5000, debugTrue) # debugTrue仅用于开发前端页面index.html则负责提供上传界面和展示结果。这里使用简单的HTML和JavaScript通过Fetch API与后端交互。!DOCTYPE html html head title牙齿龋齿检测系统/title style body { font-family: Arial; margin: 40px; } .container { max-width: 800px; margin: auto; } .upload-box { border: 2px dashed #ccc; padding: 40px; text-align: center; margin-bottom: 20px; } #preview, #resultImg { max-width: 100%; margin-top: 20px; display: none; } #detectionList { margin-top: 20px; } /style /head body div class“container” h1牙齿X光龋齿智能检测/h1 p上传一张牙齿X光片支持PNG, JPG格式系统将自动检测龋齿区域。/p div class“upload-box” input type“file” id“fileInput” accept“image/*” p或将图片拖拽到此处/p /div button onclick“uploadImage()”开始检测/button div h3原图预览/h3 img id“preview” src“#” alt“Preview” /div div h3检测结果/h3 img id“resultImg” src“#” alt“Result” div id“detectionList”/div /div /div script const fileInput document.getElementById(‘fileInput’); const preview document.getElementById(‘preview’); const resultImg document.getElementById(‘resultImg’); const detectionList document.getElementById(‘detectionList’); fileInput.addEventListener(‘change’, function(e) { const file e.target.files[0]; if (file) { const reader new FileReader(); reader.onload function(e) { preview.src e.target.result; preview.style.display ‘block’; } reader.readAsDataURL(file); } }); async function uploadImage() { const file fileInput.files[0]; if (!file) { alert(‘请先选择一张图片’); return; } const formData new FormData(); formData.append(‘file’, file); try { const response await fetch(‘/predict’, { method: ‘POST’, body: formData }); const data await response.json(); if (data.success) { resultImg.src data.result_image_url; resultImg.style.display ‘block’; let listHtml ‘h4检测到 ‘ data.detections.length ‘ 个目标/h4ul’; data.detections.forEach(det { listHtml li类别${det.class} 置信度${(det.confidence*100).toFixed(2)}% 位置[${det.bbox.map(xx.toFixed(1)).join(‘, ‘)}]/li; }); listHtml ‘/ul’; detectionList.innerHTML listHtml; } else { alert(‘检测失败’ data.error); } } catch (error) { alert(‘请求出错’ error); } } // 简单拖拽上传支持 const uploadBox document.querySelector(‘.upload-box’); uploadBox.addEventListener(‘dragover’, (e) { e.preventDefault(); uploadBox.style.borderColor ‘#666’; }); uploadBox.addEventListener(‘dragleave’, () { uploadBox.style.borderColor ‘#ccc’; }); uploadBox.addEventListener(‘drop’, (e) { e.preventDefault(); uploadBox.style.borderColor ‘#ccc’; if (e.dataTransfer.files.length) { fileInput.files e.dataTransfer.files; const event new Event(‘change’, { bubbles: true }); fileInput.dispatchEvent(event); } }); /script /body /html4. 部署、优化与常见问题排查4.1 本地与服务器部署实践开发完成后你需要让其他人也能访问这个系统。本地测试直接用python app.py即可Flask会启动一个开发服务器地址通常是http://127.0.0.1:5000。但开发服务器性能弱且不安全不能用于生产环境。对于生产部署我推荐使用Gunicorn作为WSGI服务器配合Nginx做反向代理。Gunicorn能管理多个工作进程处理并发请求的能力强得多。首先安装Gunicornpip install gunicorn。然后创建一个wsgi.py文件作为入口点。# wsgi.py from app import app if __name__ “__main__”: app.run()使用Gunicorn启动应用在项目根目录下gunicorn -w 4 -b 0.0.0.0:8000 wsgi:app这里-w 4表示启动4个工作进程-b指定绑定地址和端口。接着配置Nginx。在/etc/nginx/sites-available/下创建一个配置文件比如dental_ai。server { listen 80; server_name your_domain.com; # 你的域名或服务器IP location / { proxy_pass http://127.0.0.1:8000; # 转发给Gunicorn proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location /static { alias /path/to/your/web_app/static; # 静态文件由Nginx直接处理效率更高 expires 30d; } }然后创建一个符号链接到sites-enabled目录并重启Nginx。sudo ln -s /etc/nginx/sites-available/dental_ai /etc/nginx/sites-enabled/ sudo nginx -t # 测试配置语法 sudo systemctl restart nginx现在通过你的服务器IP或域名就能访问应用了。记得在云服务器的安全组或防火墙中开放80端口。4.2 性能优化与体验提升技巧一个可用的系统和一个好用的系统之间隔着许多优化细节。以下是我在实践中总结的几个关键点1. 模型推理优化首次加载模型和进行第一次推理通常较慢。可以在Web应用启动时在app.py顶部就加载模型并将其保存在全局变量或应用上下文中实现“热启动”后续请求直接调用速度飞快。另外对于高并发场景可以考虑使用模型缓存池或者将模型服务单独部署如使用TorchServe通过API调用实现计算资源的弹性管理。2. 前端体验优化上传大尺寸X光片如全景片时如果直接上传原图速度慢且占用带宽。可以在前端使用JavaScript如使用canvas对图片进行等比例压缩限制最大边长如1024像素再上传压缩后的图片。同时在上传时显示加载动画给用户明确的反馈。检测完成后除了显示带框的图片还可以将检测到的每个龋齿区域单独裁剪出来以缩略图列表形式展示方便医生重点查看。3. 静态资源处理如Nginx配置所示将/static路径下的图片、CSS、JS文件交给Nginx直接处理远比经过Python后端处理要高效。同时设置浏览器缓存expires 30d用户再次访问时加载速度会极大提升。4. 结果缓存对于相同的图片没必要重复进行模型推理。可以在后端增加一个简单的缓存机制例如计算上传图片的MD5值作为键将检测结果包括结果图片路径和检测数据临时存储在Redis或甚至一个文件字典中设定一个过期时间如1小时。下次收到相同图片时直接返回缓存结果大幅降低响应时间。4.3 常见问题与排查实录在实际开发和部署中你几乎一定会遇到下面这些问题。这里是我的排查笔记问题一Ignoring corrupt image/label: ...警告。这是YOLOv8在加载数据时常见的警告意思是它跳过了某些损坏的图片或标签文件。首先检查报错路径下的文件是否存在、格式是否正确图片是否能正常打开标签文件是否符合YOLO格式。很多时候问题出在标签文件里包含了非数值字符、坐标值超出了[0,1]范围或者图片文件本身损坏。写一个简单的数据校验脚本遍历所有图片和标签用cv2.imread()和常规文件读取检查一遍能帮你快速定位“坏数据”。问题二Web界面访问或上传图片卡顿、缓慢。这通常是多方面原因。首先检查服务器带宽和资源使用情况top,htop命令。其次确认Nginx和Gunicorn配置是否正确Gunicorn工作进程数是否合适通常推荐CPU核心数 * 2 1。再次检查前端代码是否在上传前对图片进行了合理压缩。最后查看Flask应用日志看模型推理单次耗时是否过长。如果单张图片推理超过3秒可能需要考虑优化模型如改用YOLOv8n、使用GPU推理或者引入异步任务队列如Celery将耗时的推理任务放到后台执行通过WebSocket或轮询告知前端结果。问题三训练时Loss不下降或波动很大。这是深度学习训练中的典型问题。我的排查顺序是1)学习率这是首要怀疑对象。尝试将lr0调小一个数量级如从0.01调到0.001或使用cos_lr调度。2)数据检查数据标注质量是否有大量错误标注数据增强是否过于激进导致图像难以学习可以暂时关闭增强(augmentFalse)试试。3)模型对于小数据集预训练模型至关重要。确保pretrainedTrue。也可以尝试更小的模型如YOLOv8s以减少过拟合风险。4)批次大小在GPU内存允许范围内尽量使用大的batch size。如果只能设得很小如2或4梯度更新会很不稳定可以尝试使用梯度累积YOLOv8参数accumulate来模拟大批次效果。问题四在嵌入式设备如RK3588上部署YOLOv8模型。这是另一个常见的需求。YOLOv8的PyTorch模型.pt不能直接在嵌入式设备上高效运行。通常需要将模型转换为该平台支持的格式。对于RK3588这类有NPU的设备流程一般是PyTorch (.pt) - ONNX (.onnx) - RKNN (.rknn)。首先使用model.export(format‘onnx’)导出ONNX模型。然后使用瑞芯微提供的RKNN-Toolkit2工具链在PC上将ONNX模型量化和转换为RKNN格式。最后在嵌入式设备上使用RKNN的C或Python API加载和推理。这个过程对模型算子支持、量化精度要求很高需要仔细阅读官方文档和示例。问题五Flask应用在服务器上无法远程访问。如果你在服务器上运行python app.py但只能本地访问无法通过IP访问是因为Flask开发服务器默认只监听127.0.0.1。你需要像示例中那样指定host为0.0.0.0app.run(host‘0.0.0.0’, port5000)。但再次强调这只是为了测试。生产环境务必使用Gunicorn Nginx。这个项目从数据到应用涵盖了AI项目落地的多个关键环节。我个人最大的体会是构建一个可用的原型系统并不算太难有YOLOv8和Flask这样的利器帮助很大。但要让它稳定、高效、易用需要你在数据质量、模型调优、代码健壮性和用户体验等细节上反复打磨。每一个报错信息都是学习的机会每一次性能瓶颈都是优化的入口。希望这份详细的复盘能帮你绕过我当年踩过的一些坑更顺畅地完成你自己的AI应用。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进