接上篇海康威视GigE工业相机的python调用demo-CSDN博客
取到了图像数据后,就需要对数据进行处理。我手里的这台CU系列面阵相机,在MVS中可以看到它的数据默认格式是Bayer RG8:
Bayer RG8图像格式,采用 RGGB 布局。在这种布局中,红色(R)、绿色(G)和蓝色(B)像素点交错排列。具体来说,奇数扫描行按 R、G、R、G…… 顺序排列,偶数扫描行按 G、B、G、B…… 顺序排列,每个像素仅需一个颜色通道的数据,数据量仅为 RGB8 格式的三分之一。从图像缓存中获取到了数据后,需要进行一些处理才能转换成常规的RGB三通道数据。
使用opencv转换Bayer RG8到RGB格式并保存文件的demo:
image_data = np.frombuffer(self.buf_save_image, dtype=np.uint8).reshape((height, width))
rgb_image = cv2.cvtColor(image_data, cv2.COLOR_BAYER_RG2RGB)
cv2.imwrite(file_path, rgb_image)
实测3072*2048的图像数据,i510代CPU,转换时间为7ms,还算是比较迅速的。
厂家的ConvertPixelType.py提供了转换Bayer RG8到RGB格式的代码,核心部分:
stOutFrame = MV_FRAME_OUT() # 创建输出帧
memset(byref(stOutFrame), 0, sizeof(stOutFrame)) # 开辟内存区
ret = cam.MV_CC_GetImageBuffer(stOutFrame, 1000) # 获取帧数据
if None != stOutFrame.pBufAddr and 0 == ret :
print ("get one frame: Width[%d], Height[%d], nFrameNum[%d]" % (stOutFrame.stFrameInfo.nWidth, stOutFrame.stFrameInfo.nHeight, stOutFrame.stFrameInfo.nFrameNum))
nRGBSize = stOutFrame.stFrameInfo.nWidth * stOutFrame.stFrameInfo.nHeight * 3 # 转换后的rgb数据长度
stConvertParam = MV_CC_PIXEL_CONVERT_PARAM_EX() # 创建转换参数集
memset(byref(stConvertParam), 0, sizeof(stConvertParam)) # 开辟内存区
stConvertParam.nWidth = stOutFrame.stFrameInfo.nWidth # 宽
stConvertParam.nHeight = stOutFrame.stFrameInfo.nHeight # 高
stConvertParam.pSrcData = stOutFrame.pBufAddr # 源数据指针
stConvertParam.nSrcDataLen = stOutFrame.stFrameInfo.nFrameLen # 源数据长度
stConvertParam.enSrcPixelType = stOutFrame.stFrameInfo.enPixelType # 源数据像素格式
stConvertParam.enDstPixelType = PixelType_Gvsp_RGB8_Packed # 目标数据像素格式
stConvertParam.pDstBuffer = (c_ubyte * nRGBSize)() # 目标数据指针
stConvertParam.nDstBufferSize = nRGBSize # 目标数据长度
ret = cam.MV_CC_ConvertPixelTypeEx(stConvertParam) # 转换数据
实测,同样配置下,厂家自带的SDK,转换时间为13ms左右,比OpenCV慢了不少。
所以,从代码简洁和运行效率两方面,opencv是优解。
# -*- coding: utf-8 -*-
import inspect
import time
import cv2
import numpy as np
from MvCameraControl_class import *
import threading
import msvcrt
from ctypes import *
# 海康威视直连IPGigE相机
class HiKGidECamera(MvCamera):
def __init__(self, deviceIp, netIp):
super().__init__()
self.buffRGB8 = None # RGB8格式的图像缓存数据
self.save_file_path = ""
self.hThreadHandle = None # 用于取流和存图的线程句柄
self.ui_winHandle = 0 # ui显示窗口的句柄,0表示不显示。etc:ui.widgetDisplay.winId()
self.save_pre_path = "" # 保存图像的前置路径
self.save_jpg_quality = 80 # 保存的jpg图像质量
self.stSaveParam = MV_SAVE_IMAGE_TO_FILE_PARAM_EX() # 保存图像文件的参数集
self.stDisplayParam = MV_DISPLAY_FRAME_INFO() # 显示图像的参数集
self.stConvertParam = MV_CC_PIXEL_CONVERT_PARAM() # 转换图像像素格式的参数集
self.buf_image = None # 用以保存和处理的图像buffer
self.save_type = "bmp" # 保存图像的类型
self.model_name = ""
self.user_defined_name = ""
self.netIpList = []
self.deviceIpList = []
self.stDevInfo = MV_CC_DEVICE_INFO() # 设备信息
self.stGigEDev = MV_GIGE_DEVICE_INFO() # 设备信息
self.deviceIp = deviceIp # 相机IP
self.netIp = netIp # 相机接入的网卡IP
self.isOpened = False # 相机是否打开
self.isGrabbing = False # 相机是否正在采集图像
self.is_trigger_mode = True # 是否触发模式
# self.b_exit = True # 退出取流的标志
self.buf_lock = threading.Lock() # 取图和存图的buffer锁
self.b_save_bmp = False # 是否保存bmp文件
self.b_save_jpg = False # 是否保存jpg文件
self.buf_image = None # 从缓存区中拷贝的图像buffer,用以保存和处理
# 强制关闭线程
def Async_raise(self, tid, exctype):
tid = ctypes.c_long(tid)
if not inspect.isclass(exctype):
exctype = type(exctype)
res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))
if res == 0:
raise ValueError("invalid thread id")
elif res != 1:
ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
raise SystemError("PyThreadState_SetAsyncExc failed")
# 停止线程
def Stop_thread(self, thread):
self.Async_raise(thread.ident, SystemExit)
# 转为16进制字符串
def To_hex_str(self, num):
chaDic = {10: 'a', 11: 'b', 12: 'c', 13: 'd', 14: 'e', 15: 'f'}
hexStr = ""
if num < 0:
num = num + 2 ** 32
while num >= 16:
digit = num % 16
hexStr = chaDic.get(digit, str(digit)) + hexStr
num //= 16
hexStr = chaDic.get(num, str(num)) + hexStr
return hexStr
# 解码操作
def decoding_char(self, c_ubyte_value):
c_char_p_value = ctypes.cast(c_ubyte_value, ctypes.c_char_p)
try:
decode_str = c_char_p_value.value.decode('gbk') # Chinese characters
except UnicodeDecodeError:
decode_str = str(c_char_p_value.value)
return decode_str
# 是否是Mono图像
def Is_mono_data(self, enGvspPixelType):
if PixelType_Gvsp_Mono8 == enGvspPixelType or PixelType_Gvsp_Mono10 == enGvspPixelType \
or PixelType_Gvsp_Mono10_Packed == enGvspPixelType or PixelType_Gvsp_Mono12 == enGvspPixelType \
or PixelType_Gvsp_Mono12_Packed == enGvspPixelType:
return True
else:
return False
# 是否是彩色图像
def Is_color_data(self, enGvspPixelType):
if PixelType_Gvsp_BayerGR8 == enGvspPixelType or PixelType_Gvsp_BayerRG8 == enGvspPixelType \
or PixelType_Gvsp_BayerGB8 == enGvspPixelType or PixelType_Gvsp_BayerBG8 == enGvspPixelType \
or PixelType_Gvsp_BayerGR10 == enGvspPixelType or PixelType_Gvsp_BayerRG10 == enGvspPixelType \
or PixelType_Gvsp_BayerGB10 == enGvspPixelType or PixelType_Gvsp_BayerBG10 == enGvspPixelType \
or PixelType_Gvsp_BayerGR12 == enGvspPixelType or PixelType_Gvsp_BayerRG12 == enGvspPixelType \
or PixelType_Gvsp_BayerGB12 == enGvspPixelType or PixelType_Gvsp_BayerBG12 == enGvspPixelType \
or PixelType_Gvsp_BayerGR10_Packed == enGvspPixelType or PixelType_Gvsp_BayerRG10_Packed == enGvspPixelType \
or PixelType_Gvsp_BayerGB10_Packed == enGvspPixelType or PixelType_Gvsp_BayerBG10_Packed == enGvspPixelType \
or PixelType_Gvsp_BayerGR12_Packed == enGvspPixelType or PixelType_Gvsp_BayerRG12_Packed == enGvspPixelType \
or PixelType_Gvsp_BayerGB12_Packed == enGvspPixelType or PixelType_Gvsp_BayerBG12_Packed == enGvspPixelType \
or PixelType_Gvsp_YUV422_Packed == enGvspPixelType or PixelType_Gvsp_YUV422_YUYV_Packed == enGvspPixelType:
return True
else:
return False
# 注册相机
def register(self):
self.deviceIpList = self.deviceIp.split('.') # 相机的主机IP
self.netIpList = self.netIp.split('.') # 连接相机的网卡IP
self.stGigEDev.nCurrentIp = (int(self.deviceIpList[0]) << 24) | (int(self.deviceIpList[1]) << 16) | (
int(self.deviceIpList[2]) << 8) | int(self.deviceIpList[3]) # 设置相机IP
self.stGigEDev.nNetExport = (int(self.netIpList[0]) << 24) | (int(self.netIpList[1]) << 16) | (
int(self.netIpList[2]) << 8) | int(
self.netIpList[3]) # 设置网卡IP
self.stDevInfo.nTLayerType = MV_GIGE_DEVICE # 设备类型为GigE
self.stDevInfo.SpecialInfo.stGigEInfo = self.stGigEDev # 传入GigE相机信息
# 打开设备
def open_device(self):
if self.isOpened:
print("相机已打开!")
return
# 选择设备并创建句柄
ret = self.MV_CC_CreateHandle(self.stDevInfo) # 创建设备句柄
if ret != 0:
print("create handle fail! ret[0x%x]" % ret)
return ret
ret = self.MV_CC_OpenDevice() # 打开设备
if ret != 0:
print("open device fail! ret[0x%x]" % ret)
return ret
else:
# 探测网络最佳包大小
nPacketSize = self.MV_CC_GetOptimalPacketSize()
if int(nPacketSize) > 0:
ret = self.MV_CC_SetIntValue("GevSCPSPacketSize", nPacketSize)
if ret != 0:
print("Warning: Set Packet Size fail! ret[0x%x]" % ret)
return ret
self.isOpened = True # 相机已打开
# 获取相机信息
deviceList = MV_CC_DEVICE_INFO_LIST() # 所有在线相机的设备信息列表
n_layer_type = MV_GIGE_DEVICE # 只使用GigE相机
ret = MvCamera.MV_CC_EnumDevices(n_layer_type, deviceList) # 返回值为0表示成功
if ret != 0:
print(f"enum devices fail! ret = {self.To_hex_str(ret)}")
return -1
for i in range(0, deviceList.nDeviceNum):
mvcc_dev_info = cast(deviceList.pDeviceInfo[i], POINTER(MV_CC_DEVICE_INFO)).contents # 设备信息
# 相机的IP地址
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)
# 如果相机在线,则获取相机的信息
if str(nip1) == self.deviceIpList[0] and str(nip2) == self.deviceIpList[1] and str(nip3) == \
self.deviceIpList[2] and str(nip4) == self.deviceIpList[3]:
# self.n_gide_device = i # GidE设备序号
self.user_defined_name = self.decoding_char(
mvcc_dev_info.SpecialInfo.stGigEInfo.chUserDefinedName) # 设备的用户定义名称(支持中文名称)
self.model_name = self.decoding_char(mvcc_dev_info.SpecialInfo.stGigEInfo.chModelName) # 相机的型号
print("user_defined_name:", self.user_defined_name)
print("model_name:", self.model_name)
break
if i == deviceList.nDeviceNum - 1:
print("指定的相机不在线!")
return -1
print("open device successfully!")
return 0
else:
print("Warning: Get Packet Size fail! ret[0x%x]" % nPacketSize)
# 关闭设备
def close_device(self):
if not self.isOpened:
print("相机已关闭!")
return
else:
# 退出取流线程
if self.isGrabbing:
self.stop_grabbing() # 停止取流
ret = self.MV_CC_CloseDevice() # 关闭设备
if ret != 0:
print("关闭相机失败!")
return -1
self.isOpened = False
self.MV_CC_DestroyHandle() # 销毁句柄
print("close device successfully!")
return 0
# 设置连续取流模式(亦即关闭触发模式)
def set_continue_mode(self):
if not self.isOpened:
print("相机未打开!")
return -1
if self.is_trigger_mode:
# ret = self.MV_CC_SetEnumValue("TriggerMode", MV_TRIGGER_MODE_OFF) # 关闭触发模式
ret = self.MV_CC_SetEnumValue("TriggerMode", 0) # 关闭触发模式
if ret != 0:
print(f"设置连续模式失败:ret = {self.To_hex_str(ret)}")
return ret
self.is_trigger_mode = False
return 0
# 设置软触发模式
def set_software_trigger_mode(self):
# ret = self.MV_CC_SetEnumValue("TriggerMode", MV_TRIGGER_MODE_ON) # 打开触发模式
ret = self.MV_CC_SetEnumValue("TriggerMode", 1) # 打开触发模式
if ret != 0:
print(f"设置软触发模式失败!ret = {self.To_hex_str(ret)}")
return -1
# ret = self.MV_CC_SetEnumValue("TriggerSource", MV_TRIGGER_SOURCE_SOFTWARE) # 设置触发源为软触发
self.is_trigger_mode = True
ret = self.MV_CC_SetEnumValue("TriggerSource", 7) # 设置触发源为软触发
if ret != 0:
print(f"设置触发源失败! ret = {self.To_hex_str(ret)}")
return ret
return 0
# 软触发一次
def Trigger_once(self):
if self.isOpened and self.isGrabbing:
return self.MV_CC_SetCommandValue("TriggerSoftware")
# 设置线路0外部触发模式
def set_line0_trigger_mode(self):
ret = self.MV_CC_SetEnumValue("TriggerMode", MV_TRIGGER_MODE_ON) # 打开触发模式
if ret != 0:
print(f"设置外部触发模式失败!ret = {self.To_hex_str(ret)}")
return ret
self.is_trigger_mode = True
ret = self.MV_CC_SetEnumValue("TriggerSource", MV_TRIGGER_SOURCE_LINE0) # 设置触发源为线路0
if ret != 0:
print(f"设置触发源失败!ret = {self.To_hex_str(ret)}")
return ret
return 0
# 设置线路2外部触发模式
def set_line2_trigger_mode(self):
ret = self.MV_CC_SetEnumValue("TriggerMode", MV_TRIGGER_MODE_ON) # 打开触发模式
if ret != 0:
print(f"设置外部触发模式失败!ret = {self.To_hex_str(ret)}")
return ret
self.is_trigger_mode = True
ret = self.MV_CC_SetEnumValue("TriggerSource", MV_TRIGGER_SOURCE_LINE2) # 设置触发源为线路0
if ret != 0:
print(f"设置触发源失败!ret = {self.To_hex_str(ret)}")
return ret
return 0
# 开始取流
def start_grabbing(self, winHandle):
if not self.isOpened:
print("相机未打开!")
return -1
if self.isGrabbing:
print("相机正在取流!")
return
ret = self.MV_CC_StartGrabbing() # 开始取流
if ret != 0:
print(f"start grabbing fail! ret = {self.To_hex_str(ret)}")
return ret
try:
# 创建取流线程
self.isGrabbing = True # 正在取流
self.hThreadHandle = threading.Thread(target=self.work_thread, args=(winHandle,)) # 创建取流线程
self.hThreadHandle.start() # 启动取流线程
return 0
finally:
pass
# 停止取流
def stop_grabbing(self):
if not self.isOpened:
print("相机未打开!")
return -1
if self.isGrabbing:
ret = self.MV_CC_StopGrabbing()
if ret != 0:
print("stop grabbing fail!", ret)
return ret
else:
# 停止取流线程
self.Stop_thread(self.hThreadHandle)
self.isGrabbing = False
print("stop grabbing successfully!")
else:
print("相机未处于取流状态!")
return -1
self.isGrabbing = False # 正在取流
return 0
# 取流的线程
def work_thread(self, winHandle):
stOutFrame = MV_FRAME_OUT() # 输出图像帧
memset(byref(stOutFrame), 0, sizeof(stOutFrame)) # 初始化输出图像帧的内存区
while True:
ret = self.MV_CC_GetImageBuffer(stOutFrame, 1000) # 获取一帧图像到缓存
if 0 == ret:
# 拷贝图像和图像信息
if self.buf_image is None: # 创建图像的内存空间
self.buf_image = (c_ubyte * stOutFrame.stFrameInfo.nFrameLen)()
self.st_frame_info = stOutFrame.stFrameInfo # 图像的帧信息
self.buf_lock.acquire() # 获取线程锁
cdll.msvcrt.memcpy(byref(self.buf_image), stOutFrame.pBufAddr, self.st_frame_info.nFrameLen) # 从缓存中拷贝图像
# ##############################获取显示图像需要的帧数据#############################################
memset(byref(self.stDisplayParam), 0, sizeof(self.stDisplayParam))
self.stDisplayParam.hWnd = int(winHandle)
self.stDisplayParam.nWidth = self.st_frame_info.nWidth
self.stDisplayParam.nHeight = self.st_frame_info.nHeight
self.stDisplayParam.enPixelType = self.st_frame_info.enPixelType
self.stDisplayParam.pData = self.buf_image
self.stDisplayParam.nDataLen = self.st_frame_info.nFrameLen
self.MV_CC_DisplayOneFrame(self.stDisplayParam)
# ##############################获取保存图像需要的帧数据#############################################
self.stSaveParam.enPixelType = self.st_frame_info.enPixelType # 相机对应的像素格式
self.stSaveParam.nWidth = self.st_frame_info.nWidth # 相机对应的宽
self.stSaveParam.nHeight = self.st_frame_info.nHeight # 相机对应的高
self.stSaveParam.nDataLen = self.st_frame_info.nFrameLen # 帧的数据长度
self.stSaveParam.pData = cast(self.buf_image, POINTER(c_ubyte)) # 图像数据的指针
self.save_file_path = self.save_pre_path + str(self.st_frame_info.nFrameNum) # 保存图像的文件名
self.stSaveParam.iMethodValue = 1 # 保存图像的参数
# ##############################获取转换像素需要的帧数据#############################################
# 获取帧数据的参数
self.stConvertParam.nWidth = self.st_frame_info.nWidth # 宽度
self.stConvertParam.nHeight = self.st_frame_info.nHeight # 高度
self.buf_lock.release() # 释放线程锁
print("get one frame: Width[%d], Height[%d], nFrameNum[%d]"
% (self.st_frame_info.nWidth, self.st_frame_info.nHeight, self.st_frame_info.nFrameNum))
# 释放缓存
self.MV_CC_FreeImageBuffer(stOutFrame)
else:
print(f"no data, ret = {self.To_hex_str(ret)}")
continue
# 是否退出
if not self.isGrabbing:
print("停止取流")
if self.buf_image is not None:
del self.buf_image
break
def Save_Bmp(self):
if self.buf_image is not None:
# 获取缓存锁
self.buf_lock.acquire()
self.save_file_path += ".bmp"
path = self.save_file_path.encode('utf-8')
self.stSaveParam.enImageType = MV_Image_Bmp # 需要保存的图像类型
self.stSaveParam.pcImagePath = ctypes.create_string_buffer(path) # 保存的文件名
ret = self.MV_CC_SaveImageToFileEx(self.stSaveParam) # 保存图像
if ret != 0:
print(f"保存bmp文件失败: {ret}")
return ret
self.buf_lock.release()
print("保存bmp文件成功!")
return ret
else:
print("无可用的图像缓存!")
return -1
def Save_bmp_opencv(self):
if self.buf_image is not None:
self.save_file_path += ".bmp"
path = self.save_file_path.encode('utf-8')
image_data = self.convert2RGB8()
# 获取缓存锁
self.buf_lock.acquire()
_, img_encoded = cv2.imencode('.bmp', image_data) # 使用编码的方式规避opencv不支持中文名的缺陷
# 将图像数据写入文件
with open(path, 'wb') as f:
f.write(img_encoded.tobytes())
print("保存bmp文件成功!")
self.buf_lock.release()
return 0
else:
print("无可用的图像缓存!")
return -1
def Save_jpg(self):
if self.buf_image is not None:
# 获取缓存锁
self.buf_lock.acquire()
self.save_file_path += ".jpg"
path = self.save_file_path.encode('utf-8')
self.stSaveParam.enImageType = MV_Image_Jpeg # 需要保存的图像类型
self.stSaveParam.pcImagePath = ctypes.create_string_buffer(path) # 保存的文件名
self.stSaveParam.nQuality = 80
ret = self.MV_CC_SaveImageToFileEx(self.stSaveParam) # 保存图像
if ret != 0:
print(f"保存jpg文件失败: {ret}")
self.buf_lock.release()
return ret
self.buf_lock.release()
print("保存jpg文件成功!")
return ret
else:
print("无可用的图像缓存!")
return -1
def Save_jpg_opencv(self):
if self.buf_image is not None:
self.save_file_path += ".jpg"
path = self.save_file_path.encode('utf-8')
image_data = self.convert2RGB8()
# 获取缓存锁
self.buf_lock.acquire()
_, img_encoded = cv2.imencode('.jpg', image_data) # 使用编码的方式规避opencv不支持中文名的缺陷
# 将图像数据写入文件
with open(path, 'wb') as f:
f.write(img_encoded.tobytes())
print("保存jpg文件成功!")
self.buf_lock.release()
return 0
else:
print("无可用的图像缓存!")
return -1
# 转换像素到RGB8格式
def convert2RGB8(self):
if self.buf_image is not None:
# 获取缓存锁
self.buf_lock.acquire() # 转换bayer数据到RGB格式#########
image_data = np.frombuffer(self.buf_image, dtype=np.uint8).reshape((self.stConvertParam.nHeight,self.stConvertParam.nWidth))
rgb_image = cv2.cvtColor(image_data, cv2.COLOR_BAYER_RG2RGB)
print("转换像素格式成功!")
self.buf_lock.release()
return rgb_image
else:
print("无可用的图像缓存!")
return None
if __name__ == "__main__":
# 初始化SDK
MvCamera.MV_CC_Initialize()
cam = HiKGidECamera("192.168.100.100", "192.168.100.1") # 相机IP和网卡IP
cam.register() # 注册相机
cam.open_device() # 打开设备
time.sleep(5)
cam.set_continue_mode() # 设置连续取流模式
cam.start_grabbing(0) # 开始取流
i = 0
cam.save_pre_path = "连续取图" # 保存图像的路径
while i < 3:
time.sleep(2)
cam.Save_Bmp() # 保存bmp文件
i += 1
i = 0
cam.save_pre_path = "连续取图" # 保存图像的路径
while i < 3:
time.sleep(2)
cam.Save_jpg() # 保存jpg文件
i += 1
i = 0
cam.save_pre_path = "opencv存图" # 保存图像的路径
while i < 3:
time.sleep(2)
cam.Save_bmp_opencv() # opencv保存bmp文件
cam.Save_jpg_opencv() # opencv保存jpg文件
i += 1
i = 0
cam.save_pre_path = "软触发" # 保存图像的路径
cam.set_software_trigger_mode() # 设置软触发模式
while i < 3:
time.sleep(2)
cam.Trigger_once() # 软触发一次
cam.Save_Bmp() # 保存bmp文件
i += 1
cam.stop_grabbing()
cam.close_device() # 关闭设备
# ch:销毁句柄 | Destroy handle
ret = cam.MV_CC_DestroyHandle()
if ret != 0:
print("destroy handle fail! ret[0x%x]" % ret)
# ch:反初始化SDK | en: finalize SDK
MvCamera.MV_CC_Finalize()
sys.exit()