第2203篇:工业质检的视觉AI——缺陷检测系统的Java工程实现
2026/4/30大约 7 分钟
第2203篇:工业质检的视觉AI——缺陷检测系统的Java工程实现
适读人群:工业自动化或智能制造领域的Java工程师 | 阅读时长:约17分钟 | 核心价值:工业视觉质检系统的完整工程实现,从相机采集到缺陷分类的端到端方案
工厂的质检场景和互联网的AI应用完全不一样。
我去年参与过一个电子元器件工厂的质检系统改造项目。他们原来的做法是:传送带上放产品,工人盯着看,看到有问题的就手动剔除。一条线要配3-4个工人,眼睛盯着看8小时,漏检率大概在1.5%。
听起来漏检率不高,但这家厂的年产量是10亿个元器件,1.5%就是1500万个问题产品流出去,会造成大量售后和召回。
AI质检的目标不是"识别图片里有没有猫",而是:
- 实时性:每秒处理20-100个产品
- 精确性:漏检率要低于0.1%(比人工低10倍)
- 泛化性:能识别"没见过"的新缺陷类型
- 可靠性:24小时不间断运行,硬件故障自动切换
一、工业质检的技术架构选型
工业质检不能直接用通用VLM(GPT-4V等),原因:
- 响应延迟:VLM API调用需要2-5秒,传送带不会等你
- 精度不足:通用VLM没有针对特定工业缺陷微调,对微小划痕、色差等识别能力弱
- 离线要求:工厂通常是内网环境,不能访问外部API
推荐架构:专用检测模型 + VLM辅助分析
这个架构的关键是双层过滤:
- 第一层(YOLOv8):快速、高召回率,宁可多报不漏报
- 第二层(VLM):精确分析,减少第一层的误报
二、Java集成本地缺陷检测模型
用YOLOv8做第一层检测,通过Java调用Python服务:
@Service
public class DefectDetectionService {
private static final Logger log = LoggerFactory.getLogger(DefectDetectionService.class);
private final RestTemplate restTemplate;
@Value("${defect.detection.url:http://localhost:9000}")
private String detectionServiceUrl;
/**
* 缺陷检测核心方法
* @param imageBytes 产品图片字节数组
* @param productType 产品类型(用于选择对应模型)
* @return 检测结果
*/
public DetectionResult detect(byte[] imageBytes, String productType) {
long startTime = System.nanoTime();
try {
String base64Image = Base64.getEncoder().encodeToString(imageBytes);
Map<String, Object> request = Map.of(
"image", base64Image,
"product_type", productType,
"confidence_threshold", 0.3, // 低阈值,宁可误报不漏报
"iou_threshold", 0.45
);
ResponseEntity<Map> response = restTemplate.postForEntity(
detectionServiceUrl + "/detect",
request, Map.class);
long latencyNs = System.nanoTime() - startTime;
return parseDetectionResponse(response.getBody(), latencyNs / 1_000_000);
} catch (ResourceAccessException e) {
// 检测服务不可用,触发降级:全部标记为疑似,转人工
log.error("缺陷检测服务不可用,降级处理", e);
return DetectionResult.degraded("检测服务不可用");
}
}
@SuppressWarnings("unchecked")
private DetectionResult parseDetectionResponse(Map<String, Object> body, long latencyMs) {
boolean hasDefect = Boolean.TRUE.equals(body.get("has_defect"));
List<Map<String, Object>> detections = (List<Map<String, Object>>)
body.getOrDefault("detections", List.of());
List<DefectBox> defectBoxes = detections.stream()
.map(d -> new DefectBox(
(String) d.get("class"),
((Number) d.get("confidence")).floatValue(),
new BoundingBox(
((Number) d.get("x1")).intValue(),
((Number) d.get("y1")).intValue(),
((Number) d.get("x2")).intValue(),
((Number) d.get("y2")).intValue()
)
))
.collect(Collectors.toList());
return new DetectionResult(hasDefect, defectBoxes, latencyMs, false);
}
public record DetectionResult(
boolean hasDefect,
List<DefectBox> defects,
long latencyMs,
boolean isDegraded
) {
public static DetectionResult degraded(String reason) {
return new DetectionResult(true, List.of(), 0, true);
}
/**
* 是否需要VLM二次分析(高置信度直接放行/拦截,低置信度才需要VLM)
*/
public boolean needsVLMAnalysis() {
if (!hasDefect) return false;
// 如果最高置信度 < 0.7,说明是疑似,需要VLM确认
return defects.stream()
.anyMatch(d -> d.confidence() < 0.7f);
}
}
public record DefectBox(String defectClass, float confidence, BoundingBox box) {}
public record BoundingBox(int x1, int y1, int x2, int y2) {}
}三、VLM精细分析层
对于疑似缺陷,裁剪区域图像后用VLM做精细分析:
@Service
public class DefectVLMAnalyzer {
private final VisionService visionService;
// 缺陷类型的专业描述模板(帮助VLM做精确分类)
private static final Map<String, String> DEFECT_TYPE_GUIDE = Map.of(
"scratch", "划痕:表面有线状或条纹状损伤",
"dent", "凹坑:表面有局部下陷变形",
"stain", "污渍:表面有异色斑点或污染",
"crack", "裂纹:表面或内部有断裂线",
"bubble", "气泡:涂层或材料内部有空腔",
"missing", "缺料:产品某部分缺失"
);
/**
* 对疑似缺陷区域进行精细VLM分析
*/
public DefectAnalysis analyzeDefectRegion(byte[] fullImage,
BoundingBox defectRegion,
String productType) {
// 裁剪缺陷区域(加一些边距)
byte[] croppedImage = cropWithPadding(fullImage, defectRegion, 20);
// 构建专业提示词
String defectGuideText = DEFECT_TYPE_GUIDE.entrySet().stream()
.map(e -> "- " + e.getKey() + ": " + e.getValue())
.collect(Collectors.joining("\n"));
String prompt = String.format("""
你是一名%s产品质检专家。请分析这张产品缺陷图像。
可能的缺陷类型:
%s
请回答:
1. 这是真实缺陷还是误检(光线反射/阴影/噪点)?
2. 如果是真实缺陷,是哪种类型?
3. 缺陷严重程度:轻微(不影响功能)/ 中等(影响外观)/ 严重(影响功能)
4. 建议处理:通过 / 降级 / 报废
返回JSON格式:
{
"isRealDefect": true/false,
"defectType": "划痕/凹坑/污渍/裂纹/气泡/缺料/其他",
"severity": "轻微/中等/严重",
"recommendation": "通过/降级/报废",
"confidence": 0.0-1.0,
"description": "简短描述"
}
只返回JSON。
""", productType, defectGuideText);
VisionRequest request = VisionRequest.builder()
.images(List.of(ImageInput.fromBytes(croppedImage, "image/jpeg")))
.prompt(prompt)
.metadata(Map.of("detail", "high"))
.build();
String response = visionService.analyzeImage(request).getContent();
return parseDefectAnalysis(response);
}
private byte[] cropWithPadding(byte[] imageBytes, BoundingBox box, int padding) {
// 用OpenCV裁剪图像
Mat image = Imgcodecs.imdecode(new MatOfByte(imageBytes), Imgcodecs.IMREAD_COLOR);
int x = Math.max(0, box.x1() - padding);
int y = Math.max(0, box.y1() - padding);
int width = Math.min(image.cols() - x, (box.x2() - box.x1()) + 2 * padding);
int height = Math.min(image.rows() - y, (box.y2() - box.y1()) + 2 * padding);
Rect cropRect = new Rect(x, y, width, height);
Mat cropped = new Mat(image, cropRect);
MatOfByte outputBytes = new MatOfByte();
Imgcodecs.imencode(".jpg", cropped, outputBytes);
return outputBytes.toArray();
}
private DefectAnalysis parseDefectAnalysis(String response) {
try {
String cleanJson = response.replaceAll("```json\\s*", "")
.replaceAll("```\\s*", "").trim();
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(cleanJson);
return new DefectAnalysis(
root.get("isRealDefect").asBoolean(),
root.get("defectType").asText(),
root.get("severity").asText(),
root.get("recommendation").asText(),
root.get("confidence").asDouble(),
root.get("description").asText()
);
} catch (Exception e) {
// 解析失败,返回保守结果(视为缺陷)
return new DefectAnalysis(true, "未知", "严重", "报废", 0.5, "VLM分析失败");
}
}
public record DefectAnalysis(
boolean isRealDefect, String defectType, String severity,
String recommendation, double confidence, String description
) {}
}四、质检流水线的性能优化
工业质检对实时性要求极高,几个关键优化:
@Service
public class QualityInspectionPipeline {
private final DefectDetectionService detectionService;
private final DefectVLMAnalyzer vlmAnalyzer;
// 使用线程池隔离不同阶段的处理
private final ExecutorService detectionPool =
Executors.newFixedThreadPool(4, r -> {
Thread t = new Thread(r, "detection-worker");
t.setDaemon(true);
return t;
});
private final ExecutorService vlmPool =
Executors.newFixedThreadPool(2, r -> {
Thread t = new Thread(r, "vlm-worker");
t.setDaemon(true);
return t;
});
/**
* 异步检测,立即返回Future
* 适合流水线场景:可以在等待当前产品分析结果的同时,继续采集下一个产品
*/
public CompletableFuture<InspectionResult> inspectAsync(byte[] imageBytes,
String productId,
String productType) {
return CompletableFuture
// 第一阶段:快速检测
.supplyAsync(() -> detectionService.detect(imageBytes, productType), detectionPool)
// 第二阶段:条件性VLM分析(只对疑似缺陷做精细分析)
.thenComposeAsync(detection -> {
if (!detection.hasDefect()) {
// 无缺陷,直接通过
return CompletableFuture.completedFuture(
InspectionResult.pass(productId, detection.latencyMs()));
}
if (detection.isDegraded()) {
// 降级模式,标记人工检查
return CompletableFuture.completedFuture(
InspectionResult.manualReview(productId, "检测服务降级"));
}
// 高置信度缺陷,直接拦截
boolean highConfidenceDefect = detection.defects().stream()
.anyMatch(d -> d.confidence() >= 0.85f);
if (highConfidenceDefect) {
return CompletableFuture.completedFuture(
InspectionResult.reject(productId, detection));
}
// 疑似缺陷,用VLM精细分析
return CompletableFuture.supplyAsync(() -> {
DefectBox primaryDefect = detection.defects().get(0);
DefectVLMAnalyzer.DefectAnalysis analysis =
vlmAnalyzer.analyzeDefectRegion(imageBytes,
primaryDefect.box(), productType);
if (!analysis.isRealDefect()) {
return InspectionResult.pass(productId, detection.latencyMs());
}
return InspectionResult.fromVLMAnalysis(productId, detection, analysis);
}, vlmPool);
});
}
public record InspectionResult(
String productId, String decision, String defectType,
String severity, long latencyMs, String reason
) {
public static InspectionResult pass(String id, long latency) {
return new InspectionResult(id, "PASS", null, null, latency, null);
}
public static InspectionResult reject(String id, DefectDetectionService.DetectionResult d) {
return new InspectionResult(id, "REJECT",
d.defects().get(0).defectClass(), "严重", d.latencyMs(), "高置信度缺陷");
}
public static InspectionResult manualReview(String id, String reason) {
return new InspectionResult(id, "MANUAL_REVIEW", null, null, 0, reason);
}
public static InspectionResult fromVLMAnalysis(String id,
DefectDetectionService.DetectionResult d,
DefectVLMAnalyzer.DefectAnalysis a) {
String decision = switch (a.recommendation()) {
case "通过" -> "PASS";
case "降级" -> "DOWNGRADE";
default -> "REJECT";
};
return new InspectionResult(id, decision, a.defectType(), a.severity(),
d.latencyMs(), a.description());
}
}
}五、工程部署经验
实时性目标
- YOLOv8检测层(GPU推理):15-30ms/张,满足大多数传送带速度需求
- VLM分析层(异步):500-2000ms,由于只处理疑似缺陷(约5-10%),不成为瓶颈
- 端到端(无VLM):30-50ms;有VLM参与时:异步,不阻塞流水线
漏检率 vs 误检率的权衡
工业质检的核心原则:宁可误检10个,不能漏检1个。因此第一层检测阈值设低一些(0.3),第二层VLM再过滤误检。
在我们的项目里,最终效果:
- 漏检率:0.08%(相比人工的1.5%,改善了约18倍)
- 误检率:2.1%(相比只用YOLOv8的8%,VLM过滤降低了75%的误检)
