2022-05-16 OpenCV Python 接入海康工业相机

背景

Python OpenCV连接海康工业相机做图像处理

环境

Python:3.9.9
OpenCV:4.5.5
numpy:1.19.3


2022-05-16_112138.png

相机

海康:MV-CE200-10GC
协议:GigE


2022-05-16_112542.png

MVS

海康网站下载的相机调试工具,可以预览相机和设置相机的一些参数。

网站:https://www.hikrobotics.com/cn

下载你相机型号相关的安装包,这个要是有困难可以找海康售后帮忙,你提供的相机型号,工作人员会知道你下载和操作,海康售后挺好的,当初笔者不熟悉的时候也是很耐心的指导我操作。

主机通过网线连接相机,主机的IP地址选择自动分配,这样就会和相机在一个网段内。打开MVS工具,会识别主机中的网卡,如果该网卡中有相机连接,会显示出来。

2022-05-16_114020.png

选中相机后点击连接按钮,可以连接到该相机

2022-05-16_114116.png

连接成功后,界面右侧会出现相机的属性列表

2022-05-16_114225.png

点击预览按钮,界面中间会出现相机的预览画面

2022-05-16_114506.png

笔者记得第一次连接的时候,好像预览画面中什么都没有是黑色的,如果你也是这样不必着急,因为相机需要设置。首先相机上面有1个调亮度和1个调焦距的物理旋钮。你需要将物理按钮的属性调整到合适位置,确保那个环节没有问题。再一个这个工具中可以设置相机的属性,笔者这里在相机的默认属性基础上调节了4个属性:

常用属性
* 自动曝光:连续
* 自动增益:连续
* 亮度:100
高级属性
* 像素格式:UV 422 Packed

完成上述操作,应该能正常预览相机的画面。

Python

在Python中连接工业相机获取帧图像处理,我们首先需要连接相机,然后获取到每一帧图像,将图像送给业务层。

在Python中需要连接相机,需要借助海康提供的.dll文件和其API文件

这些API文件和.dll文件,在安装完MVS软件后会获得。


2022-05-16_115925.png
2022-05-16_120008.png

笔者的工程结构如下:


2022-05-16_115825.png

将5个API文件和.dll文件夹里面的所有文件都拷贝到你的工程中,存放路径你可以自己定义。但是在MvCameraControl_class.py文件中,会有一行代码是输入MvCameraControl.dll文件路径的:

from ctypes import *

from org.venus.std.src.pro.input.hk2000w.api.CameraParams_const import MV_ACCESS_Exclusive

MvCamCtrldll = WinDLL("./api/dll/MvCameraControl.dll")

这里请根据你自己的存放目录填写。补充一下,在Python中填写相对路径时在路径前面加./,就是你项目运行的根目录。这个连接代码是参考示例程序的,示例程序里面填写的路径就是一个MvCameraControl.dll文件,但是笔者在使用过程中只导入这一个文件程序运行有错误,肯定的是这个文件找到了,但是这个文件中也引用到了其他的.dll文件,有人说通过什么工具之类的可以查看这个.dll文件引用到了其他的具体哪一个.dll文件,笔者对那个操作不熟悉,就没有那样做,而是把那个.dll文件夹中所有的文件都拷贝过来,约60M,省事。

将这些文件拷贝到项目中就可以开始写代码了:

import msvcrt
import sys
import threading
import time
from ctypes import POINTER, sizeof, byref, memset, c_ubyte, cdll, cast, c_bool

import cv2 as cv
import numpy as np

from org.venus.std.src.config import constants
from org.venus.std.src.pro.input.hk2000w import watch
from org.venus.std.src.pro.input.hk2000w.api.CameraParams_const import MV_GIGE_DEVICE, MV_USB_DEVICE, MV_ACCESS_Exclusive
from org.venus.std.src.pro.input.hk2000w.api.CameraParams_header import MV_CC_DEVICE_INFO_LIST, MV_CC_DEVICE_INFO, \
    MV_TRIGGER_MODE_OFF, MV_FRAME_OUT, MV_EXPOSURE_AUTO_MODE_CONTINUOUS, MV_GAIN_MODE_CONTINUOUS
from org.venus.std.src.pro.input.hk2000w.api.MvCameraControl_class import MvCamera

from org.venus.std.src.pro.input.hk2000w.api.PixelType_header import PixelType_Gvsp_YUV422_Packed


# 为线程定义一个函数
def work_thread(cam=0, pData=0, nDataSize=0):
    stOutFrame = MV_FRAME_OUT()
    memset(byref(stOutFrame), 0, sizeof(stOutFrame))
    while True:
        startFrame = time.perf_counter()
        ret = cam.MV_CC_GetImageBuffer(stOutFrame, 1000)
        # print("相机像素格式(PixelType_header.py文件中根据编码找到对应的格式)",stOutFrame.stFrameInfo.enPixelType)
        if None != stOutFrame.pBufAddr and 0 == ret and stOutFrame.stFrameInfo.enPixelType == 17301505:
            print("get one frame: Width[%d], Height[%d], nFrameNum[%d]" % (
                stOutFrame.stFrameInfo.nWidth, stOutFrame.stFrameInfo.nHeight, stOutFrame.stFrameInfo.nFrameNum))
            pData = (c_ubyte * stOutFrame.stFrameInfo.nWidth * stOutFrame.stFrameInfo.nHeight)()
            cdll.msvcrt.memcpy(byref(pData), stOutFrame.pBufAddr,
                               stOutFrame.stFrameInfo.nWidth * stOutFrame.stFrameInfo.nHeight)
            data = np.frombuffer(pData, count=int(stOutFrame.stFrameInfo.nWidth * stOutFrame.stFrameInfo.nHeight),
                                 dtype=np.uint8)
            image_control(data=data, stFrameInfo=stOutFrame.stFrameInfo)
        elif None != stOutFrame.pBufAddr and 0 == ret and stOutFrame.stFrameInfo.enPixelType == 17301514:
            print("get one frame: Width[%d], Height[%d], nFrameNum[%d]" % (
                stOutFrame.stFrameInfo.nWidth, stOutFrame.stFrameInfo.nHeight, stOutFrame.stFrameInfo.nFrameNum))
            pData = (c_ubyte * stOutFrame.stFrameInfo.nWidth * stOutFrame.stFrameInfo.nHeight)()
            cdll.msvcrt.memcpy(byref(pData), stOutFrame.pBufAddr,
                               stOutFrame.stFrameInfo.nWidth * stOutFrame.stFrameInfo.nHeight)
            data = np.frombuffer(pData, count=int(stOutFrame.stFrameInfo.nWidth * stOutFrame.stFrameInfo.nHeight),
                                 dtype=np.uint8)
            image_control(data=data, stFrameInfo=stOutFrame.stFrameInfo)
        elif None != stOutFrame.pBufAddr and 0 == ret and stOutFrame.stFrameInfo.enPixelType == 35127316:
            print("get one frame: Width[%d], Height[%d], nFrameNum[%d]" % (
                stOutFrame.stFrameInfo.nWidth, stOutFrame.stFrameInfo.nHeight, stOutFrame.stFrameInfo.nFrameNum))
            pData = (c_ubyte * stOutFrame.stFrameInfo.nWidth * stOutFrame.stFrameInfo.nHeight * 3)()
            cdll.msvcrt.memcpy(byref(pData), stOutFrame.pBufAddr,
                               stOutFrame.stFrameInfo.nWidth * stOutFrame.stFrameInfo.nHeight * 3)
            data = np.frombuffer(pData, count=int(stOutFrame.stFrameInfo.nWidth * stOutFrame.stFrameInfo.nHeight * 3),
                                 dtype=np.uint8)
            image_control(data=data, stFrameInfo=stOutFrame.stFrameInfo)
        elif None != stOutFrame.pBufAddr and 0 == ret and stOutFrame.stFrameInfo.enPixelType == 34603039:
            print("get one frame: Width[%d], Height[%d], nFrameNum[%d]" % (
                stOutFrame.stFrameInfo.nWidth, stOutFrame.stFrameInfo.nHeight, stOutFrame.stFrameInfo.nFrameNum))
            pData = (c_ubyte * stOutFrame.stFrameInfo.nWidth * stOutFrame.stFrameInfo.nHeight * 2)()
            cdll.msvcrt.memcpy(byref(pData), stOutFrame.pBufAddr,
                               stOutFrame.stFrameInfo.nWidth * stOutFrame.stFrameInfo.nHeight * 2)
            data = np.frombuffer(pData, count=int(stOutFrame.stFrameInfo.nWidth * stOutFrame.stFrameInfo.nHeight * 2),
                                 dtype=np.uint8)
            image_control(data=data, stFrameInfo=stOutFrame.stFrameInfo)
        else:
            print("no data[0x%x]" % ret)
        nRet = cam.MV_CC_FreeImageBuffer(stOutFrame)

        endFrame = time.perf_counter()
        print("相机一帧时间",str(endFrame-startFrame))


# 需要显示的图像数据转换
def image_control(data, stFrameInfo):
    if stFrameInfo.enPixelType == 17301505:
        image = data.reshape((stFrameInfo.nHeight, stFrameInfo.nWidth))
        image_show(image=image, name=stFrameInfo.nHeight)
    elif stFrameInfo.enPixelType == 17301514:
        data = data.reshape(stFrameInfo.nHeight, stFrameInfo.nWidth, -1)
        image = cv.cvtColor(data, cv.COLOR_BAYER_GB2RGB)
        image_show(image=image, name=stFrameInfo.nHeight)
    elif stFrameInfo.enPixelType == 35127316:
        data = data.reshape(stFrameInfo.nHeight, stFrameInfo.nWidth, -1)
        image = cv.cvtColor(data, cv.COLOR_RGB2BGR)
        image_show(image=image, name=stFrameInfo.nHeight)
    elif stFrameInfo.enPixelType == 34603039:
        data = data.reshape(stFrameInfo.nHeight, stFrameInfo.nWidth, -1)
        image = cv.cvtColor(data, cv.COLOR_YUV2BGR_Y422)
        image_show(image=image, name=stFrameInfo.nHeight)


# 显示图像
def image_show(image, name):
    startTime = time.perf_counter()
    infos = watch.run(image)
    if infos is None:
        print("没有识别到FJ信息")
        pass
    else:
        print("FJ信息数量",len(infos))
        for info in infos:
            print("FJ信息", info)
            pass
    endTime = time.perf_counter()
    #我们可以使用time.perf_counter()方法来查找程序的执行时间。
    #方法time.perf_counter()返回以秒为单位的时间浮点值
    print("拿到图像时间", str(startTime),"图像处理完毕时间", str(startTime),"耗时", str(endTime-startTime))
    if constants.WaitNextFrame:
        cv.waitKey()

def run():
    deviceList = MV_CC_DEVICE_INFO_LIST()
    tlayerType = MV_GIGE_DEVICE | MV_USB_DEVICE

    # ch:枚举设备 | en:Enum device
    ret = MvCamera.MV_CC_EnumDevices(tlayerType, deviceList)
    if ret != 0:
        print("enum devices fail! ret[0x%x]" % ret)
        sys.exit()

    if deviceList.nDeviceNum == 0:
        print("find no device!")
        sys.exit()

    print("Find %d devices!" % deviceList.nDeviceNum)

    for i in range(0, deviceList.nDeviceNum):
        mvcc_dev_info = cast(deviceList.pDeviceInfo[i], POINTER(MV_CC_DEVICE_INFO)).contents
        if mvcc_dev_info.nTLayerType == MV_GIGE_DEVICE:
            print("\ngige device: [%d]" % i)
            strModeName = ""
            for per in mvcc_dev_info.SpecialInfo.stGigEInfo.chModelName:
                strModeName = strModeName + chr(per)
            print("device model name: %s" % strModeName)

            nip1 = ((mvcc_dev_info.SpecialInfo.stGigEInfo.nCurrentIp & 0xff000000) >> 24)
            nip2 = ((mvcc_dev_info.SpecialInfo.stGigEInfo.nCurrentIp & 0x00ff0000) >> 16)
            nip3 = ((mvcc_dev_info.SpecialInfo.stGigEInfo.nCurrentIp & 0x0000ff00) >> 8)
            nip4 = (mvcc_dev_info.SpecialInfo.stGigEInfo.nCurrentIp & 0x000000ff)
            print("current ip: %d.%d.%d.%d\n" % (nip1, nip2, nip3, nip4))
        elif mvcc_dev_info.nTLayerType == MV_USB_DEVICE:
            print("\nu3v device: [%d]" % i)
            strModeName = ""
            for per in mvcc_dev_info.SpecialInfo.stUsb3VInfo.chModelName:
                if per == 0:
                    break
                strModeName = strModeName + chr(per)
            print("device model name: %s" % strModeName)

            strSerialNumber = ""
            for per in mvcc_dev_info.SpecialInfo.stUsb3VInfo.chSerialNumber:
                if per == 0:
                    break
                strSerialNumber = strSerialNumber + chr(per)
            print("user serial number: %s" % strSerialNumber)

    # nConnectionNum = input("please input the number of the device to connect:") #输入一个相机编号进行连接

    if int(constants.ConnectionCameraNum) >= deviceList.nDeviceNum:
        print("intput error!")
        sys.exit()

    # ch:创建相机实例 | en:Creat Camera Object
    cam = MvCamera()

    # ch:选择设备并创建句柄 | en:Select device and create handle
    stDeviceList = cast(deviceList.pDeviceInfo[int(constants.ConnectionCameraNum)], POINTER(MV_CC_DEVICE_INFO)).contents

    ret = cam.MV_CC_CreateHandle(stDeviceList)
    if ret != 0:
        print("create handle fail! ret[0x%x]" % ret)
        sys.exit()

    # ch:打开设备 | en:Open device
    ret = cam.MV_CC_OpenDevice(MV_ACCESS_Exclusive, 0)
    if ret != 0:
        print("open device fail! ret[0x%x]" % ret)
        sys.exit()

    # ch:探测网络最佳包大小(只对GigE相机有效) | en:Detection network optimal package size(It only works for the GigE camera)
    if stDeviceList.nTLayerType == MV_GIGE_DEVICE:
        nPacketSize = cam.MV_CC_GetOptimalPacketSize()
        if int(nPacketSize) > 0:
            ret = cam.MV_CC_SetIntValue("GevSCPSPacketSize", nPacketSize)
            if ret != 0:
                print("Warning: Set Packet Size fail! ret[0x%x]" % ret)
        else:
            print("Warning: Get Packet Size fail! ret[0x%x]" % nPacketSize)

    stBool = c_bool(False)
    ret = cam.MV_CC_GetBoolValue("AcquisitionFrameRateEnable", stBool)
    if ret != 0:
        print("get AcquisitionFrameRateEnable fail! ret[0x%x]" % ret)
        sys.exit()

    # ch:设置触发模式为off | en:Set trigger mode as off
    ret = cam.MV_CC_SetEnumValue("TriggerMode", MV_TRIGGER_MODE_OFF)
    if ret != 0:
        print("set trigger mode fail! ret[0x%x]" % ret)
        sys.exit()


    '''
    设置一些属性:
    自动曝光:连续
    亮度:100
    自动增益:连续
    像素格式:YUV 422 Packed
    '''

    # ch:设置自动曝光为连续
    ret = cam.MV_CC_SetEnumValue("ExposureAuto", MV_EXPOSURE_AUTO_MODE_CONTINUOUS)
    if ret != 0:
        print("设置自动曝光为连续 失败! ret[0x%x]" % ret)
        sys.exit()

    # ch:设置自动增益为连续
    ret = cam.MV_CC_SetEnumValue("GainAuto", MV_GAIN_MODE_CONTINUOUS)
    if ret != 0:
        print("设置自动增益为连续 失败! ret[0x%x]" % ret)
        sys.exit()

    # ch:设置亮度为100
    ret = cam.MV_CC_SetIntValue("Brightness", constants.Brightness)
    if ret != 0:
        print("设置亮度为100 失败! ret[0x%x]" % ret)
        sys.exit()

    # ch:设置像素格式为PixelType_Gvsp_YUV422_Packed
    ret = cam.MV_CC_SetEnumValue("PixelFormat", PixelType_Gvsp_YUV422_Packed)
    if ret != 0:
        print("设置像素格式为PixelType_Gvsp_YUV422_Packed 失败! ret[0x%x]" % ret)
        sys.exit()

    # ch:开始取流 | en:Start grab image
    ret = cam.MV_CC_StartGrabbing()
    if ret != 0:
        print("start grabbing fail! ret[0x%x]" % ret)
        sys.exit()

    try:
        hThreadHandle = threading.Thread(target=work_thread, args=(cam, None, None))
        hThreadHandle.start()
    except:
        print("error: unable to start thread")

    print("press a key to stop grabbing.")
    msvcrt.getch()

    g_bExit = True
    hThreadHandle.join()

    # ch:停止取流 | en:Stop grab image
    ret = cam.MV_CC_StopGrabbing()
    if ret != 0:
        print("stop grabbing fail! ret[0x%x]" % ret)
        sys.exit()

    # ch:关闭设备 | Close device
    ret = cam.MV_CC_CloseDevice()
    if ret != 0:
        print("close deivce fail! ret[0x%x]" % ret)
        sys.exit()

    # ch:销毁句柄 | Destroy handle
    ret = cam.MV_CC_DestroyHandle()
    if ret != 0:
        print("destroy handle fail! ret[0x%x]" % ret)
        sys.exit()

if __name__ =="__main__":
    print("连接hk工业相机图像")
    run()

上述代码是工程结构中/org/venus/std/src/pro/input/hk2000w/main.py文件的内容。代码不多,可以简单解读一下。

程序入口main函数中执行run函数,在run函数中查找所有的相机设备,虽然我平常只是连接着一个相机,但是的你主机中可以连接多个相机,在找到所有的相机后,相机列表中每个相机会有个编号标识符。通过相机编号连接相机创建句柄并打开相机,然后为相机设置一些属性,就可以开始获取相机的视频流了,run函数中通过如下2行代码将取流操作执行到work_thread线程中,主线程就等待取流线程的工作,结束后就关闭设备,销毁句柄等释放资源操作。

hThreadHandle = threading.Thread(target=work_thread, args=(cam, None, None))
hThreadHandle.start()

work_thread取流线程是一个while True操作,在获取视频流时后根据上文中设置相机的一个属性(像素格式),来执行不同的操作,因为不同的像素格式,视频流转换为图像的操作是不一样的,所以上文中设置像素格式的操作也不是任意设置的,笔者设置的值YUV 422 Packed是因为在示例程序中列出了4种像素格式的转换代码,YUV 422 Packed是其中的一个,所以笔者使用了这一个,代码中使用的匹配方式是如(34603039)类型的数据,这个数据对应的像素格式可以在API文件PixelType_header.py中进行匹配,看看你使用的究竟是哪种像素格式。在对像素格式匹配后,程序执行到image_control函数,将视频流数据传送到image_control函数,继续对像素格式进行匹配,并将视频流转换成图像对象,程序继续执行到image_show函数,这个函数中传递过来的就是图像对象了,可以将图像对象传递到业务层进行处理。

补充一下:在代码中连接相机后可以设置一些属性,是示例代码中只有一个设置触发模式的示例,比如笔者还需要设置一些属性:自动曝光:连续/亮度:100/自动增益:连续/像素格式:YUV 422 Packed,设置属性是调用MvCameraControl_class.py文件中提供的接口函数,接口中并没有直接设置某一个属性的函数,接口是设置某一类型的调用方式。大致有MV_CC_SetIntValue设置整形数据,MV_CC_SetEnumValue设置枚举数据,MV_CC_SetFloatValue设置小数数据,MV_CC_SetBoolValue设置开关数据,MV_CC_SetStringValue设置字符串数据等。这里不过多阐述,当你有这个业务时可以自行研究。

根据笔者想要设置的属性来看,要用到设置枚举和整形的接口。调用设置枚举的接口,需要传入一个key和一个枚举,枚举就是具体的值,这个可以在CameraParams_header.py文件中找到你所要设置的参数的枚举值。那么这个key哪里来呢?key是个字符串,笔者尝试像实例中设置触发模式一样TriggerMode表示触发模式,那么我想设置自动曝光模式应该就是ExposureAutoMode,但是执行时这里却有错误,很显然没有这个key。那么就要再次用到MVS工具了,在工具中连接相机后找到属性树菜单,列表里面都是英文名称对应着相机的属性,找到你要设置相机参数的英文名称,比如截图中的自动曝光就是Exposure Auto属性,点击属性在列表下方会出现该属性的信息,其中有个Node Name属性是ExposureAuto,这个属性就是我们在代码中使用API设置相机属性的key,其他属性同理都可以在这里找到。

2022-05-16_135920.png

至此Python OpenCV中连接海康工业相机就可以调试通过。

项目地址:https://gitee.com/premeditate/Tools-Camera

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容