-
下载安装MVS
首先在海康机器人官网 海康机器人-机器视觉-下载中心 下载安装MVS,不同的版本,调用会有区别,本文基于这个版本:
默认路径安装完成后,在路径C:\Program Files (x86)\MVS\Development\Samples\Python\BasicDemo下的BasicDemo.py中,海康威视官方提供了基本的调用方法,本文也是基于这个修改的。
BasicDemo.py提供了通过枚举选择相机的方法,在官方的另一个DEMO:ConnectSpecCamera.py中提供了另一种不通过枚举,直接访问特定IP相机的方法。
-
创建环境和项目
创建conda环境以及在pycharm中创建环境和安装必要的支持包,这些属于基操了,不赘述。
将MVS所在路径下的\Development\Samples\Python\BasicDemo的CamOperation_class.py文件拷贝到创建好的空项目的文件夹中;
将MVS所在路径下的\Development\Samples\Python\MvImport下的几个文件拷贝到创建好的空项目的文件夹中;(也可以通过在代码中添加路径的方法实现这一步)
拷贝好文件之后的项目文件目录:
-
初始化和创建相机的实例
官方的demo中,将相机初始化和创建为实例,这是一个标准套路。
# 初始化SDK
MvCamera.MV_CC_Initialize()
# 创建相机实例
cam = MvCamera()
并通过下面的代码获取所有相机的设备信息列表:
deviceList = MV_CC_DEVICE_INFO_LIST() # 所有相机信息的列表
然后可以通过mvcc_dev_info对列表中的相机进行设备序号、产品型号、用户定义名、IP地址等信息的查询。
deviceList.nDeviceNum # 相机列表中的数量
user_defined_name = decoding_char(mvcc_dev_info.SpecialInfo.stGigEInfo.chUserDefinedName) # 用户自定义的相机名称
decoding_char(mvcc_dev_info.SpecialInfo.stGigEInfo.chModelName) # 相机型号
# 相机的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)
然后选择一个列表中的相机,定义操作实例:
obj_cam_operation = CameraOperation(cam, deviceList, nSelCamIndex) # 操作的实例
接下来就可以使用定义好的操作实例进行常用操作:
obj_cam_operation.Open_device() # 打开设备
obj_cam_operation.Close_device() # 关闭设备
obj_cam_operation.Trigger_once() # 软触发一次
obj_cam_operation.Start_grabbing() # 开始取流
obj_cam_operation.Stop_grabbing() # 停止取流
obj_cam_operation.Set_trigger_mode() # 设置触发模式
obj_cam_operation.Get_parameter() # 获取参数
obj_cam_operation.Set_parameter() # 设置参数
obj_cam_operation.Save_jpg() # 保存jpg文件
obj_cam_operation.Save_bmp() # 保存bmp文件
-
选择相机的方法
可以通过上面提供的设备序号方法选择特定的相机。
obj_cam_operation = CameraOperation(cam, deviceList, nSelCamIndex)
在工程应用中,由于相机的在线数量可能是不确定的,那么通过设备序号的方法有可能会有不确定性。而IP地址具有唯一性和不可变性(需要在MVS软件中将相机的IP设为固定模式),用IP地址来对相机进行定义和操作比较具有实用性。
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 nip1 == self.nip1 and nip2 == self.nip2 and nip3 == self.nip3 and nip4 == self.nip4:
self.obj_cam_operation = CameraOperation(cam, deviceList, i)
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) # 相机的型号
break
if i == deviceList.nDeviceNum - 1:
print("未发现指定的相机!")
另外,如果每台相机都定义了唯一的用户定义名,也可以通过用户定义名来区分相机。
for i in range(0, deviceList.nDeviceNum):
mvcc_dev_info = cast(deviceList.pDeviceInfo[i], POINTER(MV_CC_DEVICE_INFO)).contents # 设备信息
# 相机的用户自定义名称
user_defined_name = self.decoding_char(
mvcc_dev_info.SpecialInfo.stGigEInfo.chUserDefinedName) # 设备的用户定义名称(支持中文名称)
# 如果相机在线,则获取相机在设备列表中的序号,并定义它的操作类
if user_defined_name == self.user_defined_name:
self.obj_cam_operation = CameraOperation(cam, deviceList, i)
self.n_gide_device = i # GidE设备序号
nip1 = ((mvcc_dev_info.SpecialInfo.stGigEInfo.nCurrentIp & 0xff000000) >> 24) # IP1
nip2 = ((mvcc_dev_info.SpecialInfo.stGigEInfo.nCurrentIp & 0x00ff0000) >> 16) # IP2
nip3 = ((mvcc_dev_info.SpecialInfo.stGigEInfo.nCurrentIp & 0x0000ff00) >> 8) # IP3
nip4 = (mvcc_dev_info.SpecialInfo.stGigEInfo.nCurrentIp & 0x000000ff) # IP4
self.model_name = self.decoding_char(mvcc_dev_info.SpecialInfo.stGigEInfo.chModelName) # 相机的型号
break
if i == deviceList.nDeviceNum - 1:
print("未发现指定的相机!")
-
基于枚举的方法区别相机的完整代码
下面一段代码,演示了如何通过IP地址定义一台相机,并将其设置为软触发后获取3帧图像,然后将获取到的图像保存。
# -*- coding: utf-8 -*-
import time
from CamOperation_class import CameraOperation
from MvCameraControl_class import *
from MvErrorDefine_const import *
from CameraParams_header import *
import ctypes
# 示例代码(基于MVS SDK)
# 将海康的GidE相机定义为类
class HiKGidECamera(MvCamera):
def __init__(self, nip1, nip2, nip3, nip4):
super().__init__()
self.n_gide_device = -1 # GidE设备序号
self.user_defined_name = "" # 用户自定义名称
self.model_name = "" # 相机型号
self.nip1 = nip1 # IP地址1
self.nip2 = nip2 # IP地址2
self.nip3 = nip3 # IP地址3
self.nip4 = nip4 # IP地址4
self.obj_cam_operation = None # 相机操作类
self.isOpen = False # 相机是否打开
self.isGrabbing = False # 相机是否正在采集图像
self.is_trigger_mode = False # 是否触发模式
# 注册相机
def log_on(self):
"""
注册相机,如果相机在线,则获取相机在设备列表中的序号
:return: 0表示成功,其他表示失败
"""
deviceList = MV_CC_DEVICE_INFO_LIST() # 所有相机的设备信息列表
# ######如果需要枚举GigE设备、USB设备、Gentl设备、CXP设备、XOF设备##############
# n_layer_type = (MV_GIGE_DEVICE | MV_USB_DEVICE | MV_GENTL_CAMERALINK_DEVICE
# | MV_GENTL_CXP_DEVICE | MV_GENTL_XOF_DEVICE)
##################################################################
# 本例只使用GigE相机
n_layer_type = MV_GIGE_DEVICE # 只使用GigE相机
ret = MvCamera.MV_CC_EnumDevices(n_layer_type, deviceList) # 返回值为0表示成功
if ret != 0:
print("查找设备失败。 错误码[0x%x]" % ret)
return -1
if deviceList.nDeviceNum == 0: # 在线设备数量
print("未发现设备!")
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 nip1 == self.nip1 and nip2 == self.nip2 and nip3 == self.nip3 and nip4 == self.nip4:
self.obj_cam_operation = CameraOperation(self, deviceList, i) # 定义相机操作的实体类
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) # 相机的型号
return 0
if i == deviceList.nDeviceNum - 1:
print("未发现指定的相机!")
return -1
# 打开相机
def open_device(self):
if self.isOpen == True:
print("相机已打开!")
return MV_E_CALLORDER # 返回错误信息
ret = self.obj_cam_operation.Open_device()
if ret != 0:
print("打开设备失败,错误码:", ret)
self.isOpen = False
else:
self.isOpen = True
# 关闭相机
def close_device(self):
if self.isOpen == False:
print("相机已关闭!")
return MV_E_CALLORDER # 返回错误信息
ret = self.obj_cam_operation.Close_device()
if ret != 0:
print("关闭设备失败,错误码:", ret)
self.isOpen = True
else:
self.isOpen = False
# 设置连续取流模式
def set_continue_mode(self):
ret = self.obj_cam_operation.Set_trigger_mode(False)
if ret != 0:
print("设置连续模式失败:" , self.ToHexStr(ret))
# ch:设置软触发模式 | en:set software trigger mode
# 设置软触发模式
def set_software_trigger_mode(self):
ret = self.obj_cam_operation.Set_trigger_mode(True)
if ret != 0:
print("设置软触发模式失败::", self.ToHexStr(ret))
# 软触发一次
def trigger_once(self):
ret = self.obj_cam_operation.Trigger_once()
if ret != 0:
print("单次软触发失败:",self.ToHexStr(ret))
# 开始取流
def start_grabbing(self):
# ret = cam.MV_CC_StartGrabbing()
ret = self.obj_cam_operation.Start_grabbing(0)
if ret != 0:
print("开始取流失败:" + self.ToHexStr(ret))
else:
self.isGrabbing = True
# time.sleep(5)
# 停止取流
def stop_grabbing(self):
ret = self.obj_cam_operation.Stop_grabbing()
if ret != 0:
print("停止取流失败:", self.ToHexStr(ret))
else:
self.isGrabbing = False
# 解码操作
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
# 将返回的错误码转换为十六进制显示
def ToHexStr(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
if __name__ == "__main__":
# 初始化SDK
MvCamera.MV_CC_Initialize()
# 创建相机实例
cam = HiKGidECamera(192,168,100,100) # 创建基于IP地址的相机实例
ret = cam.log_on() # 注册相机
if ret == 0:
cam.open_device() # 打开相机
ret = cam.obj_cam_operation.Set_trigger_mode(True) # 设置软触发模式
cam.start_grabbing() # 开始取流
i = 0
while cam.isGrabbing:
cam.trigger_once() # 软触发一次
cam.obj_cam_operation.b_save_bmp = True # 将保存图像标志置位
print("保存成功", ret)
time.sleep(1)
i += 1
if i == 3:
cam.stop_grabbing() # 停止取流
cam.close_device() # 关闭相机
else:
print("相机注册失败或不在线")
# 反初始化SDK
MvCamera.MV_CC_Finalize()
需要注意的是,厂家提供的SDK中,CamOperation_class.py的代码有点小bug,修改一下才能正常保存。
运行厂家的BasicDemo.py,会报错:
原因在CamOperation_class.py的以下代码:
由于取帧是一个异步线程,而原代码线程锁的位置设置不合理,导致保存图像的过程中有数据(帧图像的特征数据)发生变化,从而造成保存失败。
第一次修改后的代码:
# 取图线程函数
def Work_thread(self, winHandle):
stOutFrame = MV_FRAME_OUT()
memset(byref(stOutFrame), 0, sizeof(stOutFrame))
while True:
self.buf_lock.acquire()
ret = self.obj_cam.MV_CC_GetImageBuffer(stOutFrame, 1000)
if 0 == ret:
# 拷贝图像和图像信息
if self.buf_save_image is None:
self.buf_save_image = (c_ubyte * stOutFrame.stFrameInfo.nFrameLen)()
self.st_frame_info = stOutFrame.stFrameInfo
# 获取缓存锁
# self.buf_lock.acquire()
cdll.msvcrt.memcpy(byref(self.buf_save_image), stOutFrame.pBufAddr, self.st_frame_info.nFrameLen)
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.obj_cam.MV_CC_FreeImageBuffer(stOutFrame)
else:
print("no data, ret = " + To_hex_str(ret))
self.buf_lock.release()
continue
# 使用Display接口显示图像
stDisplayParam = MV_DISPLAY_FRAME_INFO()
memset(byref(stDisplayParam), 0, sizeof(stDisplayParam))
stDisplayParam.hWnd = int(winHandle)
stDisplayParam.nWidth = self.st_frame_info.nWidth
stDisplayParam.nHeight = self.st_frame_info.nHeight
stDisplayParam.enPixelType = self.st_frame_info.enPixelType
stDisplayParam.pData = self.buf_save_image
stDisplayParam.nDataLen = self.st_frame_info.nFrameLen
self.obj_cam.MV_CC_DisplayOneFrame(stDisplayParam)
# 是否退出
if self.b_exit:
if self.buf_save_image is not None:
del self.buf_save_image
break
修改后在连续模式下就可以保存图像了。
但是触发模式下点保存图像按钮程序会停止响应,原因在于:由于是触发模式,当保存图像时,已经返回过了很多次“no data”,帧信息已经无效了,造成保存的时候帧信息不正确而失败。
最终的解决方法:当读取到了有效帧,线程上锁,帧数据马上保存,防止被无效帧数据取代。
修改后可以正常保存图像。总之,厂家这次提供的代码确实有些粗糙。
修改后的CamOperation_class.py完整代码:
# -- coding: utf-8 --
import threading
import time
import sys
import inspect
import ctypes
import random
import os
from ctypes import *
sys.path.append(os.getenv('MVCAM_COMMON_RUNENV') + "/Samples/Python/MvImport")
from CameraParams_header import *
from MvCameraControl_class import *
# 强制关闭线程
def Async_raise(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(thread):
Async_raise(thread.ident, SystemExit)
# 转为16进制字符串
def To_hex_str(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
# 是否是Mono图像
def Is_mono_data(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(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
# 相机操作类
class CameraOperation:
def __init__(self, obj_cam, st_device_list, n_connect_num=0, b_open_device=False, b_start_grabbing=False,
h_thread_handle=None,
b_thread_closed=False, st_frame_info=None, b_exit=False, b_save_bmp=False, b_save_jpg=False,
buf_save_image=None,
n_save_image_size=0, n_win_gui_id=0, frame_rate=0, exposure_time=0, gain=0):
self.obj_cam = obj_cam
self.st_device_list = st_device_list
self.n_connect_num = n_connect_num
self.b_open_device = b_open_device
self.b_start_grabbing = b_start_grabbing
self.b_thread_closed = b_thread_closed
self.st_frame_info = st_frame_info
self.b_exit = b_exit
self.b_save_bmp = b_save_bmp
self.b_save_jpg = b_save_jpg
self.buf_save_image = buf_save_image
self.n_save_image_size = n_save_image_size
self.h_thread_handle = h_thread_handle
self.b_thread_closed
self.frame_rate = frame_rate
self.exposure_time = exposure_time
self.gain = gain
self.buf_lock = threading.Lock() # 取图和存图的buffer锁
self.stSaveParam = MV_SAVE_IMAGE_TO_FILE_PARAM_EX()
# 打开相机
def Open_device(self):
if not self.b_open_device:
if self.n_connect_num < 0:
return MV_E_CALLORDER
# ch:选择设备并创建句柄 | en:Select device and create handle
nConnectionNum = int(self.n_connect_num)
stDeviceList = cast(self.st_device_list.pDeviceInfo[int(nConnectionNum)],
POINTER(MV_CC_DEVICE_INFO)).contents
self.obj_cam = MvCamera()
ret = self.obj_cam.MV_CC_CreateHandle(stDeviceList)
if ret != 0:
self.obj_cam.MV_CC_DestroyHandle()
return ret
ret = self.obj_cam.MV_CC_OpenDevice()
if ret != 0:
return ret
print("open device successfully!")
self.b_open_device = True
self.b_thread_closed = False
# ch:探测网络最佳包大小(只对GigE相机有效) | en:Detection network optimal package size(It only works for the GigE camera)
if stDeviceList.nTLayerType == MV_GIGE_DEVICE or stDeviceList.nTLayerType == MV_GENTL_GIGE_DEVICE:
nPacketSize = self.obj_cam.MV_CC_GetOptimalPacketSize()
if int(nPacketSize) > 0:
ret = self.obj_cam.MV_CC_SetIntValue("GevSCPSPacketSize", nPacketSize)
if ret != 0:
print("warning: set packet size fail! ret[0x%x]" % ret)
else:
print("warning: set packet size fail! ret[0x%x]" % nPacketSize)
stBool = c_bool(False)
ret = self.obj_cam.MV_CC_GetBoolValue("AcquisitionFrameRateEnable", stBool)
if ret != 0:
print("get acquisition frame rate enable fail! ret[0x%x]" % ret)
# ch:设置触发模式为off | en:Set trigger mode as off
ret = self.obj_cam.MV_CC_SetEnumValue("TriggerMode", MV_TRIGGER_MODE_OFF)
if ret != 0:
print("set trigger mode fail! ret[0x%x]" % ret)
return MV_OK
# 开始取图
def Start_grabbing(self, winHandle):
if not self.b_start_grabbing and self.b_open_device:
self.b_exit = False
ret = self.obj_cam.MV_CC_StartGrabbing()
if ret != 0:
return ret
self.b_start_grabbing = True
print("start grabbing successfully!")
try:
thread_id = random.randint(1, 10000)
self.h_thread_handle = threading.Thread(target=CameraOperation.Work_thread, args=(self, winHandle))
self.h_thread_handle.start()
self.b_thread_closed = True
finally:
pass
return MV_OK
return MV_E_CALLORDER
# 停止取图
def Stop_grabbing(self):
if self.b_start_grabbing and self.b_open_device:
# 退出线程
if self.b_thread_closed:
Stop_thread(self.h_thread_handle)
self.b_thread_closed = False
ret = self.obj_cam.MV_CC_StopGrabbing()
if ret != 0:
return ret
print("stop grabbing successfully!")
self.b_start_grabbing = False
self.b_exit = True
return MV_OK
else:
return MV_E_CALLORDER
# 关闭相机
def Close_device(self):
if self.b_open_device:
# 退出线程
if self.b_thread_closed:
Stop_thread(self.h_thread_handle)
self.b_thread_closed = False
ret = self.obj_cam.MV_CC_CloseDevice()
if ret != 0:
return ret
# ch:销毁句柄 | Destroy handle
self.obj_cam.MV_CC_DestroyHandle()
self.b_open_device = False
self.b_start_grabbing = False
self.b_exit = True
print("close device successfully!")
return MV_OK
# 设置触发模式
def Set_trigger_mode(self, is_trigger_mode):
if not self.b_open_device:
return MV_E_CALLORDER
if not is_trigger_mode:
ret = self.obj_cam.MV_CC_SetEnumValue("TriggerMode", 0)
if ret != 0:
return ret
else:
ret = self.obj_cam.MV_CC_SetEnumValue("TriggerMode", 1)
if ret != 0:
return ret
ret = self.obj_cam.MV_CC_SetEnumValue("TriggerSource", 7)
if ret != 0:
return ret
return MV_OK
# 软触发一次
def Trigger_once(self):
if self.b_open_device:
return self.obj_cam.MV_CC_SetCommandValue("TriggerSoftware")
# 获取参数
def Get_parameter(self):
if self.b_open_device:
stFloatParam_FrameRate = MVCC_FLOATVALUE()
memset(byref(stFloatParam_FrameRate), 0, sizeof(MVCC_FLOATVALUE))
stFloatParam_exposureTime = MVCC_FLOATVALUE()
memset(byref(stFloatParam_exposureTime), 0, sizeof(MVCC_FLOATVALUE))
stFloatParam_gain = MVCC_FLOATVALUE()
memset(byref(stFloatParam_gain), 0, sizeof(MVCC_FLOATVALUE))
ret = self.obj_cam.MV_CC_GetFloatValue("AcquisitionFrameRate", stFloatParam_FrameRate)
if ret != 0:
return ret
self.frame_rate = stFloatParam_FrameRate.fCurValue
ret = self.obj_cam.MV_CC_GetFloatValue("ExposureTime", stFloatParam_exposureTime)
if ret != 0:
return ret
self.exposure_time = stFloatParam_exposureTime.fCurValue
ret = self.obj_cam.MV_CC_GetFloatValue("Gain", stFloatParam_gain)
if ret != 0:
return ret
self.gain = stFloatParam_gain.fCurValue
return MV_OK
# 设置参数
def Set_parameter(self, frameRate, exposureTime, gain):
if '' == frameRate or '' == exposureTime or '' == gain:
print('show info', 'please type in the text box !')
return MV_E_PARAMETER
if self.b_open_device:
ret = self.obj_cam.MV_CC_SetEnumValue("ExposureAuto", 0)
time.sleep(0.2)
ret = self.obj_cam.MV_CC_SetFloatValue("ExposureTime", float(exposureTime))
if ret != 0:
print('show error', 'set exposure time fail! ret = ' + To_hex_str(ret))
return ret
ret = self.obj_cam.MV_CC_SetFloatValue("Gain", float(gain))
if ret != 0:
print('show error', 'set gain fail! ret = ' + To_hex_str(ret))
return ret
ret = self.obj_cam.MV_CC_SetFloatValue("AcquisitionFrameRate", float(frameRate))
if ret != 0:
print('show error', 'set acquistion frame rate fail! ret = ' + To_hex_str(ret))
return ret
print('show info', 'set parameter success!')
return MV_OK
# 取图线程函数
def Work_thread(self, winHandle):
stOutFrame = MV_FRAME_OUT()
memset(byref(stOutFrame), 0, sizeof(stOutFrame))
while True:
ret = self.obj_cam.MV_CC_GetImageBuffer(stOutFrame, 1000)
if 0 == ret:
# 拷贝图像和图像信息
if self.buf_save_image is None:
self.buf_save_image = (c_ubyte * stOutFrame.stFrameInfo.nFrameLen)()
self.st_frame_info = stOutFrame.stFrameInfo
# 获取缓存锁
self.buf_lock.acquire()
cdll.msvcrt.memcpy(byref(self.buf_save_image), stOutFrame.pBufAddr, self.st_frame_info.nFrameLen)
cdll.msvcrt.memcpy(byref(self.buf_save_image), stOutFrame.pBufAddr, self.st_frame_info.nFrameLen)
self.stSaveParam.enPixelType = self.st_frame_info.enPixelType # ch:相机对应的像素格式 | en:Camera pixel type
self.stSaveParam.nWidth = self.st_frame_info.nWidth # ch:相机对应的宽 | en:Width
self.stSaveParam.nHeight = self.st_frame_info.nHeight # ch:相机对应的高 | en:Height
self.stSaveParam.nDataLen = self.st_frame_info.nFrameLen
self.stSaveParam.pData = cast(self.buf_save_image, POINTER(c_ubyte))
self.stSaveParam.enImageType = MV_Image_Bmp # ch:需要保存的图像类型 | en:Image format to save
file_path = str(self.st_frame_info.nFrameNum) + ".bmp"
c_file_path = file_path.encode('ascii')
self.stSaveParam.pcImagePath = ctypes.create_string_buffer(c_file_path)
self.stSaveParam.iMethodValue = 1
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.obj_cam.MV_CC_FreeImageBuffer(stOutFrame)
else:
print("no data, ret = " + To_hex_str(ret))
continue
# 使用Display接口显示图像
stDisplayParam = MV_DISPLAY_FRAME_INFO()
memset(byref(stDisplayParam), 0, sizeof(stDisplayParam))
stDisplayParam.hWnd = int(winHandle)
stDisplayParam.nWidth = self.st_frame_info.nWidth
stDisplayParam.nHeight = self.st_frame_info.nHeight
stDisplayParam.enPixelType = self.st_frame_info.enPixelType
stDisplayParam.pData = self.buf_save_image
stDisplayParam.nDataLen = self.st_frame_info.nFrameLen
self.obj_cam.MV_CC_DisplayOneFrame(stDisplayParam)
# 是否退出
if self.b_exit:
if self.buf_save_image is not None:
del self.buf_save_image
break
# 存jpg图像
def Save_jpg(self):
if self.buf_save_image is None:
return
# 获取缓存锁
self.buf_lock.acquire()
file_path = str(self.st_frame_info.nFrameNum) + ".jpg"
c_file_path = file_path.encode('ascii')
stSaveParam = MV_SAVE_IMAGE_TO_FILE_PARAM_EX()
stSaveParam.enPixelType = self.st_frame_info.enPixelType # ch:相机对应的像素格式 | en:Camera pixel type
stSaveParam.nWidth = self.st_frame_info.nWidth # ch:相机对应的宽 | en:Width
stSaveParam.nHeight = self.st_frame_info.nHeight # ch:相机对应的高 | en:Height
stSaveParam.nDataLen = self.st_frame_info.nFrameLen
stSaveParam.pData = cast(self.buf_save_image, POINTER(c_ubyte))
stSaveParam.enImageType = MV_Image_Jpeg # ch:需要保存的图像类型 | en:Image format to save
stSaveParam.nQuality = 80
stSaveParam.pcImagePath = ctypes.create_string_buffer(c_file_path)
stSaveParam.iMethodValue = 1
ret = self.obj_cam.MV_CC_SaveImageToFileEx(stSaveParam)
self.buf_lock.release()
return ret
# 存BMP图像
def Save_Bmp(self):
if 0 == self.buf_save_image:
return
# 获取缓存锁
self.buf_lock.acquire()
# file_path = str(self.st_frame_info.nFrameNum) + ".bmp"
# c_file_path = file_path.encode('ascii')
# stSaveParam.enPixelType = self.st_frame_info.enPixelType # ch:相机对应的像素格式 | en:Camera pixel type
# stSaveParam.nWidth = self.st_frame_info.nWidth # ch:相机对应的宽 | en:Width
# stSaveParam.nHeight = self.st_frame_info.nHeight # ch:相机对应的高 | en:Height
# stSaveParam.nDataLen = self.st_frame_info.nFrameLen
# stSaveParam.pData = cast(self.buf_save_image, POINTER(c_ubyte))
# stSaveParam.enImageType = MV_Image_Bmp # ch:需要保存的图像类型 | en:Image format to save
# stSaveParam.pcImagePath = ctypes.create_string_buffer(c_file_path)
# stSaveParam.iMethodValue = 1
ret = self.obj_cam.MV_CC_SaveImageToFileEx(self.stSaveParam)
self.buf_lock.release()
return ret
-
不通过枚举,直接访问相机的demo
# -*- coding: utf-8 -*-
from MvCameraControl_class import *
import threading
import msvcrt
from ctypes import *
g_bExit = False
# 海康威视直连IPGigE相机
class HikDirectIPCamera(MvCamera):
def __init__(self, deviceIp, netIp):
super().__init__()
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.isOpen = False # 相机是否打开
self.isGrabbing = False # 相机是否正在采集图像
self.is_trigger_mode = True # 是否触发模式
self.g_bExit = 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 log_on(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相机信息
# ch:选择设备并创建句柄 | en:Select device and create handle
ret = self.MV_CC_CreateHandle(self.stDevInfo) # 创建设备句柄
if ret != 0:
print("create handle fail! ret[0x%x]" % ret)
return -1
self.hThreadHandle = threading.Thread(target=self.work_thread) # 创建取流线程
return ret
# 打开设备
def open_device(self):
if self.isOpen:
print("相机已打开!")
return
ret = self.MV_CC_OpenDevice() # 打开设备
if ret != 0:
print("open device fail! ret[0x%x]" % ret)
return -1
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 -1
self.isOpen = True # 相机已打开
print("open device successfully!")
# 获取相机信息
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("enum devices fail!")
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
return 0
else:
print("Warning: Get Packet Size fail! ret[0x%x]" % nPacketSize)
return -1
# 关闭设备
def close_device(self):
if not self.isOpen:
print("相机已关闭!")
return
else:
# 退出取流线程
if self.isGrabbing:
self.MV_CC_StopGrabbing() # 停止取流
self.isGrabbing = False
ret = self.MV_CC_CloseDevice() # 关闭设备
if ret != 0:
return ret
self.isOpen = False
return 0
# 设置连续取流模式(亦即关闭触发模式)
def set_continue_mode(self):
if not self.isOpen:
print("相机未打开!")
return -1
if self.is_trigger_mode:
ret = self.MV_CC_SetEnumValue("TriggerMode", MV_TRIGGER_MODE_OFF) # 关闭触发模式
if ret != 0:
print("设置连续模式失败:", ret)
return -1
self.is_trigger_mode = False
return 0
# 设置软触发模式
def set_software_trigger_mode(self):
ret = self.MV_CC_SetEnumValue("TriggerMode", 1) # 打开触发模式
if ret != 0:
print("设置软触发模式失败:", ret)
return -1
ret = self.MV_CC_SetEnumValue("TriggerSource", 7) # 设置触发源为软触发
if ret != 0:
print("设置触发源失败:", ret)
return ret
return 0
# 开始取流
def start_grabbing(self):
if not self.isOpen:
print("相机未打开!")
return -1
if self.isGrabbing:
print("相机正在取流!")
return
# 启动取流线程
if self.hThreadHandle.is_alive():
print("相机正在取流!")
return
ret = self.MV_CC_StartGrabbing()
if ret != 0:
print("start grabbing fail! ret[0x%x]" % ret)
return -1
try:
self.hThreadHandle.start() # 启动取流线程
# self.hThreadHandle.join() # 等待取流线程结束
self.g_bExit = False # 退出取流的标志
self.isGrabbing = True # 正在取流
return 0
except Exception:
print("error: unable to start thread")
return -1
# 停止取流
def stop_grabbing(self):
if not self.isOpen:
print("相机未打开!")
return -1
if not self.isGrabbing:
print("相机未取流!")
return -1
# 停止取流
ret = self.MV_CC_StopGrabbing()
if ret != 0:
print("stop grabbing fail!")
return -1
# 停止取流线程
self.g_bExit = True # 退出取流的标志
self.isGrabbing = False # 正在取流
return 0
# 取流的线程
def work_thread(self):
stOutFrame = MV_FRAME_OUT()
memset(byref(stOutFrame), 0, sizeof(stOutFrame))
while True:
# ret = self.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))
# nRet = self.MV_CC_FreeImageBuffer(stOutFrame)
# else:
# print("no data[0x%x]" % ret)
# if self.g_bExit:
# break
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)
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.obj_cam.MV_CC_FreeImageBuffer(stOutFrame)
# 释放缓存
if self.b_save_bmp:
self.Save_Bmp()
self.b_save_bmp = False
if self.b_save_jpg:
self.Save_jpg()
self.b_save_jpg = False
self.MV_CC_FreeImageBuffer(stOutFrame)
else:
print("no data, ret = ", ret)
# continue
if self.g_bExit:
break # 退出取流的标志
def Save_Bmp(self):
pass
def Save_jpg(self):
pass
# 解码操作
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
if __name__ == "__main__":
# ch:初始化SDK | en: initialize SDK
MvCamera.MV_CC_Initialize()
cam = HikDirectIPCamera("192.168.100.100", "192.168.100.1") # 相机IP和网卡IP
cam.log_on() # 注册相机
cam.open_device() # 打开设备
cam.set_continue_mode() # 设置连续取流模式
cam.start_grabbing() # 开始取流
while True:
key = input("press 'q' to exit...")
if key == 'q':
cam.stop_grabbing() # 停止取流
break
cam.close_device() # 关闭设备
# ch:销毁句柄 | Destroy handle
ret = cam.MV_CC_DestroyHandle()
if ret != 0:
print("destroy handle fail! ret[0x%x]" % ret)
sys.exit()
# ch:反初始化SDK | en: finalize SDK
MvCamera.MV_CC_Finalize()
设置了两个缓存轮流工作,并加入自定义保存路径的demo:
设置了两个缓存轮流工作,尽量避免线程等待,提高取图速度
# -*- 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.is_writed_buff = 0 # 用于判断哪个缓存被最后一次写入
self.is_busy_buff = 2 # 用于判断哪个缓存忙
self.st_frame_info = None
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.buf1_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.buf1_image = None # 从缓存区中拷贝的图像buffer1,用以保存和处理
self.buf2_image = None # 从缓存区中拷贝的图像buffer2,用以保存和处理
# 强制关闭线程
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.buf1_image is None: # 创建图像的内存空间1
self.buf1_image = (c_ubyte * stOutFrame.stFrameInfo.nFrameLen)()
if self.buf2_image is None: # 创建图像的内存空间2
self.buf2_image = (c_ubyte * stOutFrame.stFrameInfo.nFrameLen)()
self.st_frame_info = stOutFrame.stFrameInfo # 图像的帧信息
self.buf_lock.acquire() # 获取线程锁
# time1 = time.time() # 记录开始时间
if self.is_busy_buff == 2:
cdll.msvcrt.memcpy(byref(self.buf1_image), stOutFrame.pBufAddr, self.st_frame_info.nFrameLen) # 把图像数据拷贝到缓存1
self.is_writed_buff = 1 # 缓存1已经写入
else:
cdll.msvcrt.memcpy(byref(self.buf2_image), stOutFrame.pBufAddr, self.st_frame_info.nFrameLen) # 把图像数据拷贝到缓存2
self.is_writed_buff = 2 # 缓存2已经写入
# print(time.time() - time1)
# ##############################获取显示图像需要的帧数据#############################################
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
if self.is_writed_buff == 1:
self.stDisplayParam.pData = self.buf1_image
else:
self.stDisplayParam.pData = self.buf2_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 # 帧的数据长度
if self.is_writed_buff == 1:
self.stSaveParam.pData = cast(self.buf1_image, POINTER(c_ubyte)) # 图像数据的指针
else:
self.stSaveParam.pData = cast(self.buf2_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 # 高度
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), "buf = ", self.is_writed_buff)
self.buf_lock.release() # 释放线程锁
# 释放缓存
self.MV_CC_FreeImageBuffer(stOutFrame)
else:
print(f"no data, ret = {self.To_hex_str(ret)}")
continue
# 是否退出
if not self.isGrabbing:
print("停止取流")
if self.buf1_image is not None:
del self.buf1_image
if self.buf2_image is not None:
del self.buf2_image
break
def Save_Bmp(self, save_file_path=""):
if self.buf1_image is not None and self.buf2_image is not None:
# 获取缓存锁
self.buf_lock.acquire()
if self.is_writed_buff == 1:
self.is_busy_buff = 1 # 设置缓存1为忙
else:
self.is_busy_buff = 2 # 设置缓存2为忙
if save_file_path == "": # 如果没有设置自定义的文件名
self.save_file_path += ".bmp" # 就使用默认的文件名(self.save_pre_path + str(self.st_frame_info.nFrameNum))
else:
self.save_file_path = 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文件成功!")
# time.sleep(2)
return ret
else:
print("无可用的图像缓存!")
return -1
def Save_bmp_opencv(self, save_file_path=""):
"""
保存图像到文件,速度比官方的快一些
:return:
"""
if self.buf1_image is not None and self.buf2_image is not None:
if save_file_path == "": # 如果没有设置自定义的文件名
self.save_file_path += ".bmp" # 就使用默认的文件名(self.save_pre_path + str(self.st_frame_info.nFrameNum))
else:
self.save_file_path = save_file_path + ".bmp" # 使用自定义的文件名
path = self.save_file_path.encode('utf-8')
image_data = self.fromBuffer2RGB8()
# 获取缓存锁
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, save_file_path=""):
if self.buf1_image is not None and self.buf2_image is not None:
# 获取缓存锁
self.buf_lock.acquire()
if self.is_writed_buff == 1:
self.is_busy_buff = 1 # 设置缓存1为忙
else:
self.is_busy_buff = 2 # 设置缓存2为忙
# self.buf_lock.release()
if save_file_path == "": # 如果没有设置自定义的文件名
self.save_file_path += ".jpg" # 就使用默认的文件名(self.save_pre_path + str(self.st_frame_info.nFrameNum))
else:
self.save_file_path = 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, save_file_path=""):
if self.buf1_image is not None and self.buf2_image is not None:
if save_file_path == "": # 如果没有设置自定义的文件名
self.save_file_path += ".jpg" # 就使用默认的文件名(self.save_pre_path + str(self.st_frame_info.nFrameNum))
else:
self.save_file_path = save_file_path + ".jpg" # 使用自定义的文件名
path = self.save_file_path.encode('utf-8')
image_data = self.fromBuffer2RGB8()
# 获取缓存锁
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 fromBuffer2RGB8(self):
if self.buf1_image is not None and self.buf2_image is not None:
# 获取缓存锁
self.buf_lock.acquire() # 转换bayer数据到RGB格式#########
if self.is_writed_buff == 1:
self.is_busy_buff = 1 # 设置缓存1为忙
image_data = np.frombuffer(self.buf1_image, dtype=np.uint8).reshape((self.stConvertParam.nHeight, self.stConvertParam.nWidth))
else:
self.is_busy_buff = 2 # 设置缓存2为忙
image_data = np.frombuffer(self.buf2_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
while i < 3:
time.sleep(2)
path = str(time.strftime("%Y%m%d%H%M%S", time.localtime(time.time()))) # 保存图像的自定义路径名
cam.Save_Bmp(path) # 保存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()
以上代码均已成功运行,供大家参考。