Python과 opencv를 사용하여 Hikvision 산업용 카메라와 상호 작용합니다. (opencv 처리를 위한 데이터 가져오기)


머리말

프로젝트 때문에 작성자는 단순히 Hikvision 드라이버를 사용하여 카메라를 호출하는 대신 Hikvision 산업용 카메라를 실시간으로 호출하는 프로그램을 사용해야 합니다.
구덩이를 밟고 구덩이를 채우는 것을 반복한 후 저자는 C#과 Python을 사용하여 Hikvision 카메라를 호출하는 두 가지 방법을 요약했습니다.


1. 준비

C#을 사용하든 Python을 사용하든 Haikang의 SDK를 통해 2차 개발을 진행해야 합니다. Hikvision의 SDK는 Hikvision 카메라를 위한 다양한 기능을 포함하는 라이브러리와 동일하며 이러한 기능은 주요 프로그래밍 언어에서 사용하도록 패키징됩니다. 따라서 하이캉과 실시간으로 연동하기 위해 파이썬이나 C#을 사용하려면 SDK에서 각 기능의 사용법을 익혀야 합니다. 하지만 다행스럽게도 하이강은 다양한 프로그래밍 언어로 루틴을 제공하므로 루틴을 빠르게 시작할 수 있습니다. Hikvision의 루틴은 일반적으로 Hikvision 카메라 드라이버 소프트웨어 의 설치 폴더 에 있습니다 . 특정 경로는
...\MVS\Development\Samples이며,
여기에는 대부분의 주요 언어에 대한 루틴이 포함됩니다
여기에 이미지 설명 삽입
. 또한 Hikvision은 파일 경로인 SDK 문서도 제공합니다.
...\MVS\Development\Documentations 에 있습니다.

여기에 이미지 설명 삽입
c#은 .net 버전을 참조할 수 있고, python 및 c는 c 버전을 참조할 수 있습니다(둘 사이에 큰 차이는 없음).

둘, C# 호출

C#의 루틴에는 많은 프로그램이 있지만
여기에 이미지 설명 삽입
인터페이스가 있는 basicdemo를 제외하고 나머지는 콘솔 프로그램이며 그림을 표시할 수 없습니다.
또한 basicdemo는 기본적으로 Hikvision 카메라 사용의 모든 측면을 다루므로 basicdemo 프로그램 학습을 우선적으로 수행하는 것이 좋습니다.
프로그램의 주요 인터페이스는 아래 그림과 같습니다.
여기에 이미지 설명 삽입
여기에 그림을 표시할 수는 있지만, 획득한 그림 데이터는 여전히 opencv가 처리할 수 있는 매트 형식이 아닌 하이강의 그림 형식이라는 점에 유의해야 합니다. 저자는 여러 번 시도했지만 여전히 opencv에서 처리할 수 있는 데이터 형식으로 변환할 수 없습니다. 파이썬으로 이 함수를 구현했는데 C#에서 메서드를 찾지 못했고(아마도 제가 C#에 익숙하지 않아서), C#에 더 익숙한 분들은 제 파이썬 코드를 참고해서 수정하시면 됩니다.

세 번째, 파이썬 호출

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의 인터페이스 기능을 사용해야 하기 때문에 해당 라이브러리를 import 해야 합니다.공식 경로는 이렇습니다.이 패키지는 프로그램의 같은 폴더에 있기 때문에 이전 패키지는 생략 가능하지만 우리가 그것을 사용할 때 절대 경로를 작성하는 것이 가장 좋습니다. 내 경로는 다음과 같습니다. 참조할 수 있습니다.

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에서 필요한 것은 정수형이고 오류가 발생하므로 변환할 필요가 없다. 여기

4. 완전한 코드

# -- 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 처리를 위한 데이터 형식을 얻을 수 있습니다.

V. 요약

이 논문은 주로 C# 또는 Python으로 Hikvision 산업용 카메라를 호출하는 방법을 소개합니다. 그리고 Python에서는 하이강 카메라에서 추출한 카메라 데이터를 opencv에서 지원하는 형식으로 변환하여 하이강 카메라에 파이썬 플러스 opencv의 실시간 호출을 구현합니다.

참고: 하이강 카메라 데이터를 얻기 위해 rtsp 주소 형식을 사용하는 방법은 인터넷에 많이 있습니다. 저자도 처음에 이 방법을 사용하기를 원했는데, 이렇게 하면 opencv의 videocapture 클래스에서 카메라를 직접 호출할 수 있기 때문입니다. . 그러나 여러 번 시도한 후에도 여전히 소용이 없습니다. 하이강 고객 서비스에 연락한 후에야 모든 산업용 네트워크 카메라가 rtsp 형식의 데이터 수집을 지원하지 않으며 온라인 방법은 모두 하이강 카메라 및 비디오 레코더용이라는 것을 알게 되었습니다. 그리고 카메라와 비디오 리코더만 활성화를 시켜줘야 사용이 가능합니다 산업용 네트워크 포트 카메라는 가져오자마자 바로 사용이 가능합니다 아쉽게도 초반에 산업용 카메라를 활성화 시키려 했는데요 정말 멍청합니다.

코드는 모두 오픈 소스입니다. 마음에 드시면 세 개의 링크를 클릭하십시오.

2022.01.07:
제 블로그에 이렇게 많은 분들이 댓글을 달아주시고, 많은 분들께도 도움이 될 수 있어서 너무 기쁩니다. 저자가 평소 과학 연구로 바쁘기 때문에 많은 메시지가 답변되지 않았습니다. 몇 가지 일반적인 질문만 답변할 수 있습니다. 다른 질문에 대해 서로 도움이 되기를 바랍니다. 미리 감사드립니다.
저자는 Hikvision 카메라와 상호 작용하는 것 외에도 Daheng 카메라를 사용했지만 emgucv(.net 플랫폼의 opencv 버전)와 결합된 C#을 사용하여 Daheng 카메라와 상호 작용합니다. 정보를 다운로드하려면 약간의 크레딧이 필요하므로 이 작품은 공개되지 않으며 능력이 있는 사람은 다운로드할 수 있으며 정말 돈이 없지만 필요한 사람은 비공개 메시지를 보낼 수 있습니다). C#은 Daheng 카메라와 상호 작용합니다.

저자는 최근에도 Hikvision의 3D 레이저 프로파일 스캐너를 사용하고 있는데 qt 및 pcl과 결합된 c++를 사용하여 스캐너와 상호 작용하고 스캐너 데이터를 pcl의 포인트 클라우드 개체로 직접 변환하는 방법은 저자가 최근 연구한 작은 주제입니다. .
저자는 기본적으로 인터넷에 이 영역에 대한 정보가 없고, 대부분이 2차원 카메라와의 인터랙션이라는 점을 발견했다. 오픈 소스 상호 지원의 개념에 따라 진전이 있을 때 여러분과 공유하기 위해 또 다른 기사를 작성하겠습니다. 3D 카메라에 관심이 있는 친구들은 서둘러 좋아요와 모으기를 하시고, 좋아요와 즐겨찾기가 100이 되면 모든 소스코드(자세한 댓글 포함)를 수동재미용으로 공개합니다.

Supongo que te gusta

Origin blog.csdn.net/bookshu6/article/details/112761849
Recomendado
Clasificación