工业电流状态视觉检测系统
一、实际应用场景描述
在自动化生产线上,经常需要监测电机、继电器、电磁阀等设备的电流状态。传统方式依赖电流表或电流传感器,存在以下问题:
- 设备空间受限,无法安装电流表
- 强电磁干扰导致电流传感器数据不稳定
- 需要人工巡检,无法实现实时监测
- 成本较高,维护复杂
本系统通过工业相机采集设备指示灯、元件微变(如继电器吸合时的机械位移)的图像,利用计算机视觉技术判断电流通断状态,实现非接触式、低成本、高可靠性的电流状态监测。
二、引入痛点
- 物理限制:小型化设备无空间安装传统检测装置
- 环境干扰:工业现场强电磁场影响电子传感器精度
- 人工依赖:传统巡检效率低,易漏检
- 成本压力:高精度电流传感器价格昂贵
- 实时性不足:传统方法难以实现毫秒级响应
三、核心逻辑讲解
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ 图像采集 │────▶│ 预处理 │────▶│ 特征提取 │ │ (工业相机) │ │ (去噪/增强) │ │ (颜色/形状) │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ 结果输出 │◀────│ 状态判断 │◀────│ 模板匹配 │ │ (通/断状态) │ │ (阈值比较) │ │ (基准对比) │ └─────────────────┘ └─────────────────┘ └─────────────────┘
核心算法流程:
- 图像采集:通过OpenCV连接工业相机,实时捕获目标区域
- 预处理:灰度转换、高斯滤波、自适应阈值处理
- 特征提取:
- 指示灯颜色分析(RGB/HSV空间转换)
- 元件轮廓检测(边缘提取、形态学操作)
- 微变检测(帧间差分法)
- 状态判断:基于预设阈值和机器学习模型综合判断
- 结果输出:实时显示状态,支持MQTT/HTTP上报
四、代码模块化实现
项目结构
current_detection/ ├── main.py # 主程序入口 ├── config.py # 配置文件 ├── camera_handler.py # 相机控制模块 ├── image_processor.py # 图像处理模块 ├── state_analyzer.py # 状态分析模块 ├── utils.py # 工具函数 ├── models/ # 训练模型存储 ├── templates/ # 参考模板图像 ├── logs/ # 日志文件 ├── requirements.txt # 依赖包 └── README.md # 说明文档
- config.py - 配置文件
""" 工业电流视觉检测系统 - 配置文件 包含相机参数、检测阈值、路径配置等 """
import os
基础路径配置
BASE_DIR = os.path.dirname(os.path.abspath(file)) TEMPLATES_DIR = os.path.join(BASE_DIR, 'templates') MODELS_DIR = os.path.join(BASE_DIR, 'models') LOGS_DIR = os.path.join(BASE_DIR, 'logs')
创建必要目录
for dir_path in [TEMPLATES_DIR, MODELS_DIR, LOGS_DIR]: if not os.path.exists(dir_path): os.makedirs(dir_path)
相机配置
CAMERA_CONFIG = { 'device_id': 0, # 相机设备ID 'width': 1280, # 分辨率宽度 'height': 720, # 分辨率高度 'fps': 30, # 帧率 'exposure': -6, # 曝光值(对数刻度) 'gain': 1.0, # 增益 'white_balance': True # 自动白平衡 }
检测区域配置 (ROI: Region of Interest)
DETECTION_ROI = { 'indicator_light': { # 指示灯检测区域 'x': 100, # 左上角X坐标 'y': 50, # 左上角Y坐标 'width': 30, # 宽度 'height': 30 # 高度 }, 'relay_component': { # 继电器元件检测区域 'x': 200, 'y': 150, 'width': 80, 'height': 60 } }
颜色阈值配置 (HSV空间)
COLOR_THRESHOLDS = { 'red_on': { # 红色指示灯亮起状态 'lower_hsv': [0, 100, 100], 'upper_hsv': [10, 255, 255] }, 'red_off': { # 红色指示灯熄灭状态 'lower_hsv': [0, 0, 50], 'upper_hsv': [180, 50, 100] }, 'green_on': { # 绿色指示灯亮起状态 'lower_hsv': [35, 100, 100], 'upper_hsv': [85, 255, 255] }, 'green_off': { # 绿色指示灯熄灭状态 'lower_hsv': [35, 0, 50], 'upper_hsv': [85, 50, 100] } }
微变检测配置
MOTION_THRESHOLDS = { 'min_area': 50, # 最小变化区域面积(pixels) 'threshold_value': 25, # 帧间差分阈值 'min_contour_area': 30 # 最小轮廓面积 }
状态判断配置
STATE_CONFIG = { 'debounce_frames': 5, # 防抖帧数 'confidence_threshold': 0.8 # 置信度阈值 }
输出配置
OUTPUT_CONFIG = { 'show_window': True, # 是否显示实时窗口 'save_images': False, # 是否保存检测图像 'log_level': 'INFO' # 日志级别 }
- camera_handler.py - 相机控制模块
""" 工业电流视觉检测系统 - 相机控制模块 负责工业相机的初始化、图像采集和释放 支持USB相机和GigE Vision相机 """
import cv2 import numpy as np import logging from datetime import datetime from config import CAMERA_CONFIG, BASE_DIR, LOGS_DIR
配置日志
logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler(os.path.join(LOGS_DIR, 'camera.log')), logging.StreamHandler() ] ) logger = logging.getLogger('CameraHandler')
class CameraHandler: """ 工业相机处理器类 封装相机初始化、图像采集、参数设置等功能 """
def __init__(self, device_id=None):
"""
初始化相机处理器
Args:
device_id: 相机设备ID,默认使用配置文件中的设置
"""
self.device_id = device_id or CAMERA_CONFIG['device_id']
self.camera = None
self.is_initialized = False
self.frame_count = 0
self.fps_counter = 0
self.last_fps_time = datetime.now()
def initialize(self):
"""
初始化相机连接
Returns:
bool: 初始化成功返回True,失败返回False
"""
try:
# 尝试打开相机
self.camera = cv2.VideoCapture(self.device_id, cv2.CAP_DSHOW)
if not self.camera.isOpened():
logger.error(f"无法打开相机设备 {self.device_id}")
return False
# 设置相机参数
self._configure_camera()
self.is_initialized = True
logger.info(f"相机 {self.device_id} 初始化成功")
return True
except Exception as e:
logger.error(f"相机初始化异常: {str(e)}")
return False
def _configure_camera(self):
"""
配置相机参数
设置分辨率、帧率、曝光等参数
"""
config = CAMERA_CONFIG
# 设置分辨率
self.camera.set(cv2.CAP_PROP_FRAME_WIDTH, config['width'])
self.camera.set(cv2.CAP_PROP_FRAME_HEIGHT, config['height'])
# 设置帧率
self.camera.set(cv2.CAP_PROP_FPS, config['fps'])
# 设置曝光 (负值表示自动曝光,正值表示手动曝光时间)
self.camera.set(cv2.CAP_PROP_EXPOSURE, config['exposure'])
# 设置增益
self.camera.set(cv2.CAP_PROP_GAIN, config['gain'])
# 设置白平衡
if config['white_balance']:
self.camera.set(cv2.CAP_PROP_AUTO_WB, 1)
else:
self.camera.set(cv2.CAP_PROP_AUTO_WB, 0)
logger.debug("相机参数配置完成")
def capture_frame(self):
"""
捕获单帧图像
Returns:
numpy.ndarray: 捕获的图像帧,失败返回None
"""
if not self.is_initialized or self.camera is None:
logger.warning("相机未初始化")
return None
ret, frame = self.camera.read()
if ret:
self.frame_count += 1
self._update_fps()
return frame
else:
logger.warning("图像捕获失败")
return None
def _update_fps(self):
"""
更新FPS计数器
"""
self.fps_counter += 1
current_time = datetime.now()
time_diff = (current_time - self.last_fps_time).total_seconds()
if time_diff >= 1.0:
actual_fps = self.fps_counter / time_diff
logger.debug(f"当前FPS: {actual_fps:.2f}")
self.fps_counter = 0
self.last_fps_time = current_time
def get_resolution(self):
"""
获取当前相机分辨率
Returns:
tuple: (宽度, 高度)
"""
if self.camera and self.is_initialized:
width = int(self.camera.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(self.camera.get(cv2.CAP_PROP_FRAME_HEIGHT))
return (width, height)
return (0, 0)
def release(self):
"""
释放相机资源
"""
if self.camera is not None:
self.camera.release()
self.is_initialized = False
logger.info("相机资源已释放")
测试代码
if name == "main": handler = CameraHandler() if handler.initialize(): print("按 'q' 键退出测试") while True: frame = handler.capture_frame() if frame is not None: cv2.imshow("Camera Test", frame) if cv2.waitKey(1) & 0xFF == ord('q'): break handler.release() cv2.destroyAllWindows()
- image_processor.py - 图像处理模块
""" 工业电流视觉检测系统 - 图像处理模块 负责图像预处理、特征提取、颜色分析等功能 """
import cv2 import numpy as np import logging from config import COLOR_THRESHOLDS, DETECTION_ROI, MOTION_THRESHOLDS
配置日志
logger = logging.getLogger('ImageProcessor')
class ImageProcessor: """ 图像处理器类 封装所有图像处理算法 """
def __init__(self):
"""初始化图像处理器"""
self.prev_frame = None
self.roi_masks = {}
self._create_roi_masks((1280, 720)) # 默认分辨率
def _create_roi_masks(self, resolution):
"""
创建感兴趣区域(ROI)掩码
Args:
resolution: 图像分辨率 (width, height)
"""
width, height = resolution
for roi_name, roi_config in DETECTION_ROI.items():
mask = np.zeros((height, width), dtype=np.uint8)
x, y, w, h = roi_config['x'], roi_config['y'], \
roi_config['width'], roi_config['height']
mask[y:y+h, x:x+w] = 255
self.roi_masks[roi_name] = mask
logger.debug(f"创建ROI掩码: {roi_name}, 位置: ({x},{y}), 大小: {w}x{h}")
def preprocess(self, frame):
"""
图像预处理
包括灰度转换、降噪、增强等操作
Args:
frame: 原始图像帧
Returns:
numpy.ndarray: 预处理后的灰度图像
"""
# 转换为灰度图
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 高斯滤波降噪
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
# CLAHE自适应直方图均衡化,增强对比度
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
enhanced = clahe.apply(blurred)
return enhanced
def extract_color_features(self, frame, color_type='red'):
"""
提取颜色特征
将图像转换到HSV颜色空间,根据阈值提取指定颜色区域
Args:
frame: 原始彩色图像
color_type: 颜色类型 ('red', 'green', 'blue')
Returns:
dict: 包含颜色掩码、面积、质心等信息
"""
# 转换到HSV颜色空间
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
# 获取对应颜色的阈值
on_key = f"{color_type}_on"
off_key = f"{color_type}_off"
if on_key in COLOR_THRESHOLDS:
lower = np.array(COLOR_THRESHOLDS[on_key]['lower_hsv'])
upper = np.array(COLOR_THRESHOLDS[on_key]['upper_hsv'])
mask_on = cv2.inRange(hsv, lower, upper)
lower = np.array(COLOR_THRESHOLDS[off_key]['lower_hsv'])
upper = np.array(COLOR_THRESHOLDS[off_key]['upper_hsv'])
mask_off = cv2.inRange(hsv, lower, upper)
# 合并掩码
combined_mask = cv2.bitwise_or(mask_on, mask_off)
# 计算特征
contours, _ = cv2.findContours(combined_mask, cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
total_area = sum(cv2.contourArea(c) for c in contours)
centroid = None
if contours:
# 计算所有轮廓的质心
moments = [cv2.moments(c) for c in contours]
valid_moments = [m for m in moments if m['m00'] != 0]
if valid_moments:
cx = sum(m['m10']/m['m00'] for m in valid_moments) / len(valid_moments)
cy = sum(m['m01']/m['m00'] for m in valid_moments) / len(valid_moments)
centroid = (int(cx), int(cy))
return {
'mask': combined_mask,
'area': total_area,
'centroid': centroid,
'contours': contours
}
return {'mask': None, 'area': 0, 'centroid': None, 'contours': []}
def detect_motion(self, frame):
"""
微变检测
使用帧间差分法检测元件微小变化(如继电器吸合)
Args:
frame: 当前图像帧
Returns:
dict: 运动检测结果
"""
if self.prev_frame is None:
self.prev_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
return {'motion_detected': False, 'area': 0, 'contours': []}
# 预处理当前帧
current_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
current_gray = cv2.GaussianBlur(current_gray, (5, 5), 0)
# 帧间差分
diff = cv2.absdiff(self.prev_frame, current_gray)
# 二值化
_, thresh = cv2.threshold(diff, MOTION_THRESHOLDS['threshold_value'],
255, cv2.THRESH_BINARY)
# 形态学操作,去除噪声
kernel = np.ones((3, 3), np.uint8)
thresh = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
# 查找轮廓
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
# 过滤小面积变化
valid_contours = [c for c in contours
if cv2.contourArea(c) > MOTION_THRESHOLDS['min_contour_area']]
total_area = sum(cv2.contourArea(c) for c in valid_contours)
# 更新前一帧
self.prev_frame = current_gray
return {
'motion_detected': total_area > MOTION_THRESHOLDS['min_area'],
'area': total_area,
'contours': valid_contours
}
def analyze_indicator(self, frame, indicator_type='red'):
"""
分析指示灯状态
Args:
frame: 原始图像
indicator_type: 指示灯类型 ('red', 'green')
Returns:
dict: 指示灯分析结果
"""
# 提取颜色特征
color_features = self.extract_color_features(frame, indicator_type)
# 计算平均亮度
mask = color_features['mask']
if mask is not None:
mean_brightness = cv2.mean(frame, mask=mask)[0]
else:
mean_brightness = 0
# 判断状态
area = color_features['area']
is_on = area > 100 # 面积阈值
return {
'is_on': is_on,
'brightness': mean_brightness,
'area': area,
'centroid': color_features['centroid']
}
def crop_roi(self, frame, roi_name):
"""
裁剪感兴趣区域
Args:
frame: 原始图像
roi_name: ROI名称
Returns:
numpy.ndarray: 裁剪后的图像区域
"""
if roi_name in DETECTION_ROI:
config = DETECTION_ROI[roi_name]
x, y, w, h = config['x'], config['y'], config['width'], config['height']
return frame[y:y+h, x:x+w]
return frame
def draw_detection_results(self, frame, results):
"""
在图像上绘制检测结果
Args:
frame: 原始图像
results: 检测结果字典
Returns:
numpy.ndarray: 绘制了结果的图像
"""
output = frame.copy()
# 绘制ROI区域
for roi_name, config in DETECTION_ROI.items():
x, y, w, h = config['x'], config['y'], config['width'], config['height']
color = (0, 255, 0) if 'indicator' in roi_name else (255, 0, 0)
cv2.rectangle(output, (x, y), (x+w, y+h), color, 2)
cv2.putText(output, roi_name, (x, y-10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 1)
# 绘制运动检测轮廓
if 'motion' in results and results['motion']['contours']:
for contour in results['motion']['contours']:
cv2.drawContours(output, [contour], -1, (0, 0, 255), 2)
# 绘制指示灯状态
if 'indicator' in results:
status = "ON" if results['indicator']['is_on'] else "OFF"
color = (0, 255, 0) if results['indicator']['is_on'] else (0, 0, 255)
position = (results['indicator'].get('centroid', (50, 50)))
cv2.putText(output, f"Indicator: {status}", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 1, color, 2)
return output
测试代码
if name == "main": processor = ImageProcessor() test_image = np.random.randint(0, 255, (720, 1280, 3), dtype=np.uint8)
# 测试颜色特征提取
red_features = processor.extract_color_features(test_image, 'red')
print(f"红色特征面积: {red_features['area']}")
# 测试指示灯分析
indicator_result = processor.analyze_indicator(test_image, 'red')
print(f"指示灯状态: {'亮' if indicator_result['is_on'] else '灭'}")
4. state_analyzer.py - 状态分析模块
""" 工业电流视觉检测系统 - 状态分析模块 负责综合各种检测结果,判断电流通断状态 """
import cv2 import numpy as np import logging from collections import deque from config import STATE_CONFIG
配置日志
logger = logging.getLogger('StateAnalyzer')
class StateAnalyzer: """ 状态分析器类 结合多种检测方法进行综合状态判断 """
def __init__(self):
"""初始化状态分析器"""
self.debounce_queue = deque(maxlen=STATE_CONFIG['debounce_frames'])
self.current_state = None
self.state_history = []
self.confidence_scores = []
def analyze(self, indicator_result, motion_result, relay_result=None):
"""
综合分析所有检测结果,判断电流状态
Args:
indicator_result: 指示灯检测结果
motion_result: 微变检测结果
relay_result: 继电器检测结果(可选)
Returns:
dict: 综合状态分析结果
"""
# 收集各检测方法的判断
evidence = []
# 1. 指示灯证据
if indicator_result:
indicator_state = 1 if indicator_result['is_on'] else 0
evidence.append({
'source': 'indicator',
'state': indicator_state,
'confidence': 0.7 if indicator_result['brightness'] > 100 else 0.5,
'raw_data': indicator_result
})
# 2. 微变检测证据
if motion_result:
motion_state = 1 if motion_result['motion_detected'] else 0
evidence.append({
'source': 'motion',
'state': motion_state,
'confidence': 0.8 if motion_result['area'] > 200 else 0.4,
'raw_data': motion_result
})
# 3. 继电器检测证据(如果有)
if relay_result:
relay_state = 1 if relay_result['activated'] else 0
evidence.append({
'source': 'relay',
'state': relay_state,
'confidence': 0.9,
'raw_data': relay_result
})
# 如果没有足够证据,返回未知状态
if len(evidence) < 1:
return {
'state': 'UNKNOWN',
'confidence': 0.0,
'evidence': evidence,
'reason': 'Insufficient evidence'
}
# 计算加权投票
weighted_votes = 0
total_confidence = 0
for ev in evidence:
weight = ev['confidence'] * ev['state']
weighted_votes += weight
total_confidence += ev['confidence']
# 计算综合置信度
avg_confidence = total_confidence / len(evidence) if evidence else 0
combined_confidence = min(avg_confidence * (weighted_votes / len(evidence) + 1) / 2, 1.0)
# 判断状态
raw_state = 1 if weighted_votes > 0 else 0
# 防抖处理
self.debounce_queue.append(raw_state)
stable_state = max(set(self.debounce_queue), key=self.debounce_queue.count)
# 确定状态标签
if stable_state == 1:
state_label = 'CURRENT_ON'
else:
state_label = 'CURRENT_OFF'
# 记录历史
result = {
'state': state_label,
'raw_state': raw_state,
'stable_state': stable_state,
'confidence': combined_confidence,
'evidence': evidence,
'timestamp': cv2.getTickCount()
}
self.state_history.append(result)
self.confidence_scores.append(combined_confidence)
# 日志记录
logger.info(f"状态分析: {state_label}, 置信度: {combined_confidence:.2f}")
利用AI解决实际问题,如果你觉得这个工具好用。欢迎关注长安牧笛!