Python OpenCV连接海康工业相机做图像处理
Python:3.9.9
OpenCV:4.5.5
numpy:1.19.3
海康:MV-CE200-10GC
协议:GigE
海康网站下载的相机调试工具,可以预览相机和设置相机的一些参数。
网站:https://www.hikrobotics.com/cn
下载你相机型号相关的安装包,这个要是有困难可以找海康售后帮忙,你提供的相机型号,工作人员会知道你下载和操作,海康售后挺好的,当初笔者不熟悉的时候也是很耐心的指导我操作。
主机通过网线连接相机,主机的IP地址选择自动分配,这样就会和相机在一个网段内。打开MVS工具,会识别主机中的网卡,如果该网卡中有相机连接,会显示出来。
选中相机后点击连接按钮,可以连接到该相机
连接成功后,界面右侧会出现相机的属性列表
点击预览按钮,界面中间会出现相机的预览画面
笔者记得第一次连接的时候,好像预览画面中什么都没有是黑色的,如果你也是这样不必着急,因为相机需要设置。首先相机上面有1个调亮度和1个调焦距的物理旋钮。你需要将物理按钮的属性调整到合适位置,确保那个环节没有问题。再一个这个工具中可以设置相机的属性,笔者这里在相机的默认属性基础上调节了4个属性:
常用属性
* 自动曝光:连续
* 自动增益:连续
* 亮度:100
高级属性
* 像素格式:UV 422 Packed
完成上述操作,应该能正常预览相机的画面。
在Python中连接工业相机获取帧图像处理,我们首先需要连接相机,然后获取到每一帧图像,将图像送给业务层。
在Python中需要连接相机,需要借助海康提供的.dll文件和其API文件
这些API文件和.dll文件,在安装完MVS软件后会获得。
笔者的工程结构如下:
将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,其他属性同理都可以在这里找到。
至此Python OpenCV中连接海康工业相机就可以调试通过。
项目地址:https://gitee.com/premeditate/Tools-Camera