1007 lines
32 KiB
Python
1007 lines
32 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""
|
||
海康相机管理模块
|
||
|
||
本模块提供了完整的海康相机管理功能,包括:
|
||
1. 设备枚举和选择
|
||
2. 相机打开和关闭
|
||
3. 图像采集和显示
|
||
4. 曝光、增益等参数控制
|
||
5. 异常处理和重连机制
|
||
6. 相机参数控制界面
|
||
|
||
前置条件:
|
||
1. 已安装海康 MVS SDK
|
||
2. 已配置 Python 环境
|
||
3. 相机已正确连接
|
||
"""
|
||
|
||
import sys
|
||
import ctypes
|
||
import threading
|
||
import time
|
||
import os
|
||
import tkinter as tk
|
||
from tkinter import ttk
|
||
|
||
# 添加SDK路径
|
||
sys.path.append('Python/MvImport')
|
||
from MvCameraControl_class import *
|
||
from MvErrorDefine_const import *
|
||
from CameraParams_header import *
|
||
import cv2
|
||
import numpy as np
|
||
|
||
# 枚举值定义
|
||
MV_EXPOSURE_AUTO_MODE_OFF = 0
|
||
MV_GAIN_AUTO_MODE_OFF = 0
|
||
MV_BALANCE_WHITE_AUTO_OFF = 0
|
||
|
||
class Camera:
|
||
"""
|
||
海康相机管理类
|
||
|
||
功能:
|
||
- 设备枚举和选择
|
||
- 相机打开和关闭
|
||
- 图像采集和显示
|
||
- 曝光、增益等参数控制
|
||
- 异常处理和重连机制
|
||
"""
|
||
|
||
def __init__(self):
|
||
"""
|
||
初始化相机管理器
|
||
"""
|
||
# 初始化SDK
|
||
MvCamera.MV_CC_Initialize()
|
||
|
||
self.cam = None # 相机实例
|
||
self.device_list = MV_CC_DEVICE_INFO_LIST() # 设备列表
|
||
self.is_open = False # 相机是否打开
|
||
self.is_grabbing = False # 是否正在取流
|
||
self.b_exit = False # 退出标志
|
||
self.work_thread = None # 工作线程
|
||
self.current_exp = 5000.0 # 当前曝光时间(微秒)
|
||
self.current_gain = 0.0 # 当前增益(dB)
|
||
self.current_fps = 30.0 # 当前帧率
|
||
self.last_frame = None # 最后一帧图像
|
||
|
||
def to_hex_str(self, num):
|
||
"""
|
||
将返回的错误码转换为十六进制显示
|
||
|
||
Args:
|
||
num: 错误码
|
||
|
||
Returns:
|
||
str: 十六进制字符串
|
||
"""
|
||
cha_dic = {10: 'a', 11: 'b', 12: 'c', 13: 'd', 14: 'e', 15: 'f'}
|
||
hex_str = ""
|
||
if num < 0:
|
||
num = num + 2 ** 32
|
||
while num >= 16:
|
||
digit = num % 16
|
||
hex_str = cha_dic.get(digit, str(digit)) + hex_str
|
||
num //= 16
|
||
hex_str = cha_dic.get(num, str(num)) + hex_str
|
||
return hex_str
|
||
|
||
def decoding_char(self, ctypes_char_array):
|
||
"""
|
||
安全地从 ctypes 字符数组中解码出字符串
|
||
|
||
Args:
|
||
ctypes_char_array: ctypes字符数组
|
||
|
||
Returns:
|
||
str: 解码后的字符串
|
||
"""
|
||
byte_str = memoryview(ctypes_char_array).tobytes()
|
||
|
||
# 在第一个空字符处截断
|
||
null_index = byte_str.find(b'\x00')
|
||
if null_index != -1:
|
||
byte_str = byte_str[:null_index]
|
||
|
||
# 多编码尝试解码
|
||
for encoding in ['gbk', 'utf-8', 'latin-1']:
|
||
try:
|
||
return byte_str.decode(encoding)
|
||
except UnicodeDecodeError:
|
||
continue
|
||
|
||
# 如果所有编码都失败,使用替换策略
|
||
return byte_str.decode('latin-1', errors='replace')
|
||
|
||
def enum_devices(self):
|
||
"""
|
||
枚举相机设备
|
||
|
||
Returns:
|
||
int: 设备数量
|
||
"""
|
||
self.device_list = MV_CC_DEVICE_INFO_LIST()
|
||
n_layer_type = (MV_GIGE_DEVICE | MV_USB_DEVICE)
|
||
ret = MvCamera.MV_CC_EnumDevices(n_layer_type, self.device_list)
|
||
if ret != 0:
|
||
print(f"枚举设备失败! ret = :{self.to_hex_str(ret)}")
|
||
return ret
|
||
|
||
if self.device_list.nDeviceNum == 0:
|
||
print("未找到设备")
|
||
return ret
|
||
print(f"找到 {self.device_list.nDeviceNum} 台设备!")
|
||
|
||
dev_list = []
|
||
target_model_found = False
|
||
|
||
for i in range(0, self.device_list.nDeviceNum):
|
||
mvcc_dev_info = cast(self.device_list.pDeviceInfo[i], POINTER(MV_CC_DEVICE_INFO)).contents
|
||
if mvcc_dev_info.nTLayerType == MV_GIGE_DEVICE:
|
||
print(f"\ngige device: [{i}]")
|
||
user_defined_name = self.decoding_char(mvcc_dev_info.SpecialInfo.stGigEInfo.chUserDefinedName)
|
||
model_name = self.decoding_char(mvcc_dev_info.SpecialInfo.stGigEInfo.chModelName)
|
||
print(f"device user define name: {user_defined_name}")
|
||
print(f"device model name: {model_name}")
|
||
|
||
# 检查是否为目标型号
|
||
if "MV-CS016-10UC" in model_name:
|
||
print("✓ 找到目标相机型号: MV-CS016-10UC")
|
||
target_model_found = True
|
||
|
||
nip1 = ((mvcc_dev_info.SpecialInfo.stGigEInfo.nCurrentIp & 0xff000000) >> 24)
|
||
nip2 = ((mvcc_dev_info.SpecialInfo.stGigEInfo.nCurrentIp & 0x00ff0000) >> 16)
|
||
nip3 = ((mvcc_dev_info.SpecialInfo.stGigEInfo.nCurrentIp & 0x0000ff00) >> 8)
|
||
nip4 = (mvcc_dev_info.SpecialInfo.stGigEInfo.nCurrentIp & 0x000000ff)
|
||
print(f"current ip: {nip1}.{nip2}.{nip3}.{nip4} ")
|
||
dev_list.append(f"[{i}]GigE: {user_defined_name} {model_name}({nip1}.{nip2}.{nip3}.{nip4})")
|
||
elif mvcc_dev_info.nTLayerType == MV_USB_DEVICE:
|
||
print(f"\nu3v device: [{i}]")
|
||
user_defined_name = self.decoding_char(mvcc_dev_info.SpecialInfo.stUsb3VInfo.chUserDefinedName)
|
||
model_name = self.decoding_char(mvcc_dev_info.SpecialInfo.stUsb3VInfo.chModelName)
|
||
print(f"device user define name: {user_defined_name}")
|
||
print(f"device model name: {model_name}")
|
||
|
||
# 检查是否为目标型号
|
||
if "MV-CS016-10UC" in model_name:
|
||
print("✓ 找到目标相机型号: MV-CS016-10UC")
|
||
target_model_found = True
|
||
|
||
str_serial_number = ""
|
||
for per in mvcc_dev_info.SpecialInfo.stUsb3VInfo.chSerialNumber:
|
||
if per == 0:
|
||
break
|
||
str_serial_number = str_serial_number + chr(per)
|
||
print(f"user serial number: {str_serial_number}")
|
||
dev_list.append(f"[{i}]USB: {user_defined_name} {model_name}({str_serial_number})")
|
||
|
||
# 打印设备列表
|
||
print("\n设备列表:")
|
||
for i, dev in enumerate(dev_list):
|
||
print(f"{i}: {dev}")
|
||
|
||
# 提示用户是否找到目标型号
|
||
if not target_model_found:
|
||
print("\n⚠️ 警告: 未找到目标相机型号 MV-CS016-10UC")
|
||
print("⚠️ 系统仍将尝试使用找到的设备,但可能无法正常工作")
|
||
|
||
return self.device_list.nDeviceNum
|
||
|
||
def open_device(self, n_index):
|
||
"""
|
||
打开相机设备
|
||
|
||
Args:
|
||
n_index: 设备索引
|
||
|
||
Returns:
|
||
int: 错误码
|
||
"""
|
||
if self.is_open:
|
||
print("相机已经打开!")
|
||
return MV_E_CALLORDER
|
||
|
||
if n_index < 0 or n_index >= self.device_list.nDeviceNum:
|
||
print("请选择有效的相机索引!")
|
||
return MV_E_CALLORDER
|
||
|
||
# 选择设备并创建句柄
|
||
st_device_list = cast(self.device_list.pDeviceInfo[n_index], POINTER(MV_CC_DEVICE_INFO)).contents
|
||
self.cam = MvCamera()
|
||
|
||
# 句柄占用保护
|
||
ret = self.cam.MV_CC_CreateHandle(st_device_list)
|
||
if ret != 0:
|
||
print(f"创建设备句柄失败! ret = {self.to_hex_str(ret)}")
|
||
# 尝试清理可能的占用
|
||
try:
|
||
self.cam.MV_CC_DestroyHandle()
|
||
except:
|
||
pass
|
||
return ret
|
||
|
||
# 打开设备
|
||
ret = self.cam.MV_CC_OpenDevice()
|
||
if ret != 0:
|
||
print(f"打开设备失败! ret = {self.to_hex_str(ret)}")
|
||
try:
|
||
self.cam.MV_CC_DestroyHandle()
|
||
except:
|
||
pass
|
||
return ret
|
||
|
||
print("设备打开成功!")
|
||
self.is_open = True
|
||
|
||
# 设置触发模式为off
|
||
ret = self.cam.MV_CC_SetEnumValue("TriggerMode", MV_TRIGGER_MODE_OFF)
|
||
if ret != 0:
|
||
print(f"设置触发模式失败! ret = {self.to_hex_str(ret)}")
|
||
|
||
# 设置像素格式为 BGR8
|
||
ret = self.cam.MV_CC_SetEnumValue("PixelFormat", PixelType_Gvsp_BGR8_Packed)
|
||
if ret != 0:
|
||
print(f"设置像素格式失败! ret = {self.to_hex_str(ret)}")
|
||
|
||
# 设置连续采集模式
|
||
ret = self.cam.MV_CC_SetEnumValue("AcquisitionMode", 2) # Continuous
|
||
if ret != 0:
|
||
print(f"设置连续采集模式失败! ret = {self.to_hex_str(ret)}")
|
||
|
||
# 防过曝设置
|
||
self.setup_anti_overexposure()
|
||
|
||
return MV_OK
|
||
|
||
def setup_anti_overexposure(self):
|
||
"""
|
||
设置防过曝参数(点阵灯盘专用)
|
||
"""
|
||
# 关闭自动曝光,设置初始曝光时间(微秒)
|
||
ret = self.cam.MV_CC_SetEnumValue("ExposureAuto", MV_EXPOSURE_AUTO_MODE_OFF)
|
||
if ret != 0:
|
||
print(f"设置曝光自动模式失败! ret = {self.to_hex_str(ret)}")
|
||
|
||
# 点阵灯盘初始曝光时间:3000-5000微秒
|
||
initial_exp = 4000.0
|
||
ret = self.cam.MV_CC_SetFloatValue("ExposureTime", initial_exp)
|
||
if ret != 0:
|
||
print(f"设置曝光时间失败! ret = {self.to_hex_str(ret)}")
|
||
else:
|
||
self.current_exp = initial_exp
|
||
print(f"初始曝光时间设置为: {initial_exp}μs")
|
||
|
||
# 关闭自动增益
|
||
ret = self.cam.MV_CC_SetEnumValue("GainAuto", MV_GAIN_AUTO_MODE_OFF)
|
||
if ret != 0:
|
||
print(f"设置增益自动模式失败! ret = {self.to_hex_str(ret)}")
|
||
|
||
ret = self.cam.MV_CC_SetFloatValue("Gain", 0.0)
|
||
if ret != 0:
|
||
print(f"设置增益失败! ret = {self.to_hex_str(ret)}")
|
||
else:
|
||
self.current_gain = 0.0
|
||
print("初始增益设置为: 0dB")
|
||
|
||
# 关闭自动白平衡(识别绿色LED时必需)
|
||
ret = self.cam.MV_CC_SetEnumValue("BalanceWhiteAuto", MV_BALANCE_WHITE_AUTO_OFF)
|
||
if ret != 0:
|
||
print(f"设置白平衡自动模式失败! ret = {self.to_hex_str(ret)}")
|
||
else:
|
||
print("自动白平衡已关闭")
|
||
|
||
def set_exposure_time(self, microseconds):
|
||
"""
|
||
设置曝光时间(微秒)
|
||
|
||
Args:
|
||
microseconds: 曝光时间(微秒)
|
||
|
||
Returns:
|
||
int: 错误码
|
||
"""
|
||
if not self.is_open:
|
||
return MV_E_CALLORDER
|
||
|
||
ret = self.cam.MV_CC_SetFloatValue("ExposureTime", microseconds)
|
||
if ret != 0:
|
||
print(f"设置曝光时间失败! ret = {self.to_hex_str(ret)}")
|
||
return ret
|
||
|
||
self.current_exp = microseconds
|
||
return MV_OK
|
||
|
||
def set_gain(self, db):
|
||
"""
|
||
设置增益(dB)
|
||
|
||
Args:
|
||
db: 增益值(dB)
|
||
|
||
Returns:
|
||
int: 错误码
|
||
"""
|
||
if not self.is_open:
|
||
return MV_E_CALLORDER
|
||
|
||
ret = self.cam.MV_CC_SetFloatValue("Gain", db)
|
||
if ret != 0:
|
||
print(f"设置增益失败! ret = {self.to_hex_str(ret)}")
|
||
return ret
|
||
|
||
self.current_gain = db
|
||
return MV_OK
|
||
|
||
def set_frame_rate(self, fps):
|
||
"""
|
||
设置帧率
|
||
|
||
Args:
|
||
fps: 帧率值
|
||
|
||
Returns:
|
||
int: 错误码
|
||
"""
|
||
if not self.is_open:
|
||
return MV_E_CALLORDER
|
||
|
||
ret = self.cam.MV_CC_SetFloatValue("AcquisitionFrameRate", fps)
|
||
if ret != 0:
|
||
print(f"设置帧率失败! ret = {self.to_hex_str(ret)}")
|
||
return ret
|
||
|
||
self.current_fps = fps
|
||
return MV_OK
|
||
|
||
def set_roi(self, x, y, w, h):
|
||
"""
|
||
设置ROI
|
||
|
||
Args:
|
||
x: X坐标
|
||
y: Y坐标
|
||
w: 宽度
|
||
h: 高度
|
||
|
||
Returns:
|
||
int: 错误码
|
||
"""
|
||
if not self.is_open:
|
||
return MV_E_CALLORDER
|
||
|
||
# 停止取流
|
||
if self.is_grabbing:
|
||
self.stop_grabbing()
|
||
|
||
# 设置ROI参数
|
||
ret = self.cam.MV_CC_SetIntValue("OffsetX", x)
|
||
if ret != 0:
|
||
print(f"设置OffsetX失败! ret = {self.to_hex_str(ret)}")
|
||
return ret
|
||
|
||
ret = self.cam.MV_CC_SetIntValue("OffsetY", y)
|
||
if ret != 0:
|
||
print(f"设置OffsetY失败! ret = {self.to_hex_str(ret)}")
|
||
return ret
|
||
|
||
ret = self.cam.MV_CC_SetIntValue("Width", w)
|
||
if ret != 0:
|
||
print(f"设置Width失败! ret = {self.to_hex_str(ret)}")
|
||
return ret
|
||
|
||
ret = self.cam.MV_CC_SetIntValue("Height", h)
|
||
if ret != 0:
|
||
print(f"设置Height失败! ret = {self.to_hex_str(ret)}")
|
||
return ret
|
||
|
||
# 重新开始取流
|
||
if self.is_grabbing:
|
||
self.start_grabbing()
|
||
|
||
return MV_OK
|
||
|
||
def start_grabbing(self):
|
||
"""
|
||
开始取流
|
||
|
||
Returns:
|
||
int: 错误码
|
||
"""
|
||
if not self.is_open:
|
||
print("相机未打开!")
|
||
return MV_E_CALLORDER
|
||
|
||
if self.is_grabbing:
|
||
print("已经开始取流!")
|
||
return MV_E_CALLORDER
|
||
|
||
self.b_exit = False
|
||
ret = self.cam.MV_CC_StartGrabbing()
|
||
if ret != 0:
|
||
print(f"开始取流失败! ret = {self.to_hex_str(ret)}")
|
||
return ret
|
||
|
||
self.is_grabbing = True
|
||
print("开始取流成功!")
|
||
|
||
# 启动取图线程
|
||
self.work_thread = threading.Thread(target=self.work_thread_func)
|
||
self.work_thread.daemon = True
|
||
self.work_thread.start()
|
||
|
||
return MV_OK
|
||
|
||
def stop_grabbing(self):
|
||
"""
|
||
停止取流
|
||
|
||
Returns:
|
||
int: 错误码
|
||
"""
|
||
if not self.is_open:
|
||
print("相机未打开!")
|
||
return MV_E_CALLORDER
|
||
|
||
if not self.is_grabbing:
|
||
print("未开始取流!")
|
||
return MV_E_CALLORDER
|
||
|
||
self.b_exit = True
|
||
time.sleep(0.1)
|
||
|
||
ret = self.cam.MV_CC_StopGrabbing()
|
||
if ret != 0:
|
||
print(f"停止取流失败! ret = {self.to_hex_str(ret)}")
|
||
return ret
|
||
|
||
self.is_grabbing = False
|
||
print("停止取流成功!")
|
||
|
||
return MV_OK
|
||
|
||
def close_device(self):
|
||
"""
|
||
关闭相机设备
|
||
"""
|
||
if self.is_grabbing:
|
||
self.stop_grabbing()
|
||
|
||
if self.is_open:
|
||
ret = self.cam.MV_CC_CloseDevice()
|
||
if ret != 0:
|
||
print(f"关闭设备失败! ret = {self.to_hex_str(ret)}")
|
||
|
||
# 销毁句柄
|
||
self.cam.MV_CC_DestroyHandle()
|
||
self.is_open = False
|
||
print("设备关闭成功!")
|
||
|
||
def work_thread_func(self):
|
||
"""
|
||
取图线程函数
|
||
"""
|
||
st_out_frame = MV_FRAME_OUT()
|
||
memset(byref(st_out_frame), 0, sizeof(st_out_frame))
|
||
|
||
timeout_count = 0 # 超时计数器
|
||
max_timeout_count = 3 # 最大超时次数
|
||
|
||
while not self.b_exit:
|
||
# 获取图像
|
||
ret = self.cam.MV_CC_GetImageBuffer(st_out_frame, 1000)
|
||
|
||
if ret == 0:
|
||
timeout_count = 0 # 重置超时计数器
|
||
# 打印图像信息
|
||
print(f"获取一帧图像: 宽度[{st_out_frame.stFrameInfo.nWidth}], 高度[{st_out_frame.stFrameInfo.nHeight}], 帧数[{st_out_frame.stFrameInfo.nFrameNum}]")
|
||
|
||
# 转换为 OpenCV 格式
|
||
try:
|
||
# 计算数据大小
|
||
data_size = st_out_frame.stFrameInfo.nWidth * st_out_frame.stFrameInfo.nHeight * 3
|
||
|
||
# 从缓冲区复制数据
|
||
frame_data = np.ctypeslib.as_array(st_out_frame.pBufAddr, shape=(data_size,))
|
||
frame = frame_data.reshape((st_out_frame.stFrameInfo.nHeight, st_out_frame.stFrameInfo.nWidth, 3))
|
||
|
||
# 保存最后一帧图像
|
||
self.last_frame = frame
|
||
|
||
# 显示图像
|
||
cv2.imshow("Camera", frame)
|
||
key = cv2.waitKey(1) & 0xFF
|
||
if key == ord('q'):
|
||
self.b_exit = True
|
||
break
|
||
except Exception as e:
|
||
print(f"处理图像失败: {e}")
|
||
finally:
|
||
# 释放缓存
|
||
try:
|
||
self.cam.MV_CC_FreeImageBuffer(st_out_frame)
|
||
except Exception as e:
|
||
print(f"释放图像缓存失败: {e}")
|
||
else:
|
||
print(f"获取图像失败! ret = {self.to_hex_str(ret)}")
|
||
timeout_count += 1
|
||
time.sleep(0.1)
|
||
|
||
# 连续超时3次,触发重连
|
||
if timeout_count >= max_timeout_count:
|
||
print("连续超时,尝试重连相机...")
|
||
self._reconnect_camera()
|
||
timeout_count = 0
|
||
|
||
cv2.destroyAllWindows()
|
||
|
||
def _reconnect_camera(self):
|
||
"""
|
||
重连相机
|
||
"""
|
||
try:
|
||
# 停止取流
|
||
if self.is_grabbing:
|
||
print("停止取流...")
|
||
self.stop_grabbing()
|
||
|
||
# 关闭设备
|
||
print("关闭设备...")
|
||
self.close_device()
|
||
|
||
# 重新枚举设备
|
||
print("重新枚举设备...")
|
||
device_count = self.enum_devices()
|
||
if device_count == 0:
|
||
print("重连失败:未找到设备")
|
||
return
|
||
|
||
# 重新打开设备(默认使用索引0)
|
||
print("重新打开设备...")
|
||
ret = self.open_device(0)
|
||
if ret != 0:
|
||
print(f"重连失败:打开设备失败! ret = {self.to_hex_str(ret)}")
|
||
return
|
||
|
||
# 重新开始取流
|
||
print("重新开始取流...")
|
||
ret = self.start_grabbing()
|
||
if ret != 0:
|
||
print(f"重连失败:开始取流失败! ret = {self.to_hex_str(ret)}")
|
||
return
|
||
|
||
print("相机重连成功!")
|
||
except Exception as e:
|
||
print(f"重连相机失败: {e}")
|
||
|
||
def get_frame(self):
|
||
"""
|
||
获取当前帧图像
|
||
|
||
Returns:
|
||
tuple: (ret, frame)
|
||
"""
|
||
if not self.is_open or not self.is_grabbing:
|
||
return False, None
|
||
|
||
return True, self.last_frame
|
||
|
||
def __del__(self):
|
||
"""
|
||
析构函数,确保资源释放
|
||
"""
|
||
self.close_device()
|
||
# 反初始化SDK
|
||
try:
|
||
MvCamera.MV_CC_Finalize()
|
||
print("SDK反初始化成功")
|
||
except Exception as e:
|
||
print(f"SDK反初始化失败: {e}")
|
||
|
||
|
||
# SDK安装指南
|
||
"""
|
||
海康 MVS SDK 安装指南
|
||
|
||
1. 下载 MVS SDK
|
||
- 访问海康官网:https://www.hikvision.com/cn/
|
||
- 搜索 "MVS SDK" 或 "机器视觉 SDK"
|
||
- 下载对应操作系统的最新版本
|
||
|
||
2. 安装 SDK
|
||
- 运行安装程序,按照默认设置安装
|
||
- 安装路径建议使用默认路径
|
||
|
||
3. 配置环境变量
|
||
- 将 SDK 安装目录下的 bin 文件夹添加到系统 PATH 环境变量
|
||
- 例如:C:\\Program Files\\MVS\\Runtime\\bin\\win64_x64
|
||
|
||
4. 配置 Python 环境
|
||
- 复制 SDK 安装目录下的 Python 绑定文件
|
||
- 从:C:\\Program Files\\MVS\\Development\\Samples\\Python\\MvImport
|
||
- 到:项目目录\\Python\\MvImport
|
||
|
||
5. 安装依赖
|
||
- pip install opencv-python
|
||
- pip install numpy
|
||
|
||
6. 测试安装
|
||
- 运行本脚本:python camera.py
|
||
- 查看是否能成功枚举设备
|
||
|
||
7. 常见问题
|
||
- 找不到 MvCameraControl_class:检查 MvImport 目录是否正确
|
||
- 相机无法打开:检查相机是否被其他程序占用
|
||
- 图像获取失败:检查相机连接和参数设置
|
||
"""
|
||
|
||
|
||
class CameraControlPanel:
|
||
"""
|
||
相机控制面板类
|
||
|
||
功能:
|
||
- 相机初始化和状态显示
|
||
- 曝光时间、增益、帧率等参数调节
|
||
- 相机控制(打开、关闭、开始取流、停止取流)
|
||
- 操作日志显示
|
||
"""
|
||
|
||
def __init__(self, root, camera):
|
||
"""
|
||
初始化相机控制面板
|
||
|
||
Args:
|
||
root: 父窗口
|
||
camera: 相机实例
|
||
"""
|
||
self.root = root
|
||
self.camera = camera
|
||
self.root.title("相机控制面板")
|
||
self.root.geometry("500x400")
|
||
self.root.resizable(True, True)
|
||
|
||
# 创建主框架
|
||
self.main_frame = ttk.Frame(self.root, padding="10")
|
||
self.main_frame.pack(fill=tk.BOTH, expand=True)
|
||
|
||
# 相机状态
|
||
self.status_var = tk.StringVar(value="未初始化")
|
||
|
||
# 曝光时间变量
|
||
self.exp_var = tk.DoubleVar(value=5000.0)
|
||
self.exp_var.trace("w", self.on_exp_change)
|
||
|
||
# 增益变量
|
||
self.gain_var = tk.DoubleVar(value=0.0)
|
||
self.gain_var.trace("w", self.on_gain_change)
|
||
|
||
# 帧率变量
|
||
self.fps_var = tk.DoubleVar(value=30.0)
|
||
self.fps_var.trace("w", self.on_fps_change)
|
||
|
||
# 状态标签
|
||
self.create_status_section()
|
||
|
||
# 相机控制
|
||
self.create_control_section()
|
||
|
||
# 参数调节
|
||
self.create_param_section()
|
||
|
||
# 日志区域
|
||
self.create_log_section()
|
||
|
||
# 初始化相机
|
||
self.init_camera()
|
||
|
||
def create_status_section(self):
|
||
"""
|
||
创建状态显示区域
|
||
"""
|
||
status_frame = ttk.LabelFrame(self.main_frame, text="相机状态", padding="10")
|
||
status_frame.pack(fill=tk.X, pady=5)
|
||
|
||
status_label = ttk.Label(status_frame, textvariable=self.status_var, font=("Arial", 12, "bold"))
|
||
status_label.pack(anchor=tk.W)
|
||
|
||
def create_control_section(self):
|
||
"""
|
||
创建相机控制区域
|
||
"""
|
||
control_frame = ttk.LabelFrame(self.main_frame, text="相机控制", padding="10")
|
||
control_frame.pack(fill=tk.X, pady=5)
|
||
|
||
# 按钮容器
|
||
button_frame = ttk.Frame(control_frame)
|
||
button_frame.pack(fill=tk.X)
|
||
|
||
# 初始化按钮
|
||
self.init_button = ttk.Button(button_frame, text="初始化相机", command=self.init_camera)
|
||
self.init_button.pack(side=tk.LEFT, padx=5)
|
||
|
||
# 打开按钮
|
||
self.open_button = ttk.Button(button_frame, text="打开相机", command=self.open_camera, state=tk.DISABLED)
|
||
self.open_button.pack(side=tk.LEFT, padx=5)
|
||
|
||
# 开始取流按钮
|
||
self.start_button = ttk.Button(button_frame, text="开始取流", command=self.start_grabbing, state=tk.DISABLED)
|
||
self.start_button.pack(side=tk.LEFT, padx=5)
|
||
|
||
# 停止取流按钮
|
||
self.stop_button = ttk.Button(button_frame, text="停止取流", command=self.stop_grabbing, state=tk.DISABLED)
|
||
self.stop_button.pack(side=tk.LEFT, padx=5)
|
||
|
||
# 关闭按钮
|
||
self.close_button = ttk.Button(button_frame, text="关闭相机", command=self.close_camera, state=tk.DISABLED)
|
||
self.close_button.pack(side=tk.LEFT, padx=5)
|
||
|
||
def create_param_section(self):
|
||
"""
|
||
创建参数调节区域
|
||
"""
|
||
param_frame = ttk.LabelFrame(self.main_frame, text="参数调节", padding="10")
|
||
param_frame.pack(fill=tk.X, pady=5)
|
||
|
||
# 曝光时间
|
||
exp_frame = ttk.Frame(param_frame)
|
||
exp_frame.pack(fill=tk.X, pady=5)
|
||
|
||
exp_label = ttk.Label(exp_frame, text="曝光时间 (μs):")
|
||
exp_label.pack(side=tk.LEFT, padx=5)
|
||
|
||
exp_scale = ttk.Scale(exp_frame, from_=100.0, to=50000.0, orient=tk.HORIZONTAL, variable=self.exp_var, length=200)
|
||
exp_scale.pack(side=tk.LEFT, padx=5, expand=True, fill=tk.X)
|
||
|
||
exp_entry = ttk.Entry(exp_frame, textvariable=self.exp_var, width=10)
|
||
exp_entry.pack(side=tk.LEFT, padx=5)
|
||
|
||
# 增益
|
||
gain_frame = ttk.Frame(param_frame)
|
||
gain_frame.pack(fill=tk.X, pady=5)
|
||
|
||
gain_label = ttk.Label(gain_frame, text="增益 (dB):")
|
||
gain_label.pack(side=tk.LEFT, padx=5)
|
||
|
||
gain_scale = ttk.Scale(gain_frame, from_=0.0, to=48.0, orient=tk.HORIZONTAL, variable=self.gain_var, length=200)
|
||
gain_scale.pack(side=tk.LEFT, padx=5, expand=True, fill=tk.X)
|
||
|
||
gain_entry = ttk.Entry(gain_frame, textvariable=self.gain_var, width=10)
|
||
gain_entry.pack(side=tk.LEFT, padx=5)
|
||
|
||
# 帧率
|
||
fps_frame = ttk.Frame(param_frame)
|
||
fps_frame.pack(fill=tk.X, pady=5)
|
||
|
||
fps_label = ttk.Label(fps_frame, text="帧率 (fps):")
|
||
fps_label.pack(side=tk.LEFT, padx=5)
|
||
|
||
fps_scale = ttk.Scale(fps_frame, from_=1.0, to=60.0, orient=tk.HORIZONTAL, variable=self.fps_var, length=200)
|
||
fps_scale.pack(side=tk.LEFT, padx=5, expand=True, fill=tk.X)
|
||
|
||
fps_entry = ttk.Entry(fps_frame, textvariable=self.fps_var, width=10)
|
||
fps_entry.pack(side=tk.LEFT, padx=5)
|
||
|
||
def create_log_section(self):
|
||
"""
|
||
创建日志显示区域
|
||
"""
|
||
log_frame = ttk.LabelFrame(self.main_frame, text="日志", padding="10")
|
||
log_frame.pack(fill=tk.BOTH, expand=True, pady=5)
|
||
|
||
# 创建文本框
|
||
self.log_text = tk.Text(log_frame, height=10, wrap=tk.WORD)
|
||
self.log_text.pack(fill=tk.BOTH, expand=True)
|
||
|
||
# 添加滚动条
|
||
scrollbar = ttk.Scrollbar(self.log_text, command=self.log_text.yview)
|
||
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
||
self.log_text.config(yscrollcommand=scrollbar.set)
|
||
|
||
def log(self, message):
|
||
"""
|
||
添加日志信息
|
||
|
||
Args:
|
||
message: 日志消息
|
||
"""
|
||
timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
|
||
log_message = f"[{timestamp}] {message}\n"
|
||
self.log_text.insert(tk.END, log_message)
|
||
self.log_text.see(tk.END)
|
||
|
||
def init_camera(self):
|
||
"""
|
||
初始化相机
|
||
"""
|
||
try:
|
||
self.log("正在初始化相机...")
|
||
self.status_var.set("初始化中")
|
||
|
||
# 枚举设备
|
||
device_count = self.camera.enum_devices()
|
||
if device_count == 0:
|
||
self.log("未找到相机设备")
|
||
self.status_var.set("未找到设备")
|
||
return
|
||
|
||
self.log(f"找到 {device_count} 台相机设备")
|
||
self.status_var.set("就绪")
|
||
|
||
# 启用打开按钮
|
||
self.open_button.config(state=tk.NORMAL)
|
||
self.log("相机初始化成功")
|
||
|
||
except Exception as e:
|
||
self.log(f"初始化失败: {e}")
|
||
self.status_var.set("初始化失败")
|
||
|
||
def open_camera(self):
|
||
"""
|
||
打开相机
|
||
"""
|
||
try:
|
||
self.log("正在打开相机...")
|
||
self.status_var.set("打开中")
|
||
|
||
# 打开设备(默认使用索引0)
|
||
ret = self.camera.open_device(0)
|
||
if ret != 0:
|
||
self.log(f"打开相机失败,错误码: {ret}")
|
||
self.status_var.set("打开失败")
|
||
return
|
||
|
||
self.log("相机打开成功")
|
||
self.status_var.set("已打开")
|
||
|
||
# 更新参数显示
|
||
self.exp_var.set(self.camera.current_exp)
|
||
self.gain_var.set(self.camera.current_gain)
|
||
self.fps_var.set(self.camera.current_fps)
|
||
|
||
# 启用控制按钮
|
||
self.start_button.config(state=tk.NORMAL)
|
||
self.close_button.config(state=tk.NORMAL)
|
||
self.open_button.config(state=tk.DISABLED)
|
||
|
||
except Exception as e:
|
||
self.log(f"打开相机失败: {e}")
|
||
self.status_var.set("打开失败")
|
||
|
||
def start_grabbing(self):
|
||
"""
|
||
开始取流
|
||
"""
|
||
try:
|
||
self.log("正在开始取流...")
|
||
self.status_var.set("取流中")
|
||
|
||
ret = self.camera.start_grabbing()
|
||
if ret != 0:
|
||
self.log(f"开始取流失败,错误码: {ret}")
|
||
self.status_var.set("取流失败")
|
||
return
|
||
|
||
self.log("开始取流成功")
|
||
self.status_var.set("正在取流")
|
||
|
||
# 更新按钮状态
|
||
self.start_button.config(state=tk.DISABLED)
|
||
self.stop_button.config(state=tk.NORMAL)
|
||
|
||
except Exception as e:
|
||
self.log(f"开始取流失败: {e}")
|
||
self.status_var.set("取流失败")
|
||
|
||
def stop_grabbing(self):
|
||
"""
|
||
停止取流
|
||
"""
|
||
try:
|
||
self.log("正在停止取流...")
|
||
|
||
ret = self.camera.stop_grabbing()
|
||
if ret != 0:
|
||
self.log(f"停止取流失败,错误码: {ret}")
|
||
return
|
||
|
||
self.log("停止取流成功")
|
||
self.status_var.set("已打开")
|
||
|
||
# 更新按钮状态
|
||
self.stop_button.config(state=tk.DISABLED)
|
||
self.start_button.config(state=tk.NORMAL)
|
||
|
||
except Exception as e:
|
||
self.log(f"停止取流失败: {e}")
|
||
|
||
def close_camera(self):
|
||
"""
|
||
关闭相机
|
||
"""
|
||
try:
|
||
self.log("正在关闭相机...")
|
||
|
||
self.camera.close_device()
|
||
|
||
self.log("相机关闭成功")
|
||
self.status_var.set("已关闭")
|
||
|
||
# 更新按钮状态
|
||
self.close_button.config(state=tk.DISABLED)
|
||
self.stop_button.config(state=tk.DISABLED)
|
||
self.start_button.config(state=tk.DISABLED)
|
||
self.open_button.config(state=tk.NORMAL)
|
||
|
||
except Exception as e:
|
||
self.log(f"关闭相机失败: {e}")
|
||
|
||
def on_exp_change(self, *args):
|
||
"""
|
||
曝光时间变化回调
|
||
"""
|
||
if self.camera.is_open:
|
||
try:
|
||
exp_value = self.exp_var.get()
|
||
ret = self.camera.set_exposure_time(exp_value)
|
||
if ret == 0:
|
||
self.log(f"曝光时间设置为: {exp_value} μs")
|
||
except Exception as e:
|
||
self.log(f"设置曝光时间失败: {e}")
|
||
|
||
def on_gain_change(self, *args):
|
||
"""
|
||
增益变化回调
|
||
"""
|
||
if self.camera.is_open:
|
||
try:
|
||
gain_value = self.gain_var.get()
|
||
ret = self.camera.set_gain(gain_value)
|
||
if ret == 0:
|
||
self.log(f"增益设置为: {gain_value} dB")
|
||
except Exception as e:
|
||
self.log(f"设置增益失败: {e}")
|
||
|
||
def on_fps_change(self, *args):
|
||
"""
|
||
帧率变化回调
|
||
"""
|
||
if self.camera.is_open:
|
||
try:
|
||
fps_value = self.fps_var.get()
|
||
ret = self.camera.set_frame_rate(fps_value)
|
||
if ret == 0:
|
||
self.log(f"帧率设置为: {fps_value} fps")
|
||
except Exception as e:
|
||
self.log(f"设置帧率失败: {e}")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
"""
|
||
测试相机功能
|
||
"""
|
||
print("海康相机测试程序")
|
||
print("按 Q 退出显示窗口")
|
||
print("按 Ctrl+C 退出程序")
|
||
|
||
# 创建相机实例
|
||
camera = Camera()
|
||
|
||
try:
|
||
# 创建控制界面
|
||
root = tk.Tk()
|
||
app = CameraControlPanel(root, camera)
|
||
|
||
# 运行主循环
|
||
root.mainloop()
|
||
|
||
except KeyboardInterrupt:
|
||
print("\n用户中断程序")
|
||
except Exception as e:
|
||
print(f"程序异常: {e}")
|
||
finally:
|
||
# 清理资源
|
||
camera.close_device()
|
||
print("程序退出")
|