chore: 初始化四个子项目骨架目录结构
- 新增 mingxi-capture(PyQt5桌面程序)目录骨架 - 新增 mingxi-vision(FastAPI推理服务)目录骨架 - 新增 mingxi-backend(Django业务后端)目录骨架 - 新增 mingxi-frontend(Vue3管理后台)目录骨架 - 迁入 docs/(技术设计文档和PCB调研资料) - 补充 .gitignore(模型文件、Node.js依赖、运行时数据库) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
# PCB 与 HRIPCB 数据集差异分析
|
||||
|
||||
> 分析时间:2026-05-07
|
||||
> 分析对象:`pcb/`(TCC PCB Dataset)与 `HRIPCB/`(HRIPCB Dataset)
|
||||
|
||||
---
|
||||
|
||||
## 1. 核心差异一览
|
||||
|
||||
| 维度 | PCB (TCC) | HRIPCB |
|
||||
|------|-----------|--------|
|
||||
| **来源** | Roboflow `tcc-r4j2r/pcb-la0tj` | Roboflow `ma007/hripcb` |
|
||||
| **发布时间** | 2025-12-11 | 2023-04-16 |
|
||||
| **规模** | 7,971 张 | 1,386 张 |
|
||||
| **导出预处理** | **Resize to 240×240 (Fit within)** | **Resize to 640×640 (Stretch)** |
|
||||
| **缺陷密度** | **~2.0 框/张图** | **~4.2 框/张图** |
|
||||
| **同图变体数** | 每张原始图 **3~6 个变体** | 每张原始图 **2 个变体** |
|
||||
| **License** | CC BY 4.0 | CC BY 4.0 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 关键差异详解
|
||||
|
||||
### 2.1 图像分辨率与预处理策略不同
|
||||
|
||||
这是两个数据集最本质的区别。
|
||||
|
||||
- **PCB**:被 Roboflow 预处理为 **240×240(Fit within,保持比例 + 填充)**。文件名中虽带有 `_600` 字样(暗示原始采集分辨率为 600×600),但实际导出图已缩放至 240×240。
|
||||
- **HRIPCB**:被预处理为 **640×640(Stretch,直接拉伸)**。原始图被强制拉成 640×640,可能存在轻微形变。
|
||||
|
||||
**训练影响**:YOLOv8 训练时会统一 `imgsz=640`,两者标签均为归一化坐标(0~1),可直接混用。但 PCB 从 240 放大到 640 时细节损失会比 HRIPCB 更明显,小目标特征可能更模糊。
|
||||
|
||||
### 2.2 缺陷密度不同
|
||||
|
||||
| 数据集 | Split | 图片数 | 缺陷框数 | 平均框/图 |
|
||||
|--------|-------|--------|----------|-----------|
|
||||
| PCB | train | 6,346 | 12,964 | **2.04** |
|
||||
| PCB | valid | 798 | 1,588 | **1.99** |
|
||||
| PCB | test | 827 | 1,660 | **2.01** |
|
||||
| HRIPCB | train | 1,108 | 4,667 | **4.21** |
|
||||
| HRIPCB | valid | 139 | 583 | **4.19** |
|
||||
| HRIPCB | test | 139 | 576 | **4.14** |
|
||||
|
||||
- **PCB**:平均每张图约 **2.0 个缺陷框**,整体相对稀疏。
|
||||
- **HRIPCB**:平均每张图约 **4.2 个缺陷框**,缺陷更密集,有些图可能包含同一类型的多个缺陷实例。
|
||||
|
||||
**结论**:HRIPCB 的缺陷密度约为 PCB 的 **2 倍**。这在训练时意味着 HRIPCB 样本对模型学习"密集小目标共存"场景的贡献更大。
|
||||
|
||||
### 2.3 数据构成方式不同
|
||||
|
||||
**PCB 文件名特征**:
|
||||
|
||||
```
|
||||
l_light_01_missing_hole_01_1_600_jpg.rf.xxx.jpg
|
||||
l_light_01_missing_hole_01_2_600_jpg.rf.xxx.jpg
|
||||
l_light_01_missing_hole_01_3_600_jpg.rf.xxx.jpg
|
||||
```
|
||||
|
||||
- 前缀 `l_light_` 表明采集时控制了光照条件(left light / 左侧光源)。
|
||||
- `_1_`、`_2_`、`_3_` 后缀表明**同一张缺陷样本有多个变体**(可能是不同光照、角度或采集参数)。
|
||||
- 数据冗余度更高,同类缺陷的重复样本多。
|
||||
|
||||
**HRIPCB 文件名特征**:
|
||||
|
||||
```
|
||||
01_missing_hole_01_jpg.rf.xxx.jpg
|
||||
01_missing_hole_01_jpg.rf.yyy.jpg
|
||||
```
|
||||
|
||||
- 命名更简洁,每张原始图仅有 **2 个 Roboflow 自动变体**(后缀 hash 不同)。
|
||||
- 样本独立性更强,重复度低。
|
||||
|
||||
### 2.4 总标注实例数
|
||||
|
||||
| 数据集 | 图片数 | 总缺陷实例数 |
|
||||
|--------|--------|-------------|
|
||||
| PCB | 7,971 | ~16,212 |
|
||||
| HRIPCB | 1,386 | ~4,826 |
|
||||
| **合并后** | **9,357** | **~21,038** |
|
||||
|
||||
---
|
||||
|
||||
## 3. 对模型训练的影响
|
||||
|
||||
| 影响项 | 说明 |
|
||||
|--------|------|
|
||||
| **分辨率差距** | PCB 从 240 上采样到 640 会丢失细节,小目标检测可能受一定影响;HRIPCB 原生 640,细节保留更完整 |
|
||||
| **密度差异** | HRIPCB 更密集的标注有助于模型学习"多缺陷共存"场景,对实际产线更有价值 |
|
||||
| **重复样本** | PCB 中同一缺陷的多个变体相当于轻度数据增强,有助于提升光照/角度鲁棒性 |
|
||||
| **来源互补** | PCB 偏"受控光照实验"风格,HRIPCB 偏"标准工业样本"风格,合并后泛化能力更强 |
|
||||
| **类别分布** | 两者类别定义完全一致(6 类缺陷),标签格式均为标准 YOLOv8 归一化坐标,可直接合并 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 使用建议
|
||||
|
||||
### 4.1 基础用法
|
||||
|
||||
直接使用 `dataset.py` 合并后训练:
|
||||
|
||||
```bash
|
||||
python dataset.py merge --src pcb HRIPCB --dst merged
|
||||
```
|
||||
|
||||
合并后共 **9,357 张图、约 21,038 个缺陷实例**,数据量足够支撑 YOLOv8n/s 的训练。
|
||||
|
||||
### 4.2 进阶用法:重新划分
|
||||
|
||||
如果担心 PCB 占比过高(85%)导致模型过度偏向 PCB 的数据风格,可以打乱后重新按 8:1:1 划分:
|
||||
|
||||
```bash
|
||||
python dataset.py merge --src pcb HRIPCB --dst merged --rebalance --train 0.8 --val 0.1 --test 0.1
|
||||
```
|
||||
|
||||
### 4.3 训练参数建议
|
||||
|
||||
针对两个数据集的分辨率差异,建议:
|
||||
|
||||
```yaml
|
||||
# train.yaml
|
||||
model: yolov8n.pt
|
||||
data: D:/github2026/pcb-defect-detection/merged/data.yaml
|
||||
imgsz: 640 # 统一统一到 640,补偿 PCB 的 240 上采样损失
|
||||
batch: 16 # RTX 4060 8GB 安全值
|
||||
epochs: 100
|
||||
amp: True # 混合精度必开
|
||||
patience: 20 # 早停
|
||||
```
|
||||
|
||||
### 4.4 如果精度不够
|
||||
|
||||
可考虑以下策略:
|
||||
|
||||
1. **分阶段训练**:先用 PCB 预训练,再用 HRIPCB fine-tune(HRIPCB 密度高、标注更"干净")。
|
||||
2. **调整样本权重**:在 Ultralytics 中通过 `sample_weights` 或自定义 dataloader 提升 HRIPCB 样本的采样概率。
|
||||
3. **数据清洗**:检查 PCB 中 240×240 上采样后是否出现模糊/不可辨认的样本,必要时剔除低质量数据。
|
||||
|
||||
---
|
||||
|
||||
## 5. 附:数据来源链接
|
||||
|
||||
- **PCB (TCC)**:`https://universe.roboflow.com/tcc-r4j2r/pcb-la0tj/dataset/1`
|
||||
- **HRIPCB**:`https://universe.roboflow.com/ma007/hripcb/dataset/1`
|
||||
@@ -0,0 +1,826 @@
|
||||
# mingxi-vision 技术架构与设计分析
|
||||
|
||||
> 版本:v1.0 · 日期:2026-05-24
|
||||
> 硬件约束:联想 Y7000P · RTX 4060 Laptop 8GB · Python 3.8 · Windows 11
|
||||
> 上游依赖:`yolo_classification_system/yolo8/inference.py`(迁移基础)
|
||||
|
||||
---
|
||||
|
||||
## 1. 定位与职责边界
|
||||
|
||||
`mingxi-vision` 是整个明析平台的**推理微服务**,职责单一:
|
||||
|
||||
```
|
||||
接收图像 → 前处理 → 模型推理 → 后处理 → 返回结构化缺陷JSON
|
||||
```
|
||||
|
||||
它**不**做的事:
|
||||
- 不持久化数据(无数据库依赖)
|
||||
- 不做鉴权(内网服务,由 backend 代理)
|
||||
- 不管理产线/批次业务(backend 的职责)
|
||||
- 不直接控制相机(capture 的职责)
|
||||
|
||||
---
|
||||
|
||||
## 2. 框架选型:FastAPI
|
||||
|
||||
### 为什么不用 Django
|
||||
|
||||
现有系统用 Django,但推理服务有不同的特征:
|
||||
|
||||
| 维度 | Django(现有) | FastAPI(选用) |
|
||||
|------|--------------|----------------|
|
||||
| 定位 | 全功能Web框架 | 轻量API框架 |
|
||||
| 启动时间 | ~3-5s | ~0.5s |
|
||||
| ORM/DB | 必须配置 | 无需数据库 |
|
||||
| 异步支持 | 有限(Django 4+ 部分支持) | 原生 async/await |
|
||||
| 自动文档 | 需要额外配置 | 内置 /docs (Swagger) |
|
||||
| 推理场景适配 | 过重 | 刚好合适 |
|
||||
|
||||
### Python 3.8 的约束
|
||||
|
||||
FastAPI 在 Python 3.8 下完全可用,需注意:
|
||||
- 类型注解用 `Optional[X]` 而非 `X | None`(3.10+ 语法)
|
||||
- `from __future__ import annotations` 可缓解部分问题
|
||||
- Pydantic v1(随 FastAPI 早期版本)在 3.8 稳定
|
||||
|
||||
```
|
||||
# requirements.txt 核心版本锁定
|
||||
fastapi==0.104.1 # 3.8 兼容的最后稳定版系列
|
||||
uvicorn[standard]==0.24.0
|
||||
pydantic==1.10.13 # v1,Python 3.8 最稳定
|
||||
ultralytics==8.0.235 # 训练阶段用
|
||||
onnxruntime-gpu==1.16.3 # 推理部署阶段用
|
||||
opencv-python-headless==4.8.1.78
|
||||
numpy==1.24.4 # 3.8 + torch 2.0 兼容版本
|
||||
python-multipart==0.0.6 # FastAPI 文件上传必须
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 双运行时策略
|
||||
|
||||
这是 mingxi-vision 最核心的设计决策。
|
||||
|
||||
### 问题
|
||||
|
||||
- **训练阶段**:用 `ultralytics` 的 `.pt` 格式,便于迭代和验证
|
||||
- **部署阶段**:用 `.onnx` 格式,跨平台、无需安装 PyTorch、性能更稳定
|
||||
- 两套格式的推理 API 有差异,需要统一抽象
|
||||
|
||||
### 解决方案:运行时适配器(Adapter 模式)
|
||||
|
||||
```
|
||||
┌─────────────────────────────┐
|
||||
│ InferenceEngine │ ← 统一接口
|
||||
│ detect(image) → [Defect] │
|
||||
└──────────┬──────────────────┘
|
||||
│ 根据配置选择
|
||||
┌───────────────┴────────────────┐
|
||||
▼ ▼
|
||||
UltralyticsAdapter OnnxRuntimeAdapter
|
||||
(.pt 文件,训练验证用) (.onnx 文件,生产部署用)
|
||||
ultralytics.YOLO onnxruntime.InferenceSession
|
||||
```
|
||||
|
||||
### 代码设计
|
||||
|
||||
```python
|
||||
# engine/base.py
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
from typing import List
|
||||
import numpy as np
|
||||
|
||||
@dataclass
|
||||
class DefectBox:
|
||||
class_id: int
|
||||
class_name: str
|
||||
class_name_zh: str
|
||||
confidence: float
|
||||
severity: str # fatal / major / minor / rework / none
|
||||
box_xyxy: List[float] # [x1, y1, x2, y2],像素坐标
|
||||
|
||||
|
||||
@dataclass
|
||||
class DetectResult:
|
||||
defects: List[DefectBox]
|
||||
duration_ms: float
|
||||
image_width: int
|
||||
image_height: int
|
||||
model_version: str
|
||||
|
||||
@property
|
||||
def defect_count(self) -> int:
|
||||
return len(self.defects)
|
||||
|
||||
@property
|
||||
def max_severity(self) -> str:
|
||||
order = ["fatal", "major", "minor", "rework", "none"]
|
||||
found = {d.severity for d in self.defects}
|
||||
for s in order:
|
||||
if s in found:
|
||||
return s
|
||||
return "none"
|
||||
|
||||
@property
|
||||
def avg_confidence(self):
|
||||
if not self.defects:
|
||||
return None
|
||||
return round(sum(d.confidence for d in self.defects) / len(self.defects), 4)
|
||||
|
||||
|
||||
class BaseInferenceEngine(ABC):
|
||||
@abstractmethod
|
||||
def detect(self, image: np.ndarray, conf: float) -> DetectResult:
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def warmup(self) -> None:
|
||||
"""启动时预热,避免第一次推理延迟"""
|
||||
...
|
||||
```
|
||||
|
||||
```python
|
||||
# engine/ultralytics_adapter.py
|
||||
import time
|
||||
import numpy as np
|
||||
from .base import BaseInferenceEngine, DefectBox, DetectResult
|
||||
from .defect_classes import DEFECT_CLASSES
|
||||
|
||||
class UltralyticsAdapter(BaseInferenceEngine):
|
||||
"""开发/训练验证阶段使用"""
|
||||
|
||||
def __init__(self, weights_path: str):
|
||||
from ultralytics import YOLO
|
||||
self._model = YOLO(weights_path)
|
||||
self._weights_path = weights_path
|
||||
|
||||
def warmup(self):
|
||||
dummy = np.zeros((640, 640, 3), dtype=np.uint8)
|
||||
self._model.predict(source=dummy, verbose=False, conf=0.01)
|
||||
|
||||
def detect(self, image: np.ndarray, conf: float = 0.45) -> DetectResult:
|
||||
h, w = image.shape[:2]
|
||||
t0 = time.perf_counter()
|
||||
results = self._model.predict(source=image, verbose=False, conf=conf)
|
||||
duration_ms = (time.perf_counter() - t0) * 1000
|
||||
|
||||
defects = []
|
||||
result = results[0]
|
||||
if result.boxes is not None and len(result.boxes):
|
||||
xyxy = result.boxes.xyxy.cpu().numpy()
|
||||
confs = result.boxes.conf.cpu().numpy()
|
||||
clsids = result.boxes.cls.cpu().numpy()
|
||||
for i in range(len(xyxy)):
|
||||
cid = int(clsids[i])
|
||||
meta = DEFECT_CLASSES.get(cid, {
|
||||
"name": str(cid), "zh": str(cid), "severity": "minor"
|
||||
})
|
||||
defects.append(DefectBox(
|
||||
class_id=cid,
|
||||
class_name=meta["name"],
|
||||
class_name_zh=meta["zh"],
|
||||
confidence=round(float(confs[i]), 4),
|
||||
severity=meta["severity"],
|
||||
box_xyxy=[round(float(x), 1) for x in xyxy[i]],
|
||||
))
|
||||
|
||||
return DetectResult(
|
||||
defects=defects,
|
||||
duration_ms=round(duration_ms, 1),
|
||||
image_width=w,
|
||||
image_height=h,
|
||||
model_version=str(self._weights_path),
|
||||
)
|
||||
```
|
||||
|
||||
```python
|
||||
# engine/onnx_adapter.py
|
||||
import time
|
||||
import cv2
|
||||
import numpy as np
|
||||
import onnxruntime as ort
|
||||
from .base import BaseInferenceEngine, DefectBox, DetectResult
|
||||
from .defect_classes import DEFECT_CLASSES
|
||||
|
||||
class OnnxRuntimeAdapter(BaseInferenceEngine):
|
||||
"""生产部署阶段使用,无 PyTorch 依赖"""
|
||||
|
||||
def __init__(self, model_path: str, device: str = "cuda"):
|
||||
providers = (
|
||||
["CUDAExecutionProvider", "CPUExecutionProvider"]
|
||||
if device == "cuda"
|
||||
else ["CPUExecutionProvider"]
|
||||
)
|
||||
self._session = ort.InferenceSession(model_path, providers=providers)
|
||||
self._input_name = self._session.get_inputs()[0].name
|
||||
self._input_shape = self._session.get_inputs()[0].shape # [1,3,640,640]
|
||||
self._imgsz = self._input_shape[2] # 通常 640
|
||||
self._model_path = model_path
|
||||
|
||||
def warmup(self):
|
||||
dummy = np.zeros(
|
||||
(1, 3, self._imgsz, self._imgsz), dtype=np.float32
|
||||
)
|
||||
self._session.run(None, {self._input_name: dummy})
|
||||
|
||||
def _preprocess(self, image: np.ndarray):
|
||||
"""BGR → RGB → letterbox → NCHW float32 [0,1]"""
|
||||
img = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
|
||||
img, ratio, (dw, dh) = self._letterbox(img, self._imgsz)
|
||||
img = img.astype(np.float32) / 255.0
|
||||
img = np.transpose(img, (2, 0, 1)) # HWC → CHW
|
||||
img = np.expand_dims(img, 0) # CHW → NCHW
|
||||
return img, ratio, dw, dh
|
||||
|
||||
@staticmethod
|
||||
def _letterbox(img, new_size=640):
|
||||
h, w = img.shape[:2]
|
||||
ratio = min(new_size / h, new_size / w)
|
||||
nh, nw = int(h * ratio), int(w * ratio)
|
||||
img = cv2.resize(img, (nw, nh), interpolation=cv2.INTER_LINEAR)
|
||||
dw = (new_size - nw) / 2
|
||||
dh = (new_size - nh) / 2
|
||||
top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
|
||||
left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
|
||||
img = cv2.copyMakeBorder(img, top, bottom, left, right,
|
||||
cv2.BORDER_CONSTANT, value=(114, 114, 114))
|
||||
return img, ratio, dw, dh
|
||||
|
||||
def _postprocess(self, outputs, orig_h, orig_w, ratio, dw, dh, conf_thres):
|
||||
"""YOLOv8 ONNX 输出解码:[1, 84, 8400] → DefectBox列表"""
|
||||
pred = outputs[0][0] # [84, 8400]
|
||||
pred = pred.T # [8400, 84]
|
||||
boxes = pred[:, :4] # cx,cy,w,h
|
||||
scores = pred[:, 4:] # [8400, num_classes]
|
||||
|
||||
class_ids = np.argmax(scores, axis=1)
|
||||
confidences = scores[np.arange(len(scores)), class_ids]
|
||||
|
||||
mask = confidences > conf_thres
|
||||
boxes, class_ids, confidences = (
|
||||
boxes[mask], class_ids[mask], confidences[mask]
|
||||
)
|
||||
if len(boxes) == 0:
|
||||
return []
|
||||
|
||||
# cx,cy,w,h → x1,y1,x2,y2
|
||||
x1 = boxes[:, 0] - boxes[:, 2] / 2
|
||||
y1 = boxes[:, 1] - boxes[:, 3] / 2
|
||||
x2 = boxes[:, 0] + boxes[:, 2] / 2
|
||||
y2 = boxes[:, 1] + boxes[:, 3] / 2
|
||||
|
||||
# 去除 letterbox padding,还原到原始坐标
|
||||
x1 = np.clip((x1 - dw) / ratio, 0, orig_w)
|
||||
y1 = np.clip((y1 - dh) / ratio, 0, orig_h)
|
||||
x2 = np.clip((x2 - dw) / ratio, 0, orig_w)
|
||||
y2 = np.clip((y2 - dh) / ratio, 0, orig_h)
|
||||
|
||||
# NMS
|
||||
nms_ids = cv2.dnn.NMSBoxes(
|
||||
np.stack([x1, y1, x2 - x1, y2 - y1], axis=1).tolist(),
|
||||
confidences.tolist(), conf_thres, iou_threshold=0.45
|
||||
)
|
||||
if len(nms_ids) == 0:
|
||||
return []
|
||||
|
||||
defects = []
|
||||
for idx in nms_ids.flatten():
|
||||
cid = int(class_ids[idx])
|
||||
meta = DEFECT_CLASSES.get(cid, {
|
||||
"name": str(cid), "zh": str(cid), "severity": "minor"
|
||||
})
|
||||
defects.append(DefectBox(
|
||||
class_id=cid,
|
||||
class_name=meta["name"],
|
||||
class_name_zh=meta["zh"],
|
||||
confidence=round(float(confidences[idx]), 4),
|
||||
severity=meta["severity"],
|
||||
box_xyxy=[
|
||||
round(float(x1[idx]), 1), round(float(y1[idx]), 1),
|
||||
round(float(x2[idx]), 1), round(float(y2[idx]), 1),
|
||||
],
|
||||
))
|
||||
return defects
|
||||
|
||||
def detect(self, image: np.ndarray, conf: float = 0.45) -> DetectResult:
|
||||
orig_h, orig_w = image.shape[:2]
|
||||
inp, ratio, dw, dh = self._preprocess(image)
|
||||
|
||||
t0 = time.perf_counter()
|
||||
outputs = self._session.run(None, {self._input_name: inp})
|
||||
duration_ms = (time.perf_counter() - t0) * 1000
|
||||
|
||||
defects = self._postprocess(outputs, orig_h, orig_w, ratio, dw, dh, conf)
|
||||
|
||||
return DetectResult(
|
||||
defects=defects,
|
||||
duration_ms=round(duration_ms, 1),
|
||||
image_width=orig_w,
|
||||
image_height=orig_h,
|
||||
model_version=str(self._model_path),
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 模型管理:单例 + 启动加载
|
||||
|
||||
### 问题根源
|
||||
|
||||
现有 `inference.py` 的 `_MODEL_CACHE` 是进程级字典,首次请求时才加载模型(懒加载)。对推理服务来说,这会导致第一个请求有数秒延迟,在演示现场是灾难性的。
|
||||
|
||||
### 解决方案:FastAPI 生命周期钩子
|
||||
|
||||
```python
|
||||
# engine/loader.py
|
||||
from typing import Optional
|
||||
from .base import BaseInferenceEngine
|
||||
from config import settings
|
||||
|
||||
_engine: Optional[BaseInferenceEngine] = None
|
||||
|
||||
|
||||
def get_engine() -> BaseInferenceEngine:
|
||||
if _engine is None:
|
||||
raise RuntimeError("推理引擎未初始化,请检查启动日志")
|
||||
return _engine
|
||||
|
||||
|
||||
def init_engine() -> BaseInferenceEngine:
|
||||
global _engine
|
||||
if settings.runtime == "onnx":
|
||||
from .onnx_adapter import OnnxRuntimeAdapter
|
||||
_engine = OnnxRuntimeAdapter(
|
||||
model_path=settings.model_path,
|
||||
device=settings.device,
|
||||
)
|
||||
else:
|
||||
from .ultralytics_adapter import UltralyticsAdapter
|
||||
_engine = UltralyticsAdapter(weights_path=settings.model_path)
|
||||
|
||||
_engine.warmup() # 预热,消除第一次推理的延迟
|
||||
return _engine
|
||||
```
|
||||
|
||||
```python
|
||||
# main.py
|
||||
from contextlib import asynccontextmanager
|
||||
from fastapi import FastAPI
|
||||
from engine.loader import init_engine
|
||||
from api.detect import router as detect_router
|
||||
from api.health import router as health_router
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
# 启动时加载模型
|
||||
engine = init_engine()
|
||||
print(f"[mingxi-vision] 引擎就绪: {engine.__class__.__name__}")
|
||||
yield
|
||||
# 关闭时释放资源(ONNX session会自动GC)
|
||||
|
||||
|
||||
app = FastAPI(
|
||||
title="明析推理服务",
|
||||
description="PCB缺陷检测推理接口",
|
||||
version="1.0.0",
|
||||
lifespan=lifespan,
|
||||
)
|
||||
|
||||
app.include_router(detect_router, prefix="/api")
|
||||
app.include_router(health_router, prefix="/api")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 图像接收与前处理
|
||||
|
||||
### 接收来源
|
||||
|
||||
mingxi-vision 接受两种图像来源,用同一个端点处理:
|
||||
|
||||
```python
|
||||
# api/detect.py
|
||||
import cv2
|
||||
import numpy as np
|
||||
from fastapi import APIRouter, File, Form, UploadFile, HTTPException
|
||||
from fastapi.responses import JSONResponse
|
||||
from engine.loader import get_engine
|
||||
from engine.annotator import draw_boxes
|
||||
from schema import DetectResponse
|
||||
import base64, uuid
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/detect", response_model=DetectResponse)
|
||||
async def detect(
|
||||
image: UploadFile = File(...),
|
||||
conf: float = Form(default=0.45, ge=0.01, le=0.99),
|
||||
line_id: str = Form(default=""),
|
||||
batch_id: str = Form(default=""),
|
||||
return_annotated: bool = Form(default=False),
|
||||
):
|
||||
# 1. 读取图像字节
|
||||
raw = await image.read()
|
||||
if len(raw) > 20 * 1024 * 1024: # 20MB 上限
|
||||
raise HTTPException(status_code=413, detail="图像文件过大(上限20MB)")
|
||||
|
||||
# 2. 解码为 numpy BGR
|
||||
arr = np.frombuffer(raw, dtype=np.uint8)
|
||||
img = cv2.imdecode(arr, cv2.IMREAD_COLOR)
|
||||
if img is None:
|
||||
raise HTTPException(status_code=422, detail="无法解码图像,请检查文件格式")
|
||||
|
||||
# 3. 推理
|
||||
engine = get_engine()
|
||||
result = engine.detect(img, conf=conf)
|
||||
|
||||
# 4. 可选:返回标注图
|
||||
annotated_b64 = None
|
||||
if return_annotated:
|
||||
annotated = draw_boxes(img.copy(), result.defects)
|
||||
_, buf = cv2.imencode(".jpg", annotated, [cv2.IMWRITE_JPEG_QUALITY, 85])
|
||||
annotated_b64 = base64.b64encode(buf.tobytes()).decode()
|
||||
|
||||
return DetectResponse(
|
||||
task_id=str(uuid.uuid4()),
|
||||
line_id=line_id,
|
||||
batch_id=batch_id,
|
||||
duration_ms=result.duration_ms,
|
||||
image_width=result.image_width,
|
||||
image_height=result.image_height,
|
||||
defect_count=result.defect_count,
|
||||
max_severity=result.max_severity,
|
||||
avg_confidence=result.avg_confidence,
|
||||
defects=[d.__dict__ for d in result.defects],
|
||||
annotated_image_b64=annotated_b64,
|
||||
model_version=result.model_version,
|
||||
)
|
||||
```
|
||||
|
||||
### 中文路径问题(继承自现有代码)
|
||||
|
||||
现有 `inference.py` 已有 `_copy_to_ascii_temp_input()` 解决中文路径导致 OpenCV 无法读图的问题。`mingxi-vision` 改用 **内存读取**(`np.frombuffer` + `cv2.imdecode`),从源头消除路径问题,不再需要这个 workaround。
|
||||
|
||||
---
|
||||
|
||||
## 6. 后处理:标注图生成
|
||||
|
||||
```python
|
||||
# engine/annotator.py
|
||||
import cv2
|
||||
import numpy as np
|
||||
from typing import List
|
||||
from .base import DefectBox
|
||||
|
||||
SEVERITY_COLORS = {
|
||||
"fatal": (0, 0, 220), # 红(BGR)
|
||||
"major": (0, 128, 255), # 橙
|
||||
"minor": (0, 215, 255), # 黄
|
||||
"rework": (255, 165, 0 ), # 蓝
|
||||
"none": (180, 180, 180), # 灰
|
||||
}
|
||||
|
||||
|
||||
def draw_boxes(image: np.ndarray, defects: List[DefectBox]) -> np.ndarray:
|
||||
for d in defects:
|
||||
x1, y1, x2, y2 = [int(v) for v in d.box_xyxy]
|
||||
color = SEVERITY_COLORS.get(d.severity, (180, 180, 180))
|
||||
cv2.rectangle(image, (x1, y1), (x2, y2), color, 2)
|
||||
|
||||
label = f"{d.class_name_zh} {d.confidence:.2f}"
|
||||
(tw, th), _ = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.55, 1)
|
||||
cv2.rectangle(image, (x1, y1 - th - 6), (x1 + tw + 4, y1), color, -1)
|
||||
cv2.putText(
|
||||
image, label, (x1 + 2, y1 - 4),
|
||||
cv2.FONT_HERSHEY_SIMPLEX, 0.55, (255, 255, 255), 1, cv2.LINE_AA
|
||||
)
|
||||
return image
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 缺陷类别定义
|
||||
|
||||
```python
|
||||
# engine/defect_classes.py
|
||||
# 与 HRIPCB 数据集的 6 类对齐,扩展至 10 类(含真实产线样本迁移学习后)
|
||||
|
||||
DEFECT_CLASSES = {
|
||||
# ── HRIPCB 基础 6 类(公开数据集,开箱即用)──
|
||||
0: {"name": "missing_hole", "zh": "缺孔", "severity": "fatal"},
|
||||
1: {"name": "mouse_bite", "zh": "鼠咬", "severity": "major"},
|
||||
2: {"name": "open_circuit", "zh": "断路", "severity": "fatal"},
|
||||
3: {"name": "short_circuit", "zh": "短路", "severity": "fatal"},
|
||||
4: {"name": "spur", "zh": "毛刺", "severity": "minor"},
|
||||
5: {"name": "spurious_copper", "zh": "余铜", "severity": "major"},
|
||||
# ── 扩展类(真实产线样本微调后启用)──
|
||||
6: {"name": "oxidation", "zh": "氧化", "severity": "minor"},
|
||||
7: {"name": "solder_ball", "zh": "锡珠", "severity": "rework"},
|
||||
8: {"name": "scratch", "zh": "划痕", "severity": "minor"},
|
||||
9: {"name": "label_error", "zh": "标签错贴","severity": "rework"},
|
||||
}
|
||||
|
||||
SEVERITY_ORDER = ["fatal", "major", "minor", "rework", "none"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 配置管理
|
||||
|
||||
```python
|
||||
# config.py
|
||||
import os
|
||||
from typing import Literal
|
||||
from pydantic import BaseSettings # pydantic v1
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
# 运行时选择
|
||||
runtime: Literal["ultralytics", "onnx"] = "ultralytics"
|
||||
model_path: str = "./models/pcb_defect_v1.pt"
|
||||
device: Literal["cuda", "cpu"] = "cuda"
|
||||
|
||||
# 推理默认参数
|
||||
default_conf: float = 0.45
|
||||
max_image_size_mb: int = 20
|
||||
|
||||
# 服务配置
|
||||
host: str = "0.0.0.0"
|
||||
port: int = 8001
|
||||
workers: int = 1 # 推理服务单 worker,GPU 不支持多进程共享
|
||||
|
||||
class Config:
|
||||
env_file = ".env"
|
||||
env_file_encoding = "utf-8"
|
||||
|
||||
|
||||
settings = Settings()
|
||||
```
|
||||
|
||||
`.env` 文件示例:
|
||||
|
||||
```bash
|
||||
# 开发阶段(ultralytics .pt)
|
||||
RUNTIME=ultralytics
|
||||
MODEL_PATH=./models/pcb_defect_v1.pt
|
||||
DEVICE=cuda
|
||||
|
||||
# 生产阶段(ONNX Runtime)
|
||||
# RUNTIME=onnx
|
||||
# MODEL_PATH=./models/pcb_defect_v1.onnx
|
||||
# DEVICE=cuda
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 健康检查与诊断接口
|
||||
|
||||
```python
|
||||
# api/health.py
|
||||
import platform
|
||||
from fastapi import APIRouter
|
||||
from engine.loader import get_engine
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/health")
|
||||
def health():
|
||||
try:
|
||||
engine = get_engine()
|
||||
return {
|
||||
"status": "ok",
|
||||
"runtime": engine.__class__.__name__,
|
||||
"platform": platform.system(),
|
||||
}
|
||||
except RuntimeError as e:
|
||||
return {"status": "error", "detail": str(e)}
|
||||
|
||||
|
||||
@router.get("/health/gpu")
|
||||
def gpu_info():
|
||||
"""开发调试用,确认 GPU 是否被正确使用"""
|
||||
info = {"cuda_available": False, "onnx_providers": []}
|
||||
try:
|
||||
import torch
|
||||
info["cuda_available"] = torch.cuda.is_available()
|
||||
if torch.cuda.is_available():
|
||||
info["gpu_name"] = torch.cuda.get_device_name(0)
|
||||
info["vram_total_gb"] = round(
|
||||
torch.cuda.get_device_properties(0).total_memory / 1e9, 1
|
||||
)
|
||||
except ImportError:
|
||||
pass
|
||||
try:
|
||||
import onnxruntime as ort
|
||||
info["onnx_providers"] = ort.get_available_providers()
|
||||
except ImportError:
|
||||
pass
|
||||
return info
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. 完整目录结构
|
||||
|
||||
```
|
||||
mingxi-vision/
|
||||
├── api/
|
||||
│ ├── __init__.py
|
||||
│ ├── detect.py # POST /api/detect(核心推理接口)
|
||||
│ └── health.py # GET /api/health, /api/health/gpu
|
||||
├── engine/
|
||||
│ ├── __init__.py
|
||||
│ ├── base.py # DefectBox, DetectResult, BaseInferenceEngine
|
||||
│ ├── loader.py # 单例管理 + init_engine()
|
||||
│ ├── ultralytics_adapter.py # .pt 推理(开发阶段)
|
||||
│ ├── onnx_adapter.py # .onnx 推理(生产阶段)
|
||||
│ ├── annotator.py # 在图像上绘制标注框
|
||||
│ └── defect_classes.py # 缺陷类别 + 等级映射表
|
||||
├── models/
|
||||
│ ├── pcb_defect_v1.pt # ultralytics 训练产物(gitignore)
|
||||
│ └── pcb_defect_v1.onnx # 导出的 ONNX 模型(gitignore)
|
||||
├── scripts/
|
||||
│ ├── export_onnx.py # pt → onnx 导出脚本
|
||||
│ └── benchmark.py # 本地推理性能测试
|
||||
├── tests/
|
||||
│ ├── test_detect_api.py
|
||||
│ └── fixtures/ # 测试用 PCB 图片
|
||||
├── schema.py # Pydantic 响应模型
|
||||
├── config.py # Settings(pydantic BaseSettings)
|
||||
├── main.py # FastAPI app + lifespan
|
||||
├── requirements.txt
|
||||
├── .env.example
|
||||
└── README.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. 响应 Schema
|
||||
|
||||
```python
|
||||
# schema.py
|
||||
from typing import List, Optional
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class DefectItem(BaseModel):
|
||||
class_id: int
|
||||
class_name: str
|
||||
class_name_zh: str
|
||||
confidence: float
|
||||
severity: str # fatal / major / minor / rework / none
|
||||
box_xyxy: List[float] # [x1, y1, x2, y2]
|
||||
|
||||
|
||||
class DetectResponse(BaseModel):
|
||||
task_id: str
|
||||
line_id: str
|
||||
batch_id: str
|
||||
duration_ms: float
|
||||
image_width: int
|
||||
image_height: int
|
||||
defect_count: int
|
||||
max_severity: str # 本次检测中最高等级缺陷
|
||||
avg_confidence: Optional[float]
|
||||
defects: List[DefectItem]
|
||||
annotated_image_b64: Optional[str] # 仅 return_annotated=true 时有值
|
||||
model_version: str
|
||||
```
|
||||
|
||||
实际响应示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"task_id": "a3f1c2d4-...",
|
||||
"line_id": "LINE-01",
|
||||
"batch_id": "BAT-20260524-001",
|
||||
"duration_ms": 41.3,
|
||||
"image_width": 3072,
|
||||
"image_height": 2048,
|
||||
"defect_count": 2,
|
||||
"max_severity": "fatal",
|
||||
"avg_confidence": 0.8762,
|
||||
"defects": [
|
||||
{
|
||||
"class_id": 2,
|
||||
"class_name": "open_circuit",
|
||||
"class_name_zh": "断路",
|
||||
"confidence": 0.9134,
|
||||
"severity": "fatal",
|
||||
"box_xyxy": [234.5, 891.2, 312.8, 943.7]
|
||||
},
|
||||
{
|
||||
"class_id": 4,
|
||||
"class_name": "spur",
|
||||
"class_name_zh": "毛刺",
|
||||
"confidence": 0.8390,
|
||||
"severity": "minor",
|
||||
"box_xyxy": [1204.1, 456.3, 1251.9, 489.0]
|
||||
}
|
||||
],
|
||||
"annotated_image_b64": null,
|
||||
"model_version": "./models/pcb_defect_v1.pt"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. 性能分析(Y7000P RTX 4060)
|
||||
|
||||
### 推理延迟预估
|
||||
|
||||
| 运行时 | 模型 | 分辨率 | 单次推理 | 含预处理+后处理 |
|
||||
|--------|------|--------|---------|--------------|
|
||||
| ultralytics | YOLOv8n | 640×640 | ~12ms | ~20ms |
|
||||
| ultralytics | YOLOv8s | 640×640 | ~20ms | ~30ms |
|
||||
| ONNX Runtime GPU | YOLOv8n | 640×640 | ~8ms | ~15ms |
|
||||
| ONNX Runtime CPU | YOLOv8n | 640×640 | ~80ms | ~100ms |
|
||||
|
||||
> 数据来源:`pcb缺陷检测初步方案.md` 引用的 Y7000P 实测基准
|
||||
|
||||
**PCB 后道复判场景需求**:触发模式,每块板约 1-3 秒间隔,15ms 的推理延迟完全满足。
|
||||
|
||||
### 显存占用
|
||||
|
||||
| 模型 | 显存占用 |
|
||||
|------|---------|
|
||||
| YOLOv8n(ultralytics) | ~1.2GB |
|
||||
| YOLOv8s(ultralytics) | ~2.4GB |
|
||||
| YOLOv8n(ONNX CUDA EP) | ~0.8GB |
|
||||
|
||||
RTX 4060 8GB 剩余 6GB+ 可用,不构成瓶颈。
|
||||
|
||||
### 并发限制
|
||||
|
||||
```python
|
||||
# main.py 启动参数
|
||||
# workers=1:GPU 不支持多进程共享同一 CUDA Context
|
||||
# 推理天然串行,配合 FastAPI 的 async 处理并发等待队列
|
||||
uvicorn main:app --host 0.0.0.0 --port 8001 --workers 1
|
||||
```
|
||||
|
||||
PCB 产线触发模式下,单工位请求天然串行,`workers=1` 完全够用。若需多产线并发,在 `mingxi-backend` 侧做请求队列即可。
|
||||
|
||||
---
|
||||
|
||||
## 13. 模型导出(pt → onnx)
|
||||
|
||||
```python
|
||||
# scripts/export_onnx.py
|
||||
from ultralytics import YOLO
|
||||
|
||||
model = YOLO("./models/pcb_defect_v1.pt")
|
||||
model.export(
|
||||
format="onnx",
|
||||
imgsz=640,
|
||||
opset=12, # onnxruntime 1.16 兼容
|
||||
simplify=True, # onnx-simplifier 优化计算图
|
||||
dynamic=False, # 固定 batch=1,推理服务不需要动态 batch
|
||||
)
|
||||
# 产物:pcb_defect_v1.onnx
|
||||
```
|
||||
|
||||
切换到 ONNX 的时机:
|
||||
|
||||
```
|
||||
1. YOLOv8n 在验证集 mAP@0.5 > 0.90 → 导出 ONNX
|
||||
2. 运行 scripts/benchmark.py 对比两个运行时延迟
|
||||
3. 确认输出一致后,修改 .env 的 RUNTIME=onnx
|
||||
4. 重启服务,观察 /api/health 确认切换成功
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 14. 与现有代码的对应关系
|
||||
|
||||
| 现有 `inference.py` 函数 | mingxi-vision 对应位置 | 变化 |
|
||||
|------------------------|----------------------|------|
|
||||
| `_MODEL_CACHE` | `engine/loader.py` 单例 | 从懒加载改为启动时加载 |
|
||||
| `_get_model()` | `loader.init_engine()` | 新增 ONNX 分支 |
|
||||
| `_copy_to_ascii_temp_input()` | **已删除** | 改用内存读图,无需文件路径 |
|
||||
| `_parse_result()` | `ultralytics_adapter.detect()` | 增加 severity 映射 |
|
||||
| `run_image_inference()` | `engine/ultralytics_adapter.py` + `engine/onnx_adapter.py` | 拆成两个运行时 |
|
||||
| `run_camera_inference()` | **不迁移** | 由 `mingxi-capture` 负责 |
|
||||
| `run_video_inference()` | **暂不迁移** | 此版本不需要 |
|
||||
| Django `MEDIA_ROOT` 文件落盘 | **已删除** | 推理服务无状态,不落盘 |
|
||||
|
||||
---
|
||||
|
||||
## 15. 关键设计决策汇总
|
||||
|
||||
| 决策 | 选择 | 理由 |
|
||||
|------|------|------|
|
||||
| Web 框架 | FastAPI | 轻量、无需 DB、原生 async、自动文档 |
|
||||
| 模型运行时 | 双运行时(适配器模式) | 开发用 .pt 方便迭代,生产用 .onnx 稳定 |
|
||||
| 模型加载时机 | 启动时加载 + warmup | 消除首次推理延迟,路演不卡顿 |
|
||||
| 图像读取 | 内存读取(np.frombuffer) | 彻底消除中文路径问题 |
|
||||
| 标注图返回 | 按需(return_annotated=true) | 默认不返回,减少响应体积和编码开销 |
|
||||
| 并发模型 | workers=1,单进程 | GPU CUDA Context 不支持多进程共享 |
|
||||
| 鉴权 | 无 | 内网服务,由 backend 代理调用 |
|
||||
| 数据持久化 | 无 | 无状态推理服务,落库交给 backend |
|
||||
| NMS 后处理 | ONNX 适配器自实现,ultralytics 自带 | 两路输出语义一致 |
|
||||
@@ -0,0 +1,218 @@
|
||||
# PCB缺陷检测初步方案(单机开发版)
|
||||
|
||||
> 硬件约束修正:仅有一台联想 Y7000P 笔记本(Intel i7-14650HX 16核 @2.20GHz,16GB RAM,预计 RTX 4060 Laptop 8GB 独显)。
|
||||
> 本方案在 v1.1 和"pcb缺陷识别开源项目.md"基础上,针对"无 Jetson、无独立服务器、仅一台笔记本"做完整重构。
|
||||
> 对齐 CLAUDE.md:Python3.8 + YOLOv8 + Vue3.0 + Django4.0。
|
||||
> 起始日期:2026-05-07
|
||||
|
||||
---
|
||||
|
||||
## 1. 硬件盘点与能力边界
|
||||
|
||||
| 组件 | 规格 | 训练能力 | 推理能力 |
|
||||
|---|---|---|---|
|
||||
| CPU | i7-14650HX 16C/24T @2.20GHz,睿频5.2GHz | 数据预处理 / 标注 / 辅助 | OpenVINO CPU推理可行 |
|
||||
| GPU | RTX 4060 Laptop 8GB GDDR6(Y7000P 2024标配,需核实) | **YOLOv8n/s 可训练,batch≤16** | FP16 实时推理 60+ FPS |
|
||||
| RAM | 16GB DDR5 | **瓶颈**,大数据集需分批加载 | 推理无压力 |
|
||||
| 存储 | 1TB NVMe(标配) | 够用,注意清理缓存 | — |
|
||||
| 散热 | 笔记本主动散热 | **持续满载训练时需外接散热器** | 推理负载低 |
|
||||
|
||||
**结论**:这台笔记本可以**完成训练 + Demo推理 + Web开发全流程**,但 Jetson / TensorRT / DeepStream 路线需要**降级替代**。
|
||||
|
||||
### 首要动作:确认 GPU 型号
|
||||
|
||||
开机执行 `nvidia-smi`(或设备管理器)确认:
|
||||
- 若为 **RTX 4060 8GB**:方案按本文执行
|
||||
- 若为 **RTX 4050 6GB**:把 `imgsz` 从 640 降到 416,`batch` 从 16 降到 8
|
||||
- 若为**核显/无独显**:只能用 CPU 训练,YOLOv8n 一轮要数小时,建议立即借/租算力
|
||||
|
||||
---
|
||||
|
||||
## 2. 方案修正总览(对比原方案)
|
||||
|
||||
| 环节 | 原方案 | 修正后(单笔记本) | 修正理由 |
|
||||
|---|---|---|---|
|
||||
| 训练硬件 | Jetson Orin Nano | **Y7000P RTX 4060** | 无 Jetson |
|
||||
| 训练模型 | YOLOv8n/s 随意 | **固定 YOLOv8n**(s 作备选) | 8GB 显存 + 16GB 内存限制 |
|
||||
| 边缘部署演示 | Jetson + DeepStream + TensorRT | **笔记本 + USB工业相机 + ONNX Runtime GPU** | 节省硬件预算,聚焦算法 |
|
||||
| Demo 形态 | 边缘盒子现场演示 | **笔记本直接演示 + 预录视频兜底** | 现场网络/电源风险可控 |
|
||||
| 实时推理 | TensorRT FP16 | **ONNX Runtime DirectML 或 CUDA EP** | Windows 原生支持,无需交叉编译 |
|
||||
| 数据标注 | 标注团队 | **本机 Label Studio / labelme** | 5人团队自标注即可 |
|
||||
| 产品部署 | 云服务器 | **本机 Docker + ngrok 内网穿透** 临时演示 | 省服务器钱 |
|
||||
| 生产边缘盒 | Jetson 自购 | **MVP 阶段客户采购 / 租用** | 获奖后再投硬件 |
|
||||
|
||||
---
|
||||
|
||||
## 3. GitHub 项目选型(按硬件修正)
|
||||
|
||||
### 主选:aaravomega/pcb-defect-detection
|
||||
|
||||
https://github.com/aaravomega/pcb-defect-detection
|
||||
|
||||
- YOLOv8 + Ultralytics 标准流程,与 Python3.8 技术栈一致
|
||||
- Windows 原生支持,不需 Linux / Docker
|
||||
- fork 后直接改 `data.yaml` 和 `train.py`
|
||||
|
||||
### 数据:HRIPCB(主)+ DeepPCB(备)
|
||||
|
||||
- **HRIPCB**(Roboflow 一键导出 YOLO 格式):https://universe.roboflow.com/ma007/hripcb/dataset/3
|
||||
- **DeepPCB**(含模板图,后期差分增强用):https://github.com/tangsanli5201/DeepPCB
|
||||
|
||||
### 演示方案:放弃 Deepstream,改用 ONNX Runtime + OpenCV
|
||||
|
||||
- 原因:NVIDIA DeepStream 主要面向 Jetson 和 Tesla 服务器卡,笔记本 4060 用起来收益小、坑多
|
||||
- 替代:训练后的 `best.pt` → 导出 ONNX → **ONNX Runtime GPU (CUDA Execution Provider)** → OpenCV 读 USB 相机 → Vue 看板展示
|
||||
- 备选:**OpenVINO**(Intel CPU 推理,i7-14650HX 上 YOLOv8n 可跑 20-30 FPS,作为 GPU 挂掉时的兜底)
|
||||
|
||||
### 挠性板扩展:Flexible-PCB-Defect-Detection
|
||||
|
||||
https://github.com/Pranshuk29/Flexible-PCB-Defect-Detection
|
||||
|
||||
- 仅在 MVP 阶段、成功对接梅州智科后启用
|
||||
- 不在 Demo 阶段占用训练资源
|
||||
|
||||
---
|
||||
|
||||
## 4. 训练配置(针对 8GB 显存 + 16GB 内存)
|
||||
|
||||
### 4.1 环境清单(Windows 11 + WSL2 可选)
|
||||
|
||||
```
|
||||
Python 3.8.x
|
||||
PyTorch 2.0.1 + CUDA 11.8(Y7000P 驱动原生支持)
|
||||
Ultralytics 8.x
|
||||
onnxruntime-gpu 1.16+
|
||||
opencv-python 4.8+
|
||||
label-studio 或 labelme
|
||||
```
|
||||
|
||||
> 不建议用 WSL2 训练:Windows 下 PyTorch CUDA 性能已够,WSL 多一层开销且调试麻烦。
|
||||
|
||||
### 4.2 训练超参数(起步版)
|
||||
|
||||
```yaml
|
||||
# train.yaml(Y7000P 实测可行)
|
||||
model: yolov8n.pt
|
||||
data: hripcb.yaml
|
||||
imgsz: 640
|
||||
batch: 16 # 8GB 显存极限,若 OOM 降到 8
|
||||
epochs: 100
|
||||
device: 0 # 单 GPU
|
||||
workers: 4 # 16GB 内存下别超 4,否则卡死
|
||||
amp: True # 混合精度必开,省显存
|
||||
cache: ram # 数据集 <2GB 时开 RAM 缓存加速
|
||||
patience: 20 # 早停,省电省时间
|
||||
```
|
||||
|
||||
### 4.3 训练时间预估(HRIPCB 1386 张)
|
||||
|
||||
| 模型 | 单轮时间 | 100轮总时 | 显存占用 |
|
||||
|---|---|---|---|
|
||||
| YOLOv8n | ~45s | **约 1.5 小时** | ~4GB |
|
||||
| YOLOv8s | ~80s | 约 2.5 小时 | ~6GB |
|
||||
| YOLOv8m | OOM 风险 | 不推荐 | >8GB |
|
||||
|
||||
**建议**:先 n 后 s,n 版本达到 mAP@0.5 > 0.9 即可上路演,s 作为备选。
|
||||
|
||||
### 4.4 散热与稳定性
|
||||
|
||||
- 训练时**外接散热底座**,温度控制在 85℃ 以内
|
||||
- 关闭 Windows 自动更新,避免训练中断
|
||||
- 每晚跑训练、白天做产品开发,错峰使用笔记本资源
|
||||
|
||||
---
|
||||
|
||||
## 5. Demo 现场展示方案(替代 Jetson)
|
||||
|
||||
### 5.1 硬件清单(总预算 ≤ 2000 元)
|
||||
|
||||
| 设备 | 型号建议 | 价格 | 用途 |
|
||||
|---|---|---|---|
|
||||
| USB 工业相机 | 海康威视 MV-CU013-A0UC 或 海康 USB 摄像头 | ~800-1500 | 实拍 PCB 样品 |
|
||||
| LED 环形光源 | 工业白光环形灯 | ~200 | 保证光照一致 |
|
||||
| 简易光箱 / 黑色背景板 | 自制 | ~100 | 降低杂光干扰 |
|
||||
| PCB 样品 | 向本地厂讨要废板 | 0 | 演示素材 |
|
||||
| 外接散热底座 | 联想/酷冷至尊 | ~200 | 训练稳定性 |
|
||||
|
||||
### 5.2 演示流程
|
||||
|
||||
```
|
||||
笔记本(Y7000P)
|
||||
├─ USB 相机 → OpenCV 取流
|
||||
├─ ONNX Runtime GPU 推理
|
||||
├─ Django 后端写入事件
|
||||
├─ Vue 看板实时展示(localhost)
|
||||
└─ 投屏到决赛现场大屏
|
||||
```
|
||||
|
||||
### 5.3 三重兜底
|
||||
|
||||
- **A 计划**:现场实时相机 + PCB 样品演示(最理想)
|
||||
- **B 计划**:预录 90 秒场景视频 + Vue 看板重放(现场网络/相机故障时启用)
|
||||
- **C 计划**:纯截图 PPT(极端情况,笔记本挂掉时)
|
||||
|
||||
**每个计划必须提前演练 3 次以上**。
|
||||
|
||||
---
|
||||
|
||||
## 6. 开发环境(单机全栈)
|
||||
|
||||
```
|
||||
Y7000P 上同时跑:
|
||||
├─ 终端1:Django runserver (8000)
|
||||
├─ 终端2:Vue dev server (5173)
|
||||
├─ 终端3:YOLOv8 推理服务(FastAPI 独立进程,8001)
|
||||
├─ 终端4:Label Studio(8080,标注用)
|
||||
└─ 后台:PostgreSQL 或 SQLite(MVP 阶段 SQLite 足够)
|
||||
```
|
||||
|
||||
**资源分配**:
|
||||
- 训练时段:关掉 Django/Vue/Label Studio,独占 GPU 和内存
|
||||
- 开发时段:关掉训练,16GB 内存跑全栈无压力
|
||||
- **不要同时训练和开发**,16GB 必 OOM
|
||||
|
||||
---
|
||||
|
||||
## 7. 三个月执行计划(对齐 Y7000P 节奏)
|
||||
|
||||
| 周 | 工作内容 | 是否占用 GPU |
|
||||
|---|---|---|
|
||||
| W1 | 确认 GPU 型号;装环境;fork aaravomega;下载 HRIPCB | 轻度(跑通推理) |
|
||||
| W2 | YOLOv8n 在 HRIPCB 上训练完成;拿到 mAP 基准 | **整夜占用** |
|
||||
| W3 | Django 后端框架 + Vue 看板骨架;接 SQLite | 否 |
|
||||
| W4 | YOLOv8 导出 ONNX;FastAPI 推理服务;OpenCV 取相机流 | 轻度 |
|
||||
| W5 | 买 USB 工业相机 + 环形光;搭建演示光箱;拍测试素材 | 否 |
|
||||
| W6 | Demo 版 v1:相机 → 推理 → 看板 全链路打通 | 轻度 |
|
||||
| W7 | 联系丰顺威达 / 梅州超捷 / 智科,签 NDA 收样本 | 否 |
|
||||
| W8 | 真实样本标注(Label Studio)+ 迁移学习微调 | **整夜占用** |
|
||||
| W9 | 规则引擎(批次缺陷率/追溯)+ 事件管理 + 告警 | 否 |
|
||||
| W10 | 挠性板扩展(若智科签约)+ 录 90s 场景视频 | 轻度 |
|
||||
| W11 | 软著申请;客户 MOU 签署;BP 定稿 | 否 |
|
||||
| W12 | 决赛 Demo 联调;三重兜底演练;路演彩排 | 否 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 风险清单(硬件层)
|
||||
|
||||
| 风险 | 概率 | 影响 | 对策 |
|
||||
|---|---|---|---|
|
||||
| 笔记本训练过热降频 | 高 | 训练慢 | 外接散热底座 + 限制房间温度 |
|
||||
| 8GB 显存 OOM | 中 | 训练中断 | batch 降到 8;imgsz 降到 416 |
|
||||
| 16GB 内存爆满 | 中 | 系统卡死 | workers 降到 2;关闭浏览器等 |
|
||||
| 决赛现场笔记本故障 | 低 | **灾难** | 二号机备份(团队另一人的笔记本同步工程)+ B/C 计划 |
|
||||
| GPU 驱动更新导致 CUDA 挂掉 | 低 | 训练中断 | 关闭 NVIDIA 自动更新;锁定驱动版本 |
|
||||
| 真实 PCB 样本获取失败 | 中 | 只能用公开数据集 | 提前 2 周对接 2 家以上厂商分散风险 |
|
||||
|
||||
---
|
||||
|
||||
## 9. 后续升级路径(晋级后)
|
||||
|
||||
- **初赛通过后**:考虑租云 GPU(AutoDL / 腾讯云 GN7 一周 ~300 元)加速 YOLOv8s / m 训练
|
||||
- **获奖后**:用奖金采购 1 台 **Jetson Orin Nano 8GB**(约 3500 元)做客户演示边缘盒子,切回原方案的 DeepStream 部署路线
|
||||
- **MVP 阶段**:客户侧硬件由客户采购或租用,我方只交付**软件 + 配置服务**
|
||||
|
||||
---
|
||||
|
||||
## 10. 一句话结论
|
||||
|
||||
**用好 Y7000P 这一台机器,完全够支撑 Demo 版晋级**——关键是**把训练和开发错峰安排,显存内存资源专用**。Jetson / 云服务器留到 MVP 阶段和获奖后再投入,Demo 阶段聚焦"算法精度 + 产品闭环 + 本地客户意向"三板斧即可。
|
||||
@@ -0,0 +1,41 @@
|
||||
# PCB线路板缺陷检测项目简介
|
||||
|
||||
## 一、项目介绍
|
||||
|
||||
PCB(印刷电路板)被称为"电子产品之母",手机、电脑、汽车里都有它的身影。一块线路板上布满了细如发丝的铜线路和密密麻麻的焊点,任何一处瑕疵都可能导致整台设备故障。
|
||||
|
||||
传统的人工检测方式依赖质检员用放大镜逐片检查,效率低、眼睛疲劳后漏检率高,而且培养一名熟练质检员需要数月时间。我们的项目利用AI视觉技术——就像给机器装上一双"火眼金睛",通过工业相机自动拍摄线路板图像,再由AI算法实时识别短路、断路、缺焊、异物等各类缺陷,检测速度比人工快数十倍,且24小时不知疲倦,漏检率大幅降低。
|
||||
|
||||
本项目旨在帮助中小线路板工厂以较低成本实现智能化质检升级,提升产品良率,降低返工成本。
|
||||
|
||||
## 二、工厂调研问题清单
|
||||
|
||||
### 生产流程类
|
||||
1. 工厂目前各生产环节(曝光、蚀刻、钻孔、电镀、丝印等)的工艺流程是怎样的?
|
||||
2. 质检环节安排在哪些工序之后?目前有哪些检测站点?
|
||||
3. 日产量/月产量大概是多少?产线运行几班制?
|
||||
|
||||
### 缺陷情况类
|
||||
4. 目前最常见的缺陷类型有哪些?各占多大比例?
|
||||
5. 各类缺陷的成因是什么?主要在哪些工序产生?
|
||||
6. 是否有缺陷分类的标准或规范?是否有缺陷样本图片存档?
|
||||
7. 最严重、最亟待解决的缺陷类型是哪一种?
|
||||
|
||||
### 现有检测方式类
|
||||
8. 目前采用人工目检还是已有AOI(自动光学检测)设备?
|
||||
9. 如有AOI设备,是什么品牌型号?检测效果和痛点是什么?
|
||||
10. 人工检测的漏检率和误检率大约是多少?
|
||||
11. 一名质检员一天能检测多少片板?
|
||||
|
||||
### 数据与合作类
|
||||
12. 是否愿意提供历史缺陷图片用于AI模型训练?能否签署数据保密协议?
|
||||
13. 对AI检测的准确率期望值是多少?(如漏检率<0.5%、误检率<2%)
|
||||
14. 产线上是否已有工业相机或是否方便加装?
|
||||
15. 如果部署AI检测系统,希望以什么形式合作?(一次性采购、按月租赁、按检测量计费?)
|
||||
|
||||
### 环境与部署类
|
||||
16. 产线环境的光照条件如何?是否有稳定的照明?
|
||||
17. 检测工位是否有网络环境?内网还是外网?
|
||||
18. 线路板来料的尺寸范围是多少?最小线宽/线距是多少?
|
||||
19. 对检测速度的要求是什么?(如每秒检测几片)
|
||||
20. 工厂目前的IT基础设施如何?是否有专人维护自动化设备?
|
||||
@@ -0,0 +1,131 @@
|
||||
# PCB缺陷识别开源项目调研
|
||||
|
||||
> 服务于梅州市新一代电子信息暨人工智能产业创新创业大赛"PCB/电路板AI视觉缺陷检测平台"选题。
|
||||
> 结合2025年决赛PCB获奖企业产品形态(刚性板、挠性板、铝基板、Mini LED板、PTFE高频板、OSP涂层等),筛选出覆盖"基础6缺陷 / 挠性板 / 边缘部署 / 高质量基准"四个互补切面的5个GitHub项目,均有公开数据集可直接复用。
|
||||
> 调研日期:2026-05-07
|
||||
|
||||
---
|
||||
|
||||
## 选型总览
|
||||
|
||||
| # | 项目 | 框架 | 数据集 | 覆盖切面 | 对应梅州PCB厂 |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | tangsanli5201/DeepPCB | 基础baseline | DeepPCB(1500对) | 6类标准缺陷 + 模板对齐 | 超捷、华达、智科(AOI后复判) |
|
||||
| 2 | Ixiaohuihuihui/Tiny-Defect-Detection-for-PCB | 小目标优化 | HRIPCB(北大,1386张) | 微小缺陷 + 高分辨率 | 丰顺威达(PTFE高频板)、兴宁精进(高效散热) |
|
||||
| 3 | aaravomega/pcb-defect-detection | YOLOv8 | 7971张(HRIPCB扩增) | 即插即用算法底座 | 作为MVP版核心底座 |
|
||||
| 4 | clintonoduor/PCB-Defect-Detection-using-Deepstream | YOLOv5 + Jetson + TensorRT | HRIPCB | 边缘实时部署 | 所有中小PCB厂(不接受云端) |
|
||||
| 5 | Pranshuk29/Flexible-PCB-Defect-Detection | YOLOv5 + FlexSim | 自建FPC挠性板数据集 | 挠性板专项 | 梅州智科(挠性印制电路板,2025三等奖) |
|
||||
|
||||
---
|
||||
|
||||
## 1. tangsanli5201/DeepPCB — 基准数据集 + baseline
|
||||
|
||||
- **仓库**:https://github.com/tangsanli5201/DeepPCB
|
||||
- **数据集特性**:
|
||||
- **1,500组图像对**:每组包含"无缺陷模板图 + 带缺陷测试图",**像素级对齐**
|
||||
- 6类缺陷:open(断路)、short(短路)、mousebite(鼠咬)、spur(毛刺)、pinhole(针孔)、spurious copper(余铜)
|
||||
- 标注格式:PASCAL VOC(bounding box),开箱即用
|
||||
- **适配梅州场景**:对应**梅州超捷、华达、智科**等走"AOI后复判"路线的PCB厂——有模板图可用**差分增强**,检测精度更高
|
||||
- **建议用法**:作为**初赛资料**的算法精度证据(mAP数字有行业共识),先跑通baseline再迁移到真实样本
|
||||
|
||||
## 2. Ixiaohuihuihui/Tiny-Defect-Detection-for-PCB — 微小缺陷 + 高分辨率
|
||||
|
||||
- **仓库**:https://github.com/Ixiaohuihuihui/Tiny-Defect-Detection-for-PCB
|
||||
- **数据集**:**HRIPCB**(北京大学人机交互开放实验室发布)
|
||||
- 1,386张合成PCB图,6类缺陷(同DeepPCB)
|
||||
- **分辨率更高,缺陷尺度更小**,更接近真实AOI拍摄环境
|
||||
- **镜像下载**:
|
||||
- Roboflow:https://universe.roboflow.com/ma007/hripcb/dataset/3 (一键导出YOLO/COCO格式)
|
||||
- Kaggle:https://www.kaggle.com/datasets/akhatova/pcb-defects
|
||||
- **适配梅州场景**:对应**丰顺威达(PTFE高频板孔工艺)、兴宁精进(高效散热板)**这类**线宽细、缺陷尺度小**的场景
|
||||
- **建议用法**:作为训练主力,HRIPCB的小目标特性能弥补DeepPCB分辨率不足的问题
|
||||
|
||||
## 3. aaravomega/pcb-defect-detection — YOLOv8 即插即用
|
||||
|
||||
- **仓库**:https://github.com/aaravomega/pcb-defect-detection
|
||||
- **数据集**:**7,971张**PCB标注图,6类缺陷(HRIPCB + 数据扩增)
|
||||
- **框架**:YOLOv8 + Ultralytics transfer learning
|
||||
- **技术栈匹配**:与 CLAUDE.md 约定的 Python3.8 + YOLOv8 完全一致
|
||||
- **适配梅州场景**:作为**MVP版的算法底座**,YOLOv8n/s 在 Jetson Nano/Orin 上可跑实时推理
|
||||
- **建议用法**:直接 fork,把 Django4.0 后端和 Vue3.0 看板接上就是完整产品
|
||||
|
||||
## 4. clintonoduor/PCB-Defect-Detection-using-Deepstream — 边缘部署样板
|
||||
|
||||
- **仓库**:https://github.com/clintonoduor/PCB-Defect-Detection-using-Deepstream
|
||||
- **数据集**:HRIPCB + YOLOv5 训练权重
|
||||
- **亮点**:**NVIDIA DeepStream + Jetson AGX** 端到端 pipeline
|
||||
- 含 TensorRT 量化
|
||||
- 视频流实时推理
|
||||
- 实时可视化
|
||||
- **适配梅州场景**:**路演现场演示神器**
|
||||
- 评委偏好"边缘盒子 + 摄像头 + 实时告警"三件套
|
||||
- 中小 PCB 厂不接受云端部署,边缘盒子是刚需
|
||||
- **建议用法**:作为 **Demo 版的硬件展示方案**,现场摆一台 Jetson Orin Nano + 工业相机 + PCB 样品
|
||||
|
||||
## 5. Pranshuk29/Flexible-PCB-Defect-Detection — 挠性板专项差异化
|
||||
|
||||
- **仓库**:https://github.com/Pranshuk29/Flexible-PCB-Defect-Detection
|
||||
- **数据集**:自建 **FPC 挠性板**数据集 + YOLOv5 + **FlexSim 数据增强**(模拟弯折形变)
|
||||
- **适配梅州场景**:
|
||||
- **直接对应 2025 三等奖企业"梅州智科电路板"的挠性印制电路板产品线**
|
||||
- FPC 因弯折产生的形变缺陷(折痕、铜箔裂纹、层间分离)是**通用 PCB 数据集覆盖不到**的差异化能力
|
||||
- **建议用法**:作为**本次参赛的差异化亮点**
|
||||
- 在标准 6 类缺陷之外加一个"挠性板专用"能力模块
|
||||
- 与梅州智科对接签 MOU(客户意向书)
|
||||
|
||||
---
|
||||
|
||||
## 备选项目(路演备选 / 答辩应对)
|
||||
|
||||
| 项目 | URL | 适用场景 |
|
||||
|---|---|---|
|
||||
| AyaAshraf76/PCB-defect-detection | https://github.com/AyaAshraf76/PCB-defect-detection | YOLOv8 + Roboflow 数据,教学级 |
|
||||
| Hazrat-Ali9/Deep-Learning-AOI-for-PCBs | https://github.com/Hazrat-Ali9/Deep-Learning-AOI-for-PCBs-Real-Time-YOLO-Detection-of-Missing-Holes-Mouse-Bites-Shorts-and-Spurs | 实时 AOI 场景,补充参考 |
|
||||
| EzekiasOkuPevi/PCB-Defect-Detection-Using-Deep-Learning | https://github.com/EzekiasOkuPevi/PCB-Defect-Detection-Using-Deep-Learning | YOLOv8 vs YOLOv10 对比,答辩用 |
|
||||
| SiemonCha/pcb-defect-detection | https://github.com/SiemonCha/pcb-defect-detection | 生产级 pipeline,工程参考 |
|
||||
| utkarsh-ankit/Defect-Detection-of-PCB | https://github.com/utkarsh-ankit/Defect-Detection-of-PCB | MobileNets 轻量化方案 |
|
||||
|
||||
---
|
||||
|
||||
## 整合推进建议(对齐 v1.1 的 Demo 版→MVP 版分档)
|
||||
|
||||
### Demo 版(1.5 个月,路演可用)
|
||||
|
||||
1. 拉取 **项目1 DeepPCB** + **项目2 HRIPCB** 数据集合并训练
|
||||
2. 用 **项目3 aaravomega/pcb-defect-detection** 的 YOLOv8 底座做迁移学习
|
||||
3. 用 **项目4 Deepstream** 方案在 Jetson 上做实时演示
|
||||
4. 产出:现场可演示的边缘盒子 + 标准 mAP 基准数字
|
||||
|
||||
### MVP 版(3 个月,客户可交付)
|
||||
|
||||
1. 接入 **1 家梅州本地 PCB 厂**真实产线样本,微调项目3的模型
|
||||
2. 用 **项目5 Flexible-PCB** 的 FPC 方案扩展挠性板能力,对接梅州智科
|
||||
3. Django4.0 + Vue3.0 产品化:用户系统 / 规则引擎 / 告警回放 / 追溯看板
|
||||
4. 产出:客户 MOU、软著证书、真实产线验证数据
|
||||
|
||||
### 路演包装话术
|
||||
|
||||
> "基于北大 HRIPCB 学术数据集 + 真实梅州产线样本双源训练,面向梅州本地 PCB 产业集群的 AI 视觉质检平台。"
|
||||
|
||||
**学术可信度**(北大数据集)**+ 本地化落地**(真实产线)**双背书**,契合 2025 公告"项目展示完成度"与"自主知识产权"考察点。
|
||||
|
||||
---
|
||||
|
||||
## 版权与合规提示
|
||||
|
||||
- **DeepPCB、HRIPCB** 均以学术用途发布
|
||||
- 商业化部署前需在对应论文 / README 中确认授权条款
|
||||
- 路演阶段使用无碍,但要提前准备自有数据集方案用于答辩应对
|
||||
- 建议:**尽早启动真实样本采集**,降低对学术数据集的依赖
|
||||
|
||||
---
|
||||
|
||||
## 参考资料
|
||||
|
||||
- [pcb-defect-detection · GitHub Topics](https://github.com/topics/pcb-defect-detection)
|
||||
- [HRIPCB on Roboflow Universe](https://universe.roboflow.com/ma007/hripcb/dataset/3)
|
||||
- [PCB Defects dataset on Kaggle](https://www.kaggle.com/datasets/akhatova/pcb-defects)
|
||||
- [PCB-YOLOV8X: a network for detecting micro-sized defects on PCB surfaces (Nature, 2026)](https://www.nature.com/articles/s41598-026-46089-6)
|
||||
- [An Open Source Enhancement Algorithm for PCB Defect Detection Based on YOLOv8 (arXiv 2025)](https://arxiv.org/html/2507.02963v1)
|
||||
- [Defect detection of printed circuit board assembly based on YOLOv5 (Nature, 2024)](https://www.nature.com/articles/s41598-024-70176-1)
|
||||
- [PCB Electronic Component Soldering Defect Detection Using YOLO11 (PMC, 2025)](https://pmc.ncbi.nlm.nih.gov/articles/PMC12158347/)
|
||||
@@ -0,0 +1,38 @@
|
||||
# 简介
|
||||
* 手机、电脑、汽车里都有PCB印刷电路板。
|
||||
* 一块线路板上布满了细如发丝的铜线路和密密麻麻的焊点,任何一处瑕疵都可能导致整台设备故障。
|
||||
* 传统的人工检测方式依赖质检员用放大镜逐片检查,效率低、眼睛疲劳后漏检率高,而且培养一名熟练质检员需要数月时间。
|
||||
* 我们的项目利用AI视觉技术,通过工业相机自动拍摄线路板图像,再由AI算法实时识别短路、断路、缺焊、异物等各类缺陷,检测速度比人工快数十倍,且24小时不知疲倦,漏检率大幅降低。
|
||||
* 搭配管理平台,随时查看记录和统计缺陷,生成数据报告.
|
||||
* 本项目帮助中小线路板工厂以较低成本实现智能化质检升级,提升产品良率,降低返工成本。
|
||||
|
||||
#调研问题:
|
||||
|
||||
##生产流程类
|
||||
|
||||
1. 工厂质检环节安排在哪些工序之后?
|
||||
2. 日产量/月产量大概是多少?产线运行几班制?
|
||||
|
||||
## 缺陷情况类
|
||||
1. 目前最常见的缺陷类型有哪些?各占多大比例?
|
||||
2. 各类缺陷的成因是什么?主要在哪些工序产生?
|
||||
3. 是否有缺陷分类的标准或规范?是否有缺陷样本图片存档?
|
||||
4. 最严重、最亟待解决的缺陷类型是哪一种?
|
||||
|
||||
## 现有检测方式类
|
||||
1. 目前采用人工目检还是已有AOI(自动光学检测)设备?
|
||||
2. 如有AOI设备,是什么品牌型号?检测效果和痛点是什么?
|
||||
3. 人工检测的漏检率和误检率大约是多少?
|
||||
4. 一名质检员一天能检测多少片板?
|
||||
|
||||
## 数据与合作类
|
||||
1. 对AI检测的准确率期望值是多少?(如漏检率<0.5%、误检率<2%)
|
||||
2. 如果部署AI检测系统,希望以什么形式合作?(一次性采购、按月租赁、按检测量计费?)
|
||||
|
||||
|
||||
## 环境与部署类
|
||||
1. 产线环境的光照条件如何?是否有稳定的照明?
|
||||
2. 检测工位是否有网络环境?内网还是外网?
|
||||
3. 线路板来料的尺寸范围是多少?最小线宽/线距是多少?
|
||||
4. 对检测速度的要求是什么?(如每秒检测几片)
|
||||
5. 工厂目前的IT基础设施如何?是否有专人维护自动化设备?
|
||||
Binary file not shown.
@@ -0,0 +1,757 @@
|
||||
# 明析平台 软件重构设计文档
|
||||
|
||||
> 版本:v2.0 · 日期:2026-05-24
|
||||
> 项目:基于机器视觉的图像识别技术应用平台——明析
|
||||
> 参赛:梅州市2026年新一代电子信息暨人工智能产业创新创业大赛 · 初创企业(创客)组
|
||||
|
||||
---
|
||||
|
||||
## 1. 重构背景
|
||||
|
||||
### 1.1 现有代码概况
|
||||
|
||||
当前仓库 `yolo_classification_system` 是一套**通用YOLO推理系统**,实现了:
|
||||
|
||||
| 模块 | 现状 |
|
||||
|------|------|
|
||||
| 后端 | Django 4 + DRF,JWT鉴权,支持图片/视频/摄像头三种推理模式 |
|
||||
| 推理 | `inference.py` 封装 ultralytics YOLO,模型缓存,结果落库 |
|
||||
| 数据模型 | `UserProfile`、`YoloResult`、`SceneNews`、`SystemLog`、`YoloStatsDaily` |
|
||||
| 前端 | Vue 3 + Vite,开发/生产环境分离,API代理配置完整 |
|
||||
| 演示模型 | 驾驶员疲劳检测(`driver-drowsiness-detection/best.pt`) |
|
||||
|
||||
### 1.2 重构目标
|
||||
|
||||
将通用系统改造为**PCB线路板缺陷检测专用平台(明析)**,在不改动客户现有硬件的前提下,通过算法叠加实现AI视觉质检能力升级。
|
||||
|
||||
---
|
||||
|
||||
## 2. 系统物理架构
|
||||
|
||||
### 2.1 两台机器部署
|
||||
|
||||
```
|
||||
┌─────────────────────────────────┐ 局域网 ┌──────────────────────────────────────┐
|
||||
│ 前置机(工业PC) │ ─────────────→ │ 推理服务器(Linux+GPU) │
|
||||
│ │ │ │
|
||||
│ 相机 ──USB3──→ mingxi-capture │ │ mingxi-vision :8001 (FastAPI) │
|
||||
│ (PyQt5桌面程序) │ │ mingxi-backend :8000 (Django) │
|
||||
│ Win7 SP1+ 工业机 │ │ mingxi-frontend :80 (Vue3) │
|
||||
└─────────────────────────────────┘ └──────────────────────────────────────┘
|
||||
```
|
||||
|
||||
| 角色 | 机器 | 操作系统 | 运行软件 |
|
||||
|------|------|---------|---------|
|
||||
| 前置机 | 工厂现有工业PC(USB3.0) | Windows 7 SP1 及以上 | mingxi-capture.exe |
|
||||
| 推理服务器 | 团队配置(RTX 4060 或以上) | Linux | mingxi-vision + mingxi-backend + mingxi-frontend |
|
||||
|
||||
### 2.2 数据流
|
||||
|
||||
```
|
||||
操作工(点击按钮)或 PLC触发信号
|
||||
│
|
||||
▼
|
||||
mingxi-capture(前置机)
|
||||
│
|
||||
├─① 同步 POST 图像 ──────────────→ mingxi-vision(推理服务器:8001)
|
||||
│ │ 推理完成
|
||||
│ ←── 返回标注图+缺陷列表+结论 ──────┘
|
||||
│
|
||||
├─ 立即在桌面程序展示结果(操作工可见)
|
||||
│ · 合格 / 不合格 结论
|
||||
│ · 标注了缺陷位置的图像
|
||||
│ · 每个缺陷的类型、等级、置信度
|
||||
│
|
||||
└─② 异步队列 POST 结果 ──────────→ mingxi-backend(:8000)
|
||||
│ 存入 SQLite
|
||||
若失败 → 本地SQLite重试队列 │
|
||||
▼
|
||||
管理员/组长/厂长
|
||||
通过浏览器查看
|
||||
(短轮询,1-2秒延迟)
|
||||
```
|
||||
|
||||
### 2.3 端口分配
|
||||
|
||||
| 服务 | 端口 | 调用方 |
|
||||
|------|------|-------|
|
||||
| mingxi-vision | 8001 | mingxi-capture(同步推理) |
|
||||
| mingxi-backend | 8000 | mingxi-capture(存档)、浏览器前端 |
|
||||
| mingxi-frontend | 80 | 浏览器 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 子项目拆分方案
|
||||
|
||||
### 3.1 项目总览
|
||||
|
||||
```
|
||||
mingxi-capture/ Python 3.8 · PyQt5 · Windows桌面程序(前置机运行)
|
||||
mingxi-vision/ Python 3.8 · FastAPI · PCB缺陷推理服务(推理服务器运行)
|
||||
mingxi-backend/ Python 3.8 · Django 4 · 业务后端(推理服务器运行)
|
||||
mingxi-frontend/ Vue 3.0 · 管理后台(推理服务器运行,浏览器访问)
|
||||
```
|
||||
|
||||
### 3.2 代码迁移决策
|
||||
|
||||
| 现有文件/模块 | 处置方式 | 说明 |
|
||||
|-------------|---------|------|
|
||||
| `yolo8/models.py` → `UserProfile` | **直接复用** | JWT鉴权体系完整,扩展 role 字段 |
|
||||
| `yolo8/models.py` → `SystemLog` | **直接复用** | 日志结构通用 |
|
||||
| `yolo8/jwt_utils.py` | **直接复用** | token版本机制保留 |
|
||||
| `yolo8/models.py` → `YoloResult` | **重构为** `DefectResult` | 增加PCB专用字段:缺陷等级、产线 |
|
||||
| `yolo8/models.py` → `SceneNews` | **删除** | 与PCB业务无关 |
|
||||
| `yolo8/models.py` → `YoloStatsDaily` | **重构为** `ProductionStat` | 改为产线维度统计 |
|
||||
| `yolo8/inference.py` → `run_image_inference` | **迁移至** `mingxi-vision` | 推理服务核心,增加ONNX支持 |
|
||||
| `yolo8/inference.py` → `run_camera_inference` | **删除** | 由 `mingxi-capture` 海康SDK替代 |
|
||||
| `yolo8/inference.py` → `_copy_to_ascii_temp_input` | **删除** | 改为内存读图,彻底消除中文路径问题 |
|
||||
| `yolo8/views.py` → 鉴权相关视图 | **直接复用** | `auth_*`、`user_*` 无需改动 |
|
||||
| `yolo8/views.py` → `yolo_task_image` | **重构为** `detect_pcb` | 简化,接收capture推送 |
|
||||
| `frontend/` | **重构** | 删除通用YOLO页面,改为管理后台 |
|
||||
|
||||
---
|
||||
|
||||
## 4. mingxi-capture(前置采集程序)
|
||||
|
||||
### 4.1 定位
|
||||
|
||||
运行在**前置机**(工厂工业PC)上的 Windows 原生桌面程序。操作工唯一的交互界面,负责连接相机、触发检测、展示结果,并将结果异步上报至后台。
|
||||
|
||||
### 4.2 技术栈
|
||||
|
||||
```
|
||||
语言:Python 3.8
|
||||
GUI:PyQt5(支持 Windows 7 SP1 及以上,系统托盘、对话框完整支持)
|
||||
相机:MvImport(海康威视MVS Python SDK,随MVS安装包提供)
|
||||
本地存储:SQLite(通过 Python 内置 sqlite3)
|
||||
网络:requests(同步调用vision)+ threading(异步队列推送backend)
|
||||
打包:PyInstaller → 单文件 .exe 或目录,客户解压即用
|
||||
最低系统:Windows 7 SP1 x64
|
||||
```
|
||||
|
||||
### 4.3 目录结构
|
||||
|
||||
```
|
||||
mingxi-capture/
|
||||
├── camera/
|
||||
│ ├── hik_camera.py # 海康相机封装(MvImport,Phase 2接入)
|
||||
│ └── mock_camera.py # 模拟模式(读本地图片文件夹,Demo/开发用)
|
||||
├── ui/
|
||||
│ ├── main_window.py # 主窗口:触发按钮 + 图像展示 + 缺陷列表
|
||||
│ ├── config_dialog.py # 配置弹窗:服务器地址、端口、相机参数
|
||||
│ └── tray_icon.py # 系统托盘图标:显示/隐藏主窗口、退出
|
||||
├── core/
|
||||
│ ├── capture_worker.py # 拍图+调推理(在QThread中执行,不阻塞UI)
|
||||
│ ├── upload_queue.py # 异步上报队列(本地SQLite缓存+重试)
|
||||
│ └── settings.py # 读写 config.ini 配置文件
|
||||
├── assets/
|
||||
│ └── tray_icon.png # 托盘图标
|
||||
├── config.ini # 默认配置(首次运行生成)
|
||||
├── queue.db # 本地重试队列(SQLite,运行时生成)
|
||||
├── main.py # 程序入口
|
||||
├── requirements.txt
|
||||
└── mingxi-capture.spec # PyInstaller打包配置
|
||||
```
|
||||
|
||||
### 4.4 主窗口功能
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ 明析采集程序 [最小化到托盘] [×] │
|
||||
├─────────────┬───────────────────────────┤
|
||||
│ │ ┌───────────────────────┐│
|
||||
│ [手动检测] │ │ ││
|
||||
│ │ │ 标注图显示区域 ││
|
||||
│ 状态: 就绪 │ │ (检测后显示) ││
|
||||
│ │ │ ││
|
||||
│ 结论: │ └───────────────────────┘│
|
||||
│ ██ 不合格 │ │
|
||||
│ │ 缺陷列表: │
|
||||
│ 共 3 处 │ 类型 等级 置信度 │
|
||||
│ 最高: 致命 │ 断路 致命 0.94 │
|
||||
│ │ 短路 致命 0.88 │
|
||||
│ │ 毛刺 轻微 0.72 │
|
||||
└─────────────┴───────────────────────────┘
|
||||
```
|
||||
|
||||
### 4.5 配置弹窗(系统托盘右键→设置)
|
||||
|
||||
```
|
||||
推理服务地址: [192.168.1.100] 端口: [8001]
|
||||
后台服务地址: [192.168.1.100] 端口: [8000]
|
||||
相机序列号: [自动检测 ▼]
|
||||
模拟模式: [✓] 启用 图片目录: [C:\test_images\ 浏览]
|
||||
置信度阈值: [0.45]
|
||||
[保存] [取消]
|
||||
```
|
||||
|
||||
配置持久化至 `config.ini`,程序启动时自动读取。
|
||||
|
||||
### 4.6 异步上报队列
|
||||
|
||||
```python
|
||||
# core/upload_queue.py 逻辑
|
||||
|
||||
# 1. capture_worker 推理成功后,将结果插入本地 SQLite queue.db
|
||||
# 2. 后台线程每 5 秒扫描一次 queue.db 中状态为 pending 的记录
|
||||
# 3. 尝试 POST 到 mingxi-backend
|
||||
# 成功 → 标记为 sent
|
||||
# 失败 → retry_count + 1,超过 10 次标记为 failed(不再重试)
|
||||
# 4. 队列表结构:
|
||||
# id, image_base64, result_json, created_at, status, retry_count, last_error
|
||||
```
|
||||
|
||||
### 4.7 mock_camera 模拟模式
|
||||
|
||||
Phase 1(路演)无真实相机时,从配置目录中按序读取 `.jpg`/`.png` 文件,接口与 `hik_camera.py` 完全一致,上层代码无感知切换。
|
||||
|
||||
### 4.8 打包部署
|
||||
|
||||
```bash
|
||||
pyinstaller mingxi-capture.spec
|
||||
# 输出:dist/mingxi-capture/(目录模式)或 dist/mingxi-capture.exe(单文件)
|
||||
# 客户端:解压后双击 mingxi-capture.exe 即可运行
|
||||
# 随安装包附带:海康 MVS Runtime DLL(如需相机功能)
|
||||
```
|
||||
|
||||
### 4.9 触发模式(阶段支持)
|
||||
|
||||
| 触发方式 | Phase 1 | Phase 2 |
|
||||
|---------|---------|---------|
|
||||
| 手动点击按钮 | ✅ | ✅ |
|
||||
| PLC/传感器信号(串口/数字IO) | — | ✅ |
|
||||
| 定时自动(固定频率) | — | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 5. mingxi-vision(推理服务)
|
||||
|
||||
### 5.1 职责
|
||||
|
||||
- 接收图像(来自 `mingxi-capture`)
|
||||
- 调用明析平台(YOLOv8/ONNX)执行PCB缺陷检测
|
||||
- 返回缺陷列表(位置、类别、置信度、等级)+ 标注图(可选)
|
||||
- 支持模型热重载(管理员通过后台切换模型时调用)
|
||||
|
||||
### 5.2 技术栈
|
||||
|
||||
```
|
||||
语言:Python 3.8
|
||||
框架:FastAPI + uvicorn(workers=1,单进程,GPU不跨进程共享)
|
||||
推理:ultralytics YOLOv8(开发阶段)/ ONNX Runtime(生产部署)
|
||||
并发:asyncio.Lock(推理串行化,防止GPU冲突)
|
||||
GPU:CUDA 11.x + cuDNN
|
||||
```
|
||||
|
||||
### 5.3 并发处理:推理锁
|
||||
|
||||
```python
|
||||
# engine/loader.py
|
||||
import asyncio
|
||||
|
||||
_inference_lock = asyncio.Lock()
|
||||
_engine: Optional[BaseInferenceEngine] = None
|
||||
|
||||
def get_inference_lock() -> asyncio.Lock:
|
||||
return _inference_lock
|
||||
|
||||
# api/detect.py
|
||||
async def detect(image: UploadFile, conf: float = 0.45, ...):
|
||||
raw = await image.read()
|
||||
img = cv2.imdecode(np.frombuffer(raw, np.uint8), cv2.IMREAD_COLOR)
|
||||
|
||||
async with get_inference_lock(): # 请求自动排队,GPU串行执行
|
||||
result = engine.detect(img, conf)
|
||||
|
||||
return build_response(result)
|
||||
```
|
||||
|
||||
- 并发请求不会崩溃,自动排队等待
|
||||
- RTX 4060,YOLOv8s,PCB图:约 20~40ms/张 → 25~50 fps
|
||||
- Windows / Linux 行为完全一致(asyncio 事件循环原语,无OS依赖)
|
||||
|
||||
### 5.4 模型热重载接口
|
||||
|
||||
```
|
||||
POST /api/model/reload
|
||||
Body: { "model_path": "/opt/mingxi/models/pcb_v2.onnx" }
|
||||
Response: { "status": "ok", "model_version": "pcb_v2", "warmup_ms": 312 }
|
||||
```
|
||||
|
||||
热重载流程:
|
||||
1. 管理员在浏览器上传新模型并点击"切换"
|
||||
2. `mingxi-backend` 保存文件、更新数据库,调用此接口
|
||||
3. `mingxi-vision` 获取推理锁(等待当前推理完成),卸载旧模型,加载新模型,执行 warmup
|
||||
4. 释放锁,返回成功
|
||||
|
||||
### 5.5 目录结构
|
||||
|
||||
```
|
||||
mingxi-vision/
|
||||
├── api/
|
||||
│ ├── detect.py # POST /api/detect
|
||||
│ ├── model.py # POST /api/model/reload
|
||||
│ └── health.py # GET /api/health, /api/health/gpu
|
||||
├── engine/
|
||||
│ ├── base.py # DefectBox, DetectResult, BaseInferenceEngine
|
||||
│ ├── loader.py # 单例 + asyncio.Lock + init_engine()
|
||||
│ ├── ultralytics_adapter.py
|
||||
│ ├── onnx_adapter.py
|
||||
│ ├── annotator.py # 标注图生成(按severity着色)
|
||||
│ └── defect_classes.py # HRIPCB 6类 + 扩展4类
|
||||
├── models/ # .pt 和 .onnx 文件(gitignored)
|
||||
├── scripts/
|
||||
│ ├── export_onnx.py # pt → onnx 导出脚本
|
||||
│ └── benchmark.py
|
||||
├── tests/
|
||||
├── schema.py # Pydantic 响应模型
|
||||
├── config.py # BaseSettings(读 .env)
|
||||
├── main.py # FastAPI lifespan + app
|
||||
└── requirements.txt
|
||||
```
|
||||
|
||||
### 5.6 推理接口
|
||||
|
||||
```
|
||||
POST /api/detect
|
||||
Content-Type: multipart/form-data
|
||||
Fields:
|
||||
image: <图像文件>
|
||||
conf: float (default: 0.45)
|
||||
return_annotated: bool (default: false)
|
||||
|
||||
Response 200:
|
||||
{
|
||||
"task_id": "uuid",
|
||||
"duration_ms": 38,
|
||||
"verdict": "fail", // pass / fail
|
||||
"defect_count": 2,
|
||||
"max_severity": "fatal",
|
||||
"avg_confidence": 0.89,
|
||||
"defects": [
|
||||
{
|
||||
"class_id": 2,
|
||||
"class_name": "open_circuit",
|
||||
"class_name_zh": "断路",
|
||||
"confidence": 0.94,
|
||||
"severity": "fatal",
|
||||
"box_xyxy": [120, 45, 198, 123]
|
||||
}
|
||||
],
|
||||
"annotated_image_base64": "..." // 仅 return_annotated=true 时返回
|
||||
}
|
||||
```
|
||||
|
||||
### 5.7 缺陷类别与等级映射
|
||||
|
||||
```python
|
||||
DEFECT_CLASSES = {
|
||||
# HRIPCB 数据集原始6类
|
||||
0: {"name": "missing_hole", "zh": "缺孔", "severity": "fatal"},
|
||||
1: {"name": "mouse_bite", "zh": "鼠咬", "severity": "major"},
|
||||
2: {"name": "open_circuit", "zh": "断路", "severity": "fatal"},
|
||||
3: {"name": "short_circuit", "zh": "短路", "severity": "fatal"},
|
||||
4: {"name": "spur", "zh": "毛刺", "severity": "minor"},
|
||||
5: {"name": "spurious_copper", "zh": "余铜", "severity": "major"},
|
||||
# 扩展类(Phase 2 标注后训练)
|
||||
6: {"name": "oxidation", "zh": "氧化", "severity": "minor"},
|
||||
7: {"name": "solder_ball", "zh": "锡珠", "severity": "rework"},
|
||||
8: {"name": "scratch", "zh": "划痕", "severity": "minor"},
|
||||
9: {"name": "label_error", "zh": "标签错贴","severity": "rework"},
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. mingxi-backend(业务后端)
|
||||
|
||||
### 6.1 职责
|
||||
|
||||
- 接收 `mingxi-capture` 推送的检测结果,持久化至 SQLite
|
||||
- 提供 REST API 给浏览器管理后台
|
||||
- 用户管理、三级权限控制
|
||||
- 模型文件管理(上传、备份、切换、触发vision热重载)
|
||||
- 系统参数配置
|
||||
|
||||
### 6.2 技术栈
|
||||
|
||||
```
|
||||
语言:Python 3.8
|
||||
框架:Django 4.0 + django-rest-framework
|
||||
数据库:SQLite(内网单服务器部署,无高并发写入,SQLite足够)
|
||||
认证:JWT(从现有系统直接复用)
|
||||
```
|
||||
|
||||
> 初期不引入 Celery/Redis,保持部署简单。告警推送(Phase 2)再评估是否引入。
|
||||
|
||||
### 6.3 用户权限设计
|
||||
|
||||
| 角色 | 检测记录可见范围 | 其他权限 |
|
||||
|------|---------------|---------|
|
||||
| 操作工 | 仅自己触发的记录 | 无 |
|
||||
| 组长 | 本组所有成员的记录 | 查看本组统计 |
|
||||
| 管理员 | 全部记录 | 用户管理、系统配置、模型管理、查看所有统计 |
|
||||
|
||||
```python
|
||||
# UserProfile 扩展
|
||||
class UserProfile(models.Model):
|
||||
user = models.OneToOneField(User)
|
||||
role = models.CharField(choices=[
|
||||
("operator", "操作工"),
|
||||
("leader", "组长"),
|
||||
("admin", "管理员"),
|
||||
], default="operator")
|
||||
group = models.ForeignKey("WorkGroup", null=True) # 操作工和组长属于某个工组
|
||||
|
||||
class WorkGroup(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
leader = models.ForeignKey(UserProfile, null=True, related_name="led_group")
|
||||
```
|
||||
|
||||
### 6.4 模型管理设计
|
||||
|
||||
```python
|
||||
class ModelRegistry(models.Model):
|
||||
"""模型版本注册表"""
|
||||
name = models.CharField(max_length=100) # 文件名(含扩展名)
|
||||
runtime = models.CharField(choices=["pt", "onnx"])
|
||||
file_path = models.CharField(max_length=500) # 服务器本地路径
|
||||
file_size = models.IntegerField() # 字节数
|
||||
is_active = models.BooleanField(default=False) # 当前生效模型
|
||||
uploaded_by = models.ForeignKey(UserProfile)
|
||||
uploaded_at = models.DateTimeField(auto_now_add=True)
|
||||
notes = models.TextField(blank=True)
|
||||
```
|
||||
|
||||
**模型管理规则:**
|
||||
- 同时只有一个 `is_active=True`
|
||||
- 最多保留 10 个版本,超出时自动删除最早上传的非活跃版本及其文件
|
||||
- 切换模型时:更新 `is_active` → 调用 `POST http://localhost:8001/api/model/reload`
|
||||
|
||||
### 6.5 管理员可配置参数
|
||||
|
||||
| 参数类别 | 具体参数 |
|
||||
|---------|---------|
|
||||
| 推理参数 | 置信度阈值、IOU阈值、图像输入尺寸 |
|
||||
| 模型管理 | 上传模型、切换活跃模型、查看备份列表 |
|
||||
| 产线配置 | 产线名称、前置机IP(仅记录,不主动通信) |
|
||||
| 用户管理 | 新增/停用账号、分配角色、分配工组 |
|
||||
| 告警规则 | Phase 2,预留接口 |
|
||||
|
||||
### 6.6 数据模型设计
|
||||
|
||||
```python
|
||||
class DefectResult(models.Model):
|
||||
"""单次检测记录"""
|
||||
operator = models.ForeignKey(UserProfile, null=True)
|
||||
line_id = models.CharField(max_length=50, blank=True)
|
||||
source_image = models.ImageField(upload_to="originals/")
|
||||
annotated_image = models.ImageField(upload_to="annotated/", null=True)
|
||||
verdict = models.CharField(choices=["pass", "fail"])
|
||||
defect_count = models.IntegerField(default=0)
|
||||
max_severity = models.CharField(choices=["none","rework","minor","major","fatal"])
|
||||
detections_json = models.TextField() # 完整缺陷列表JSON
|
||||
avg_confidence = models.FloatField()
|
||||
duration_ms = models.IntegerField()
|
||||
model_version = models.CharField(max_length=100)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
|
||||
class ProductionStat(models.Model):
|
||||
"""每日产线统计(聚合)"""
|
||||
stat_date = models.DateField()
|
||||
line_id = models.CharField(max_length=50)
|
||||
total_count = models.IntegerField(default=0)
|
||||
pass_count = models.IntegerField(default=0)
|
||||
fail_count = models.IntegerField(default=0)
|
||||
defect_dist = models.TextField() # 各缺陷类型数量JSON
|
||||
|
||||
|
||||
class SysConfig(models.Model):
|
||||
"""系统参数键值存储"""
|
||||
key = models.CharField(max_length=100, unique=True)
|
||||
value = models.TextField()
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
```
|
||||
|
||||
### 6.7 API 接口设计
|
||||
|
||||
```
|
||||
# 认证(直接复用现有代码)
|
||||
POST /api/auth/login
|
||||
POST /api/auth/refresh
|
||||
POST /api/auth/logout
|
||||
GET /api/user/me
|
||||
|
||||
# 检测结果(capture推送入口)
|
||||
POST /api/detect/result/ # capture 上报推理结果
|
||||
GET /api/detect/results/ # 列表(按角色自动过滤)
|
||||
GET /api/detect/results/{id}/
|
||||
|
||||
# 统计
|
||||
GET /api/stats/overview/ # 今日合格率、缺陷总数
|
||||
GET /api/stats/trend/ # 近N天趋势
|
||||
GET /api/stats/defect-dist/ # 缺陷类型分布
|
||||
|
||||
# 模型管理(管理员)
|
||||
GET /api/admin/models/ # 模型列表(最多10条)
|
||||
POST /api/admin/models/upload/ # 上传新模型
|
||||
POST /api/admin/models/{id}/activate/ # 切换活跃模型(触发vision热重载)
|
||||
DELETE /api/admin/models/{id}/ # 手动删除某个备份
|
||||
|
||||
# 系统配置(管理员)
|
||||
GET /api/admin/config/
|
||||
PATCH /api/admin/config/
|
||||
|
||||
# 用户管理(管理员)
|
||||
GET /api/admin/users/
|
||||
POST /api/admin/users/
|
||||
PATCH /api/admin/users/{id}/
|
||||
GET /api/admin/groups/
|
||||
POST /api/admin/groups/
|
||||
|
||||
# 系统日志(直接复用)
|
||||
GET /api/admin/logs/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. mingxi-frontend(管理后台)
|
||||
|
||||
### 7.1 定位
|
||||
|
||||
**纯管理后台**,供组长、厂长、管理员通过浏览器使用。操作工不通过浏览器操作(操作工使用前置机桌面程序)。
|
||||
|
||||
### 7.2 技术栈
|
||||
|
||||
```
|
||||
框架:Vue 3 + Vite + TypeScript(保持现有配置不变)
|
||||
UI:Element Plus(复用现有)
|
||||
图表:ECharts(统计报表)
|
||||
实时:短轮询(每1秒请求一次最新记录,满足1-2秒延迟要求,无需WebSocket)
|
||||
```
|
||||
|
||||
> 短轮询而非 WebSocket:部署简单,Django 无需 Channels,延迟满足要求。
|
||||
|
||||
### 7.3 页面规划
|
||||
|
||||
| 路由 | 页面 | 可见角色 | 现有/新增 |
|
||||
|------|------|---------|---------|
|
||||
| `/login` | 登录 | 全部 | 复用 |
|
||||
| `/dashboard` | 实时监控看板 | 组长、管理员 | **新增** |
|
||||
| `/history` | 检测历史记录 | 全部(按权限过滤) | **新增** |
|
||||
| `/stats` | 统计报表 | 组长、管理员 | **新增** |
|
||||
| `/admin/models` | 模型管理 | 管理员 | **新增** |
|
||||
| `/admin/config` | 系统配置 | 管理员 | **新增** |
|
||||
| `/admin/users` | 用户管理 | 管理员 | 复用改造 |
|
||||
|
||||
### 7.4 实时监控看板(/dashboard)
|
||||
|
||||
- 最新检测结果卡片(标注图缩略图 + 结论)
|
||||
- 今日合格率仪表盘
|
||||
- 缺陷类型分布饼图
|
||||
- 近10条记录时间线
|
||||
- 每1秒轮询 `/api/detect/results/?limit=1&ordering=-created_at`
|
||||
|
||||
### 7.5 需删除的现有页面
|
||||
|
||||
```
|
||||
SceneNews 相关页面
|
||||
通用YOLO视频/摄像头检测页面
|
||||
驾驶员疲劳检测相关组件
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 系统间通信
|
||||
|
||||
### 8.1 完整通信图
|
||||
|
||||
```
|
||||
前置机(Windows) 推理服务器(Linux)
|
||||
┌──────────────────────┐ ┌──────────────────────┐
|
||||
│ mingxi-capture │ │ mingxi-vision │
|
||||
│ │──① POST─────→│ /api/detect │
|
||||
│ QThread推理线程 │←─ 结果返回──│ asyncio.Lock串行 │
|
||||
│ │ └──────────────────────┘
|
||||
│ 主线程展示结果 │
|
||||
│ │ ┌──────────────────────┐
|
||||
│ upload_queue线程 │──② 异步────→│ mingxi-backend │
|
||||
│ (失败则重试) │ │ /api/detect/result │
|
||||
└──────────────────────┘ │ │
|
||||
│ SQLite存档 │
|
||||
└──────────┬───────────┘
|
||||
│ REST API
|
||||
┌──────────▼───────────┐
|
||||
│ mingxi-frontend │
|
||||
│ 浏览器短轮询 │
|
||||
└──────────────────────┘
|
||||
管理员/组长/厂长 浏览器访问
|
||||
```
|
||||
|
||||
### 8.2 关键通信约定
|
||||
|
||||
| 通信路径 | 协议 | 格式 | 备注 |
|
||||
|---------|------|------|------|
|
||||
| capture → vision | HTTP POST | multipart/form-data(图像二进制) | 同步,等待结果 |
|
||||
| vision → capture | HTTP Response | JSON(含 base64 标注图) | 一次请求一次响应 |
|
||||
| capture → backend | HTTP POST | JSON(推理结果) | 异步,本地队列保障 |
|
||||
| browser → backend | HTTP REST | JSON | 短轮询 + 增删改查 |
|
||||
| backend → vision(热重载) | HTTP POST | JSON | 仅管理员切换模型时触发 |
|
||||
|
||||
### 8.3 前置机配置两个地址
|
||||
|
||||
前置机桌面程序配置界面中填写:
|
||||
- **推理服务地址**:`http://192.168.x.x:8001`(vision)
|
||||
- **后台服务地址**:`http://192.168.x.x:8000`(backend)
|
||||
|
||||
两个服务通常在同一台服务器上,填写相同IP、不同端口。
|
||||
|
||||
---
|
||||
|
||||
## 9. 硬件集成(相机配置)
|
||||
|
||||
### 9.1 推荐型号
|
||||
|
||||
| 阶段 | 型号 | 分辨率 | 接口 | 用途 |
|
||||
|------|------|--------|------|------|
|
||||
| Demo/POC | MV-CS060-10GM | 600万像素 | USB3.0 | 算法验证,接工业PC |
|
||||
| 产线部署 | MV-CA060-10GM | 600万像素 | GigE | 稳定产线,线长100m |
|
||||
| 大板检测 | MV-CA120-10GM | 1200万像素 | GigE | 视野≥200mm的PCB |
|
||||
|
||||
### 9.2 分辨率选型计算
|
||||
|
||||
```
|
||||
视野:100mm × 75mm(单次拍摄区域)
|
||||
最小缺陷:0.1mm(针孔、断路)
|
||||
像素尺寸 ≤ 0.1mm ÷ 3 = 0.033mm/pixel
|
||||
所需分辨率:100 ÷ 0.033 ≈ 3030 pixels(水平)
|
||||
结论:600万像素(3072×2048)满足需求 ✓
|
||||
```
|
||||
|
||||
### 9.3 配套清单(单工位 Demo 版)
|
||||
|
||||
| 器材 | 型号/规格 | 估价 |
|
||||
|------|----------|------|
|
||||
| 工业相机 | MV-CS060-10GM | 1800-2500元 |
|
||||
| 镜头 | 16mm C口定焦(1/1.8"靶面) | 500-800元 |
|
||||
| LED环形光源 | 同轴/低角度,直径120mm | 400-600元 |
|
||||
| USB3.0线缆 | ≤3m,锁扣式 | 80-150元 |
|
||||
| 简易支架光箱 | 铝型材,可调高度 | 200-400元 |
|
||||
| **合计** | | **约 3000-4450元** |
|
||||
|
||||
---
|
||||
|
||||
## 10. 开发阶段划分
|
||||
|
||||
### Phase 1:Demo版(目标:2026年6月中旬决赛前)
|
||||
|
||||
**路演方式:** 预先准备好的PCB图片,模拟模式演示完整流程,约5分钟。
|
||||
|
||||
**核心展示:** 操作工点击→检测→标注图展示→管理后台历史记录。
|
||||
|
||||
```
|
||||
P0(必须完成,路演核心):
|
||||
[ ] mingxi-vision:图片推理API + asyncio.Lock + 启动warmup
|
||||
[ ] mingxi-capture:PyQt5桌面程序,Mock模式,结果展示界面
|
||||
[ ] mingxi-backend:接收capture推送,DefectResult落库,基础REST API
|
||||
[ ] mingxi-frontend:登录 + 检测历史列表 + 详情页
|
||||
[ ] mingxi-backend:三级权限(操作工/组长/管理员)
|
||||
|
||||
P1(有时间做,加分项):
|
||||
[ ] mingxi-frontend:实时监控看板(短轮询)
|
||||
[ ] mingxi-backend:模型管理(上传/备份/切换)
|
||||
[ ] mingxi-frontend:统计报表页
|
||||
[ ] mingxi-capture:系统托盘图标
|
||||
```
|
||||
|
||||
### Phase 2:POC版(目标:2026年Q3,接入真实相机)
|
||||
|
||||
```
|
||||
[ ] mingxi-capture:接入海康MVS SDK,对接 MV-CS060-10GM
|
||||
[ ] mingxi-capture:PLC触发接口(串口/数字IO)
|
||||
[ ] mingxi-vision:ONNX Runtime部署(export_onnx.py导出)
|
||||
[ ] mingxi-vision:模型热重载接口完善
|
||||
[ ] mingxi-backend:告警规则引擎(连续不良数、缺陷率阈值)
|
||||
[ ] 完成1家PCB厂POC试用
|
||||
```
|
||||
|
||||
### Phase 3:产线版(目标:2026年Q4,首单签约)
|
||||
|
||||
```
|
||||
[ ] 多相机支持(多条产线并发采集)
|
||||
[ ] 模型迁移学习(基于客户真实样本微调)
|
||||
[ ] 告警推送(企业微信/短信)
|
||||
[ ] MES/ERP对接接口
|
||||
[ ] 边缘部署探索(Jetson Orin,脱离大型服务器)
|
||||
[ ] 软件著作权申请
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. 开发环境搭建
|
||||
|
||||
### 11.1 各项目启动命令
|
||||
|
||||
```bash
|
||||
# mingxi-vision(推理服务器,Linux)
|
||||
cd mingxi-vision
|
||||
pip install -r requirements.txt
|
||||
uvicorn main:app --host 0.0.0.0 --port 8001 --workers 1
|
||||
|
||||
# mingxi-backend(推理服务器,Linux)
|
||||
cd mingxi-backend
|
||||
pip install -r requirements.txt
|
||||
python manage.py migrate
|
||||
python manage.py runserver 0.0.0.0:8000
|
||||
|
||||
# mingxi-frontend(推理服务器,Linux)
|
||||
cd mingxi-frontend
|
||||
npm install
|
||||
npm run build # 生产:dist/ 由 nginx 托管
|
||||
npm run dev # 开发:localhost:5173
|
||||
|
||||
# mingxi-capture(前置机,Windows,开发阶段)
|
||||
cd mingxi-capture
|
||||
pip install -r requirements.txt
|
||||
python main.py # 启动桌面程序,自动进入Mock模式(若配置了图片目录)
|
||||
```
|
||||
|
||||
### 11.2 环境变量
|
||||
|
||||
```bash
|
||||
# mingxi-vision/.env
|
||||
MODEL_PATH=./models/pcb_defect_v1.pt
|
||||
RUNTIME=ultralytics # 或 onnx
|
||||
DEVICE=cuda # 或 cpu
|
||||
CONF_THRESHOLD=0.45
|
||||
|
||||
# mingxi-backend/.env
|
||||
SECRET_KEY=...
|
||||
DATABASE_PATH=./db.sqlite3
|
||||
VISION_SERVICE_URL=http://127.0.0.1:8001
|
||||
CORS_ALLOWED_ORIGINS=http://localhost:5173
|
||||
MODEL_STORAGE_DIR=/opt/mingxi/models
|
||||
MODEL_MAX_BACKUPS=10
|
||||
```
|
||||
|
||||
### 11.3 开发联调注意事项
|
||||
|
||||
- `mingxi-capture` 在 Windows 开发机上运行,配置 vision/backend 为开发机局域网IP
|
||||
- `mingxi-vision` 和 `mingxi-backend` 建议在同一台机器(或WSL2)上启动
|
||||
- SQLite 文件在 `mingxi-backend/` 目录下,无需额外数据库服务
|
||||
|
||||
---
|
||||
|
||||
## 12. 参考资料
|
||||
|
||||
| 资料 | 路径/来源 |
|
||||
|------|---------|
|
||||
| 创业技术书 | `梅州创新创业大赛/创业技术书v1.3.docx` |
|
||||
| 明析vision架构详细设计 | `梅州创新创业大赛/mingxi-vision-架构设计.md` |
|
||||
| 现有后端代码 | `yolo_classification_system/` |
|
||||
| PCB缺陷检测调研 | `梅州创新创业大赛/pcb缺陷检测初步方案.md` |
|
||||
| 海康MVS SDK | 随MVS安装包提供,路径:`C:\Program Files (x86)\MVS\Development\` |
|
||||
| HRIPCB数据集 | 公开学术数据集,CC BY 4.0,6类缺陷,10,000+样本 |
|
||||
| PyQt5文档 | https://doc.qt.io/qtforpython-5/ |
|
||||
| PyInstaller文档 | https://pyinstaller.org/en/stable/ |
|
||||
Reference in New Issue
Block a user