news 2026/4/22 16:16:44

海康威视工业相机,获取图像的两种方法:MV_CC_GetImageBuffer与MV_CC_RegisterImageCallBackEx 的对比

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
海康威视工业相机,获取图像的两种方法:MV_CC_GetImageBuffer与MV_CC_RegisterImageCallBackEx 的对比

在海康威视的 Python SDK 里,MV_CC_GetImageBufferMV_CC_RegisterImageCallBackEx这两个函数都是用于获取图像数据的。

一、MV_CC_GetImageBuffer(主动获取模式)

主动获取图像:通过调用该函数,应用程序直接从 SDK 的内部缓冲区中获取图像数据。

  • 使用流程:

1. 调用MV_CC_StartGrabbing() 启动图像采集。

2. 在应用层循环调用MV_CC_GetImageBuffer() ,取图的线程阻塞等待相机返回图像数据,如果有设置超时,超时后返回无图像ret并启动下一个调用循环。

3. 处理完图像数据后,调用MV_CC_FreeImageBuffer()释放缓冲区。

安装完官方的MVS软件后在官方提供的范例:\MVS\Development\Samples\Python\GrabImage\GrabImage.py中提供了MV_CC_StartGrabbing()的使用方法。

  • 示例代码
# -- coding: utf-8 -- import sys import threading import msvcrt import os from ctypes import * sys.path.append(os.getenv('MVCAM_COMMON_RUNENV') + "/Samples/Python/MvImport") from MvCameraControl_class import * g_bExit = False # 为线程定义一个函数 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 if __name__ == "__main__": # ch:初始化SDK | en: initialize SDK MvCamera.MV_CC_Initialize() deviceList = MV_CC_DEVICE_INFO_LIST() tlayerType = (MV_GIGE_DEVICE | MV_USB_DEVICE | MV_GENTL_CAMERALINK_DEVICE | MV_GENTL_CXP_DEVICE | MV_GENTL_XOF_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 or mvcc_dev_info.nTLayerType == MV_GENTL_GIGE_DEVICE: print("\ngige device: [%d]" % i) strModeName = "" for per in mvcc_dev_info.SpecialInfo.stGigEInfo.chModelName: if per == 0: break 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) elif mvcc_dev_info.nTLayerType == MV_GENTL_CAMERALINK_DEVICE: print("\nCML device: [%d]" % i) strModeName = "" for per in mvcc_dev_info.SpecialInfo.stCMLInfo.chModelName: if per == 0: break strModeName = strModeName + chr(per) print("device model name: %s" % strModeName) strSerialNumber = "" for per in mvcc_dev_info.SpecialInfo.stCMLInfo.chSerialNumber: if per == 0: break strSerialNumber = strSerialNumber + chr(per) print("user serial number: %s" % strSerialNumber) elif mvcc_dev_info.nTLayerType == MV_GENTL_CXP_DEVICE: print("\nCXP device: [%d]" % i) strModeName = "" for per in mvcc_dev_info.SpecialInfo.stCXPInfo.chModelName: if per == 0: break strModeName = strModeName + chr(per) print("device model name: %s" % strModeName) strSerialNumber = "" for per in mvcc_dev_info.SpecialInfo.stCXPInfo.chSerialNumber: if per == 0: break strSerialNumber = strSerialNumber + chr(per) print("user serial number: %s" % strSerialNumber) elif mvcc_dev_info.nTLayerType == MV_GENTL_XOF_DEVICE: print("\nXoF device: [%d]" % i) strModeName = "" for per in mvcc_dev_info.SpecialInfo.stXoFInfo.chModelName: if per == 0: break strModeName = strModeName + chr(per) print("device model name: %s" % strModeName) strSerialNumber = "" for per in mvcc_dev_info.SpecialInfo.stXoFInfo.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 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 or stDeviceList.nTLayerType == MV_GENTL_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) # 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: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() # ch:反初始化SDK | en: finalize SDK MvCamera.MV_CC_Finalize()
  • 重点注意

MV_CC_GetImageBuffer()获取的是「相机缓冲区队列」中的图像帧,而不是「实时最新帧」,这会带来一定的隐性延迟:相机 SDK 通常会维护一个图像缓冲区队列(先进先出 FIFO),相机持续采集的图像会依次存入这个队列;
当调用MV_CC_GetImageBuffer()时,获取的是队列头部的「最早存入」的帧,而不是当前相机刚采集完成的「最新帧」;
如果队列中积累了多个未被读取的帧(比如程序处理图像的速度慢于相机采集速度),那么获取到的帧会比「实时画面」滞后,滞后时间 = 队列中未处理帧数 × 相机单帧采集周期(例如相机帧率 30fps,单帧周期约 33ms,队列有 3 帧未处理,就会滞后约 100ms)。

二、MV_CC_RegisterImageCallBackEx(回调模式)

被动获取图像:通过注册回调函数,SDK 在每次采集到图像时自动调用回调函数,将图像数据传递给应用程序。

  • 使用流程:

1. 定义一个回调函数,用于处理图像数据。

2. 调用 MV_CC_StartGrabbing() 启动图像采集。

3. 调用 MV_CC_RegisterImageCallBackEx() 注册回调函数。

4. 在回调函数中处理图像数据。

同样的,官方SDK也提供了一个范例:\MVS\Development\Samples\Python\Grab_Callback\Grab_Callback.py

  • 示例代码
# -- coding: utf-8 -- import sys import copy import msvcrt import os from ctypes import * sys.path.append(os.getenv('MVCAM_COMMON_RUNENV') + "/Samples/Python/MvImport") from MvCameraControl_class import * winfun_ctype = WINFUNCTYPE stFrameInfo = POINTER(MV_FRAME_OUT_INFO_EX) pData = POINTER(c_ubyte) FrameInfoCallBack = winfun_ctype(None, pData, stFrameInfo, c_void_p) def image_callback(pData, pFrameInfo, pUser): stFrameInfo = cast(pFrameInfo, POINTER(MV_FRAME_OUT_INFO_EX)).contents if stFrameInfo: print ("get one frame: Width[%d], Height[%d], nFrameNum[%d]" % (stFrameInfo.nWidth, stFrameInfo.nHeight, stFrameInfo.nFrameNum)) CALL_BACK_FUN = FrameInfoCallBack(image_callback) if __name__ == "__main__": # ch:初始化SDK | en: initialize SDK MvCamera.MV_CC_Initialize() deviceList = MV_CC_DEVICE_INFO_LIST() tlayerType = (MV_GIGE_DEVICE | MV_USB_DEVICE | MV_GENTL_CAMERALINK_DEVICE | MV_GENTL_CXP_DEVICE | MV_GENTL_XOF_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 or mvcc_dev_info.nTLayerType == MV_GENTL_GIGE_DEVICE: print ("\ngige device: [%d]" % i) strModeName = "" for per in mvcc_dev_info.SpecialInfo.stGigEInfo.chModelName: if per == 0: break 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) elif mvcc_dev_info.nTLayerType == MV_GENTL_CAMERALINK_DEVICE: print ("\nCML device: [%d]" % i) strModeName = "" for per in mvcc_dev_info.SpecialInfo.stCMLInfo.chModelName: if per == 0: break strModeName = strModeName + chr(per) print ("device model name: %s" % strModeName) strSerialNumber = "" for per in mvcc_dev_info.SpecialInfo.stCMLInfo.chSerialNumber: if per == 0: break strSerialNumber = strSerialNumber + chr(per) print ("user serial number: %s" % strSerialNumber) elif mvcc_dev_info.nTLayerType == MV_GENTL_CXP_DEVICE: print ("\nCXP device: [%d]" % i) strModeName = "" for per in mvcc_dev_info.SpecialInfo.stCXPInfo.chModelName: if per == 0: break strModeName = strModeName + chr(per) print ("device model name: %s" % strModeName) strSerialNumber = "" for per in mvcc_dev_info.SpecialInfo.stCXPInfo.chSerialNumber: if per == 0: break strSerialNumber = strSerialNumber + chr(per) print ("user serial number: %s" % strSerialNumber) elif mvcc_dev_info.nTLayerType == MV_GENTL_XOF_DEVICE: print ("\nXoF device: [%d]" % i) strModeName = "" for per in mvcc_dev_info.SpecialInfo.stXoFInfo.chModelName: if per == 0: break strModeName = strModeName + chr(per) print ("device model name: %s" % strModeName) strSerialNumber = "" for per in mvcc_dev_info.SpecialInfo.stXoFInfo.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 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 or stDeviceList.nTLayerType == MV_GENTL_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:Register image callback ret = cam.MV_CC_RegisterImageCallBackEx(CALL_BACK_FUN,None) if ret != 0: print ("register image callback fail! 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() print ("press a key to stop grabbing.") msvcrt.getch() # 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() # ch:反初始化SDK | en: finalize SDK MvCamera.MV_CC_Finalize()
  • 优点:

无缓冲区队列堆积,获取的是「实时最新帧」这是最核心的优势。回调函数在「新帧刚生成」时就被触发,数据直接推送至应用程序,无需存入相机缓冲区队列等待应用程序拉取,从根源上避免了 “队列堆积旧帧” 的问题,不会出现 “获取到的帧是几百毫秒前旧帧” 的隐性延迟,能最大程度保证帧的实时性。
无主动轮询 / 阻塞等待的额外时间开销(毫秒级);而回调函数是 “有新帧才执行”,无需应用程序持续询问或等待,减少了主动轮询的空耗,也避免了 “阻塞等待过程中错过最新帧” 的情况,降低了接口层面的额外延迟。
取图与采集节奏同步,数据传输无断层,回调函数的触发与相机采集、数据传输完成是 “无缝衔接” 的,SDK 无需额外维护大量缓存队列来缓冲未被拉取的帧,减少了「数据在缓冲区暂存」的中间环节,降低了数据在 SDK 层的停留时间,进一步压缩了传输和缓存带来的延迟。

总之,实时性比较好。

  • 存在风险:

虽然回调函数式延迟更低、实时性更好,但也存在一些需要规避的问题,否则可能影响其优势发挥:
回调函数必须 “轻量级执行”。回调函数是在「SDK 的工作线程」中执行的,而非应用程序的主线程。如果在回调函数中执行耗时操作(如大量图像算法处理、文件保存、界面刷新),会阻塞 SDK 的工作线程,导致 SDK 无法及时接收后续新帧,反而会造成帧丢失、延迟升高,甚至相机连接异常。
最佳实践:回调函数中只做「最小化操作」—— 比如仅将新帧数据拷贝到应用程序自己的缓冲区,然后发送一个 “帧就绪” 通知,由应用程序的其他线程去处理后续耗时操作。
互斥锁的必要性:回调函数通常运行在独立的 SDK 线程中,而应用程序的其他模块(如主线程、处理线程)可能同时访问帧数据,若不进行线程同步(使用互斥锁),容易出现数据竞争、帧数据损坏、内存泄漏等问题,反而影响整体稳定性和实时性。

三、总结

并非所有场景都需要 “极致低延迟”。如果使用场景是「离线采集」(如批量拍摄保存图片,无需实时查看)、「低帧率采集」(如 1fps 以下),阻塞式取图的延迟差异几乎可以忽略,且阻塞式代码逻辑更简单、调试更方便,无需处理线程安全和回调函数的复杂问题,此时阻塞式反而更合适。
回调函数式取图通常延迟更小(尤其是隐性延迟),实时性更优,适合对帧实时性要求高的场景(如实时视觉检测、运动目标跟踪);其低延迟的核心是「主动推送最新帧、无缓冲区堆积、无轮询等待开销」,与阻塞式的 “主动拉取旧帧” 形成本质区别;
回调函数式的优势发挥依赖于 “轻量级回调 + 线程安全处理”,否则可能适得其反;低帧率、离线采集场景下,阻塞式取图的简单性更具优势。

关于图像数据的即时性

在代码中加入计时,计算连续取图时邻帧图像的时间间隔:

MV_CC_GetImageBuffer:

MV_CC_RegisterImageCallBackEx:

看得出,即使是所有条件相同,获取到的帧间隔都不是特别均匀的。我之前有个项目,用帧率作为时间参数,用光流计算获取物体实时运行速度,得到的数据非常不稳定,这就是失败的原因。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/20 3:01:49

Qwen2.5-0.5B优化技巧:提升法律问答准确率的3个方法

Qwen2.5-0.5B优化技巧:提升法律问答准确率的3个方法 在当前大模型快速发展的背景下,如何让轻量级模型在特定垂直领域(如法律)中发挥出最大效能,成为许多开发者关注的重点。本文基于阿里开源的 Qwen2.5-0.5B-Instruct …

作者头像 李华
网站建设 2026/4/22 12:54:08

1分钟创建定时关机网页工具:无需编程经验

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容: 快速开发一个网页版定时关机工具,功能包括:1. 响应式界面适配手机/电脑 2. 倒计时显示 3. 后台调用系统命令API 4. 关机前提醒功能。要求使用纯前端技术实现…

作者头像 李华
网站建设 2026/4/19 3:02:48

AI如何帮你自动生成NPM依赖配置?

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容: 创建一个Node.js项目,自动分析项目需求并生成最优的package.json依赖配置。要求:1. 支持输入项目类型(如前端、后端、全栈)2. 根据项…

作者头像 李华
网站建设 2026/4/17 14:18:37

GLM-4.6V-Flash-WEB从零开始:Jupyter Notebook教程

GLM-4.6V-Flash-WEB从零开始:Jupyter Notebook教程 智谱最新开源,视觉大模型。 1. 引言 1.1 学习目标 本文旨在为开发者和AI研究者提供一份从零开始使用GLM-4.6V-Flash-WEB视觉大模型的完整实践指南。通过本教程,您将掌握: 如何…

作者头像 李华
网站建设 2026/4/18 23:43:19

HunyuanVideo-Foley科研应用:心理学实验刺激材料生成

HunyuanVideo-Foley科研应用:心理学实验刺激材料生成 1. 引言:AI音效生成技术在心理学研究中的新机遇 1.1 心理学实验对高质量视听刺激的迫切需求 在认知心理学、情绪研究和人机交互等领域,实验设计高度依赖标准化、高生态效度的视听刺激材…

作者头像 李华
网站建设 2026/4/19 16:26:53

Claude Code国内使用指南:AI如何成为你的编程助手

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容: 创建一个Python脚本,使用Claude Code API实现自动化代码生成功能。要求:1. 连接Claude Code的API接口;2. 根据用户输入的自然语言描述生成对应代…

作者头像 李华