news 2026/7/30 5:33:58

海康威视工业相机,获取图像的两种方法: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/7/29 23:12:40

Ming-UniVision:3.5倍提速!AI图文全流程交互黑科技

Ming-UniVision:3.5倍提速!AI图文全流程交互黑科技 【免费下载链接】Ming-UniVision-16B-A3B 项目地址: https://ai.gitcode.com/hf_mirrors/inclusionAI/Ming-UniVision-16B-A3B 导语:近日,一款名为Ming-UniVision-16B-A…

作者头像 李华
网站建设 2026/7/29 14:37:52

企业IT必看:批量部署MSI的5个实战技巧

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容: 创建一个企业级MSI批量部署工具,功能包括:1. 多终端远程安装 2. 安装进度实时监控 3. 自动重试机制 4. 安装结果统计报表 5. 与AD域控集成。使用C#开发&…

作者头像 李华
网站建设 2026/7/23 10:49:06

1小时搞定PMOS开关电路原型开发

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容: 快速生成一个可测试的PMOS开关电路原型方案。要求:1. 使用常见元器件;2. 包含测试点和调试建议;3. 提供3种不同参数的变体设计;4. 输…

作者头像 李华
网站建设 2026/7/24 16:43:29

绿色安全框有必要吗?AI人脸卫士可视化反馈设计思路解析

绿色安全框有必要吗?AI人脸卫士可视化反馈设计思路解析 1. 引言:为什么我们需要“看得见”的隐私保护? 在数字时代,照片和视频已成为我们记录生活的重要方式。然而,一张看似普通的合照中可能隐藏着多个个体的面部信息…

作者头像 李华
网站建设 2026/7/24 21:46:40

AI人脸隐私卫士应用指南:法律行业隐私保护方案

AI人脸隐私卫士应用指南:法律行业隐私保护方案 1. 引言 随着《个人信息保护法》和《数据安全法》的全面实施,人脸信息作为敏感个人生物识别数据,在司法取证、案件归档、监控调取等法律业务场景中面临严峻的合规挑战。传统手动打码方式效率低…

作者头像 李华
网站建设 2026/7/25 3:56:55

HunyuanVideo-Foley竞赛应用:参加AI生成挑战赛的利器

HunyuanVideo-Foley竞赛应用:参加AI生成挑战赛的利器 1. 背景与技术价值 1.1 AI音效生成的行业痛点 在视频内容创作领域,音效设计一直是提升沉浸感和专业度的关键环节。传统音效制作依赖人工逐帧匹配环境声、动作声(如脚步、关门、风雨等&…

作者头像 李华