视觉判断电流通断,看指示灯/元件微变,替代电流表。

1 阅读10分钟

工业电流状态视觉检测系统

一、实际应用场景描述

在自动化生产线上,经常需要监测电机、继电器、电磁阀等设备的电流状态。传统方式依赖电流表或电流传感器,存在以下问题:

  • 设备空间受限,无法安装电流表
  • 强电磁干扰导致电流传感器数据不稳定
  • 需要人工巡检,无法实现实时监测
  • 成本较高,维护复杂

本系统通过工业相机采集设备指示灯、元件微变(如继电器吸合时的机械位移)的图像,利用计算机视觉技术判断电流通断状态,实现非接触式、低成本、高可靠性的电流状态监测。

二、引入痛点

  1. 物理限制:小型化设备无空间安装传统检测装置
  2. 环境干扰:工业现场强电磁场影响电子传感器精度
  3. 人工依赖:传统巡检效率低,易漏检
  4. 成本压力:高精度电流传感器价格昂贵
  5. 实时性不足:传统方法难以实现毫秒级响应

三、核心逻辑讲解

┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ 图像采集 │────▶│ 预处理 │────▶│ 特征提取 │ │ (工业相机) │ │ (去噪/增强) │ │ (颜色/形状) │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ 结果输出 │◀────│ 状态判断 │◀────│ 模板匹配 │ │ (通/断状态) │ │ (阈值比较) │ │ (基准对比) │ └─────────────────┘ └─────────────────┘ └─────────────────┘

核心算法流程:

  1. 图像采集:通过OpenCV连接工业相机,实时捕获目标区域
  2. 预处理:灰度转换、高斯滤波、自适应阈值处理
  3. 特征提取:
    • 指示灯颜色分析(RGB/HSV空间转换)
    • 元件轮廓检测(边缘提取、形态学操作)
    • 微变检测(帧间差分法)
  4. 状态判断:基于预设阈值和机器学习模型综合判断
  5. 结果输出:实时显示状态,支持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 # 说明文档

  1. 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' # 日志级别 }

  1. 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()

  1. 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解决实际问题,如果你觉得这个工具好用。欢迎关注长安牧笛!