利用python加opencv与海康工业相机交互。(得到供opencv处理的数据)


前言

因为项目的原因,笔者需要利用程序来实时调用海康工业相机,而不是简单的利用海康的驱动来调用相机。
在经历了反复的踩坑填坑之后,笔者总结了利用c#和python调用海康相机的两种方法


一、准备工作

无论是用c#还是python,都是要通过海康的SDK来进行二次开发的。海康的SDK相当于一个库,里面有各种用于海康相机的函数,这些函数被封装成供各大编程语言使用。所以,要想利用python或者c#与海康进行实时交互,就得像学习SDK中的各个函数的用法。但好在海康提供了各个编程语言的例程,可以让我们对照例程快速上手。海康的例程一般放在海康相机驱动软件的安装文件夹下,具体路径为
…\MVS\Development\Samples
里面有大部分主流语言的例程
在这里插入图片描述
除此之外,海康还提供了SDK的说明文档,文件路径在
…\MVS\Development\Documentations

在这里插入图片描述
c#可以参考.net版本的,python和c可以参考c版本的(两者差别都不大)

二、c#调用

在c#中的例程里面,有很多程序
在这里插入图片描述
但除了basicdemo有界面,其他的都是控制台程序,显示不了图片。
而且basicdemo基本涵盖了海康相机使用的各个方面,所以推荐优先学习basicdemo程序。
该程序的主界面如下图所示。
在这里插入图片描述
需要注意的是,虽然这里能显示图片,但得到的图片数据仍然是海康的图片格式,不是opencv可以处理的mat格式。笔者尝试了很多次,仍然没法将其转化为可以供opencv处理的数据格式。这一功能我在python中实现了,在c#中还没找到方法(应该是因为我对c#不是很熟),对c#比较熟悉的可以参考我的python代码进行改造。

三、python调用

python的话,参考例程中的grabimg程序,对里面的线程进行改造即可。但这里也有一些问题需要注意
首先,官网给的源程序只能在控制台显示得到的图片帧的长和宽,并没有取得图片的数据。但这明显不是我们想要的。所以需要对其进行改造,改造的部分主要在线程函数中

def work_thread(cam=0, pData=0, nDataSize=0):
    stOutFrame = MV_FRAME_OUT()  
    memset(byref(stOutFrame), 0, sizeof(stOutFrame))
    while True:
        ret = cam.MV_CC_GetImageBuffer(stOutFrame, 1000)
        if None != stOutFrame.pBufAddr and 0 == ret:
            print ("get one frame: Width[%d], Height[%d], nFrameNum[%d]"  % (stOutFrame.stFrameInfo.nWidth, stOutFrame.stFrameInfo.nHeight, stOutFrame.stFrameInfo.nFrameNum))
            nRet = cam.MV_CC_FreeImageBuffer(stOutFrame)
        else:
            print ("no data[0x%x]" % ret)
        if g_bExit == True:
            break

这是官方的线程函数。我们需要在里面进行改造。
改造之后的函数为

def work_thread2(cam=0, pData=0, nDataSize=0):
    # 输出帧的信息
    stFrameInfo = MV_FRAME_OUT_INFO_EX()
    # void *memset(void *s, int ch, size_t n);
    # 函数解释:将s中当前位置后面的n个字节 (typedef unsigned int size_t )用 ch 替换并返回 s 
    # memset:作用是在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法
    # byref(n)返回的相当于C的指针右值&n,本身没有被分配空间
    # 此处相当于将帧信息全部清空了
    memset(byref(stFrameInfo), 0, sizeof(stFrameInfo))

    while True:
        temp = np.asarray(pData)  # 将c_ubyte_Array转化成ndarray得到(3686400,)
        # print(temp)
        # print(temp.shape)
        temp = temp.reshape((2048, 1024, 3))  # 根据自己分辨率进行转化
        
        # temp = cv2.cvtColor(temp, cv2.COLOR_BGR2RGB)  # 这一步获取到的颜色不对,因为默认是BRG,要转化成RGB,颜色才正常
        gray = cv2.cvtColor(temp,cv2.COLOR_BGR2GRAY)
        ret,binary = cv2.threshold(gray,130,255,cv2.THRESH_BINARY)
        cv2.namedWindow("binary", cv2.WINDOW_NORMAL)
        cv2.namedWindow("ori", cv2.WINDOW_NORMAL)
        cv2.imshow('binary',binary)
        cv2.imshow("ori", temp)
        
        if cv2.waitKey(1) & 0xFF == ord('q'):
            
            break
        # 采用超时机制获取一帧图片,SDK内部等待直到有数据时返回,成功返回0
        ret = cam.MV_CC_GetOneFrameTimeout(pData, nDataSize, stFrameInfo, 1000)
        if ret == 0:
            print("get one frame: Width[%d], Height[%d], nFrameNum[%d]" % (
            stFrameInfo.nWidth, stFrameInfo.nHeight, stFrameInfo.nFrameNum))
        else:
            print("no data[0x%x]" % ret)
        if g_bExit == True:
            break
    cv2.waitKey()    

除此之外,还有几个点需要注意
第一个

sys.path.append(r"**\MvImport")

因为需要用到SDK的接口函数,所以需要导入相应的库,官方的路径是这样的,这是因为这个包在程序的同一个文件夹下,所以前面的都可以省略,但我们使用的时候,最好把它的绝对路径给写上,我的路径是这样的,可以参考

sys.path.append(r"C:\Users\Administrator\Desktop\python_vscode\MvImport")

第二个
在线程函数之外,还有几个关键语句需要注意

扫描二维码关注公众号,回复: 15470083 查看本文章
# ch:获取数据包大小 | en:Get payload size
    stParam =  MVCC_INTVALUE()
    #csharp中没有memset函数,用什么代替?
    memset(byref(stParam), 0, sizeof(MVCC_INTVALUE))
    # MV_CC_GetIntValue,获取Integer属性值,handle [IN] 设备句柄  
    # strKey [IN] 属性键值,如获取宽度信息则为"Width"  
    # pIntValue [IN][OUT] 返回给调用者有关相机属性结构体指针 
    # 得到图片尺寸,这一句很关键
    # payloadsize,为流通道上的每个图像传输的最大字节数,相机的PayloadSize的典型值是(宽x高x像素大小),此时图像没有附加任何额外信息
    
    ret = cam.MV_CC_GetIntValue("PayloadSize", stParam)

cam.MV_CC_GetIntValue(“PayloadSize”, stParam)这一句中的PayloadSize是流通道上的每个图像传输的最大字节数,相机的PayloadSize的典型值是(宽x高x像素大小),这是一个很关键的步骤,官方例子中并没有获得这个值。

nPayloadSize = stParam.nCurValue
 
    # ch:开始取流 | en:Start grab image
    ret = cam.MV_CC_StartGrabbing()
    if ret != 0:
        print ("start grabbing fail! ret[0x%x]" % ret)
        sys.exit()
    #  关键句,官方没有这个句子,抓取的图片数据是空的,CSharp中怎么实现这句话。
    data_buf = (c_ubyte * nPayloadSize)()

data_buf = (c_ubyte * nPayloadSize)()这一句话将PayloadSize的uint数据转为可供numpy处理的数据,后面就可以用numpy将其转化为numpy数组格式。

第三个,线程的使用
官方例子是

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

此处需要改成

try:
        hThreadHandle = threading.Thread(target=work_thread2, args=(cam, data_buf, nPayloadSize))
        hThreadHandle.start()

在这里,有些代码可能会在data_buf前面加上byteref,如果这样做的话,就会将数据转为浮点型,而opencv需要的是整型,会报错,所以这里就不需要转化了

四、 完整代码

# -- coding: utf-8 --
 
import sys
import threading
import msvcrt
import numpy as np
from cv2 import cv2
from ctypes import *
 
sys.path.append(r"C:\Users\Administrator\Desktop\python_vscode\MvImport")
from MvCameraControl_class import *
 
g_bExit = False
 
# 这是官方给的线程,只能捕获帧的信息,类似于get one frame: Width[3072], Height[2048], nFrameNum[711]
# 不能得到帧的数据
def work_thread(cam=0, pData=0, nDataSize=0):
    stFrameInfo = MV_FRAME_OUT_INFO_EX()
    memset(byref(stFrameInfo), 0, sizeof(stFrameInfo))
    while True:
        ret = cam.MV_CC_GetOneFrameTimeout(pData, nDataSize, stFrameInfo, 1000)
        if ret == 0:
            print ("get one frame: Width[%d], Height[%d], nFrameNum[%d]"  % (stFrameInfo.nWidth, stFrameInfo.nHeight, stFrameInfo.nFrameNum))
        else:
            print ("no data[0x%x]" % ret)
        if g_bExit == True:
                break
 
# 自己在这个线程中修改,可以将相机获得的数据转换成opencv支持的格式,然后再用opencv操作
def work_thread2(cam=0, pData=0, nDataSize=0):
    # 输出帧的信息
    stFrameInfo = MV_FRAME_OUT_INFO_EX()
    # void *memset(void *s, int ch, size_t n);
    # 函数解释:将s中当前位置后面的n个字节 (typedef unsigned int size_t )用 ch 替换并返回 s 
    # memset:作用是在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法
    # byref(n)返回的相当于C的指针右值&n,本身没有被分配空间
    # 此处相当于将帧信息全部清空了
    memset(byref(stFrameInfo), 0, sizeof(stFrameInfo))

    while True:
        temp = np.asarray(pData)  # 将c_ubyte_Array转化成ndarray得到(3686400,)
        # print(temp)
        # print(temp.shape)
        temp = temp.reshape((2048, 1024, 3))  # 根据自己分辨率进行转化
        
        # temp = cv2.cvtColor(temp, cv2.COLOR_BGR2RGB)  # 这一步获取到的颜色不对,因为默认是BRG,要转化成RGB,颜色才正常
        gray = cv2.cvtColor(temp,cv2.COLOR_BGR2GRAY)
        ret,binary = cv2.threshold(gray,130,255,cv2.THRESH_BINARY)
        cv2.namedWindow("binary", cv2.WINDOW_NORMAL)
        cv2.namedWindow("ori", cv2.WINDOW_NORMAL)
        cv2.imshow('binary',binary)
        cv2.imshow("ori", temp)
        
        if cv2.waitKey(1) & 0xFF == ord('q'):
            
            break
        # 采用超时机制获取一帧图片,SDK内部等待直到有数据时返回,成功返回0
        ret = cam.MV_CC_GetOneFrameTimeout(pData, nDataSize, stFrameInfo, 1000)
        if ret == 0:
            print("get one frame: Width[%d], Height[%d], nFrameNum[%d]" % (
            stFrameInfo.nWidth, stFrameInfo.nHeight, stFrameInfo.nFrameNum))
        else:
            print("no data[0x%x]" % ret)
        if g_bExit == True:
            break
    cv2.waitKey()    

if __name__ == "__main__":
    # 获得设备信息
    deviceList = MV_CC_DEVICE_INFO_LIST()
    tlayerType = MV_GIGE_DEVICE | MV_USB_DEVICE
    
    # ch:枚举设备 | en:Enum device
    # nTLayerType [IN] 枚举传输层 ,pstDevList [OUT] 设备列表 
    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)
            # 输出设备ID
            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))
        # 输出USB接口的信息
        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(nConnectionNum) >= deviceList.nDeviceNum:
        print ("intput error!")
        sys.exit()
 
    # ch:创建相机实例 | en:Creat Camera Object
    cam = MvCamera()
    
    # ch:选择设备并创建句柄 | en:Select device and create handle
    # cast(typ, val),这个函数是为了检查val变量是typ类型的,但是这个cast函数不做检查,直接返回val
    stDeviceList = cast(deviceList.pDeviceInfo[int(nConnectionNum)], 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)
 
    # 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()

    # 从这开始,获取图片数据
    # ch:获取数据包大小 | en:Get payload size
    stParam =  MVCC_INTVALUE()
    #csharp中没有memset函数,用什么代替?
    memset(byref(stParam), 0, sizeof(MVCC_INTVALUE))
    # MV_CC_GetIntValue,获取Integer属性值,handle [IN] 设备句柄  
    # strKey [IN] 属性键值,如获取宽度信息则为"Width"  
    # pIntValue [IN][OUT] 返回给调用者有关相机属性结构体指针 
    # 得到图片尺寸,这一句很关键
    # payloadsize,为流通道上的每个图像传输的最大字节数,相机的PayloadSize的典型值是(宽x高x像素大小),此时图像没有附加任何额外信息
    
    ret = cam.MV_CC_GetIntValue("PayloadSize", stParam)
    if ret != 0:
        print ("get payload size fail! ret[0x%x]" % ret)
        sys.exit()
    #关键句,官方没有这个句子,抓取的图片数据是空的,nCurValue是int64
    nPayloadSize = stParam.nCurValue
 
    # ch:开始取流 | en:Start grab image
    ret = cam.MV_CC_StartGrabbing()
    if ret != 0:
        print ("start grabbing fail! ret[0x%x]" % ret)
        sys.exit()
    #  关键句,官方没有这个句子,抓取的图片数据是空的,CSharp中怎么实现这句话。
    data_buf = (c_ubyte * nPayloadSize)()
    #  date_buf前面的转化不用,不然报错,因为转了是浮点型
    try:
        hThreadHandle = threading.Thread(target=work_thread2, args=(cam, data_buf, nPayloadSize))
        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)
        del data_buf
        sys.exit()
 
    # ch:关闭设备 | Close device
    ret = cam.MV_CC_CloseDevice()
    if ret != 0:
        print ("close deivce fail! ret[0x%x]" % ret)
        del data_buf
        sys.exit()
 
    # ch:销毁句柄 | Destroy handle
    ret = cam.MV_CC_DestroyHandle()
    if ret != 0:
        print ("destroy handle fail! ret[0x%x]" % ret)
        del data_buf
        sys.exit()
 
    del data_buf

结果图
在这里插入图片描述
结果展示的一个是原图,一个是用opencv二值化之后的图。经过上述处理之后,就可以得到供opencv处理的数据格式了。

五、总结

本论文主要介绍了如何用c#或者python调用海康工业相机。并且在python中实现了将海康相机提取到的相机数据转化为opencv支持的格式,这样就实现了python加opencv对海康相机的实时调用。

注意:网上有很多利用rtsp地址格式来获取海康相机数据的方法,笔者一开始也是想用这种方法实现,因为这样就可以直接用opencv中的videocapture类直接调用相机了。但经过多番尝试之后,仍然无果。后来是在联系了海康客服才知道,所有的工业网口相机都不支持rtsp格式获取数据,网上的方法都是针对于海康摄像机、录影机的。而且也只有摄像机、录影机才需要先激活再使用,工业网口相机拿来就用即可,亏的我一开始还想办法激活我的工业相机,我可真是个憨憨。

代码都已开源,喜欢的还请一键三连。

2022.01.07:
看到自己写的博客下面有这么多人评论,同时也能给很多人带来帮助,甚是开心啊。因为笔者平时科研比较忙,很多消息都没有回复,我只能挑一些比较典型的问题回复,其他一些问题希望各位小伙伴互帮互助,笔者先在这谢过了。
除了与海康威视的相机交互,笔者也使用过大恒相机,不过是使用c#结合emgucv(opencv在.net平台下的版本)与大恒相机交互,相关的资料我已经封装成了类供大家下载(笔者需要一些积分下载资料,所以这一块就不公开了,有能力的可以下载,实在没钱但又需要的可以私信我)。c#与大恒相机交互

笔者最近还在使用海康威视的三维激光轮廓扫描仪,如何利用c++结合qt以及pcl与扫描仪进行交互,把扫描仪数据直接转为pcl中的点云对象,是笔者最近研究的一个小问题。
笔者发现网上关于这块的资料基本没有,很多都是与二维相机的交互。本着开源互助的理念,有进展之后会另写一篇文章与大家分享。对三维相机感兴趣的小伙伴抓紧点赞收藏啊,点赞和收藏数达到100之后将会公布所有源码(注释很详细)手动滑稽。

猜你喜欢

转载自blog.csdn.net/bookshu6/article/details/112761849