海康威视GigE工业相机的python调用demo

  • 下载安装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()

以上代码均已成功运行,供大家参考。

后续的图像处理和测试:海康威视GidE工业相机的Bayer格式图像数据处理_rggb bayer格式-CSDN博客

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

深蓝海拓

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值