添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
直爽的企鹅  ·  关于科学研究的内涵·  3 周前    · 
耍酷的墨镜  ·  米开朗基罗广场的月光·  1 年前    · 

因为项目的原因,笔者需要利用程序来实时调用海康工业相机,而不是简单的利用海康的驱动来调用相机。
在经历了反复的踩坑填坑之后,笔者总结了利用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")

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

# 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之后将会公布所有源码(注释很详细)手动滑稽。

文章目录前言一、准备工作二、c#调用1.引入库3、python调用4 完整代码总结题外话前言因为项目的原因,笔者需要利用程序来实时调用海康工业相机,而不是简单的利用海康的驱动来调用相机。在经历了反复的踩坑填坑之后,笔者总结了利用c#和python调用海康相机的两种方法一、准备工作无论是用c#还是python,都是要通过海康的SDK来进行二次开发的。海康的SDK相当于一个库,里面有各种用于海康相机的函数,这些函数被封装成供各大编程语言使用。所以,要想利用python或者c#与海康进行实时交互,就 图片信息的表示不止只有我们熟知的RGB,还有HLS、HSV、YUV、bayer,由于opencv等视觉处理库的广泛运用,RGB空间色彩模型运用最为广泛(实际上是BGR)。 摄像头传感器的感光原理是通过一个个的感光点对光进行采样和量化。但是每一个感光点只能感光RGB中的一种颜色。所以通常所说的50万像素或5000万像素等,指的是有50万或5000万个感光点。每一个感光点只能感光一种颜色,但一个真正图像需要每一个像素点都有RGB三种颜色分量。 在传感器模组的内部有ISP模块,主要用来对前 temp = np.asarray(pData2) # 将c_ubyte_Array转化成ndarray得到(3686400,) temp = temp.reshape((2592, 2048, 1)) # 根据自己分辨率进行转化 gray = cv2.cvtColor(temp,cv2.COLOR_BGR2GRAY) cv2.imshow("img", gray) rtsp://<address>:<port>/Streaming/Channels/<id>/ rtsp://<username>:<password>@<address>:<port>/Streaming/Channels/<id>/ Where <address> is the IP address of your camera, <port> is the RTSP port of the camera, and <username> and <password> refer to the login credentials of your camera. Using the first format, you will be asked to login and verify your credentials before being allowed to view the stream. Using the second format, you will automatically login when requesting the stream <id> refers to the channel number and stream type of the camera. For example, an <id> of 101 refers to channel #1 (first digit, 1xx) using the main stream (second and third digits, x01). Typically, you'll use a program such as VLC Media Player to decode the RTSP stream.
利用python加opencv与海康工业相机交互。(得到供opencv处理的数据) 心清似水淡若云、: MVS样例中的BasicDemo可以打开相机,这应该是官网的代码,他制作了一个GUI,代码中不是也获取了图像吗?博主后续做的是换成支持opencv的格式吗请问 c#和Python交互,完美解决Python调用OpenCV等第三方库以及分发时需配置python环境的问题 66666,秀我一脸 用svm对数据进行二分类(完整代码) L0wRy: 博主第55行是不是多打了一个b,运行的时候报错了