Python结合OpenCV与海康工业相机交互应用详解

海康相机SDK魔改:实时图像以及格式转化

前言

最近在学习海康的工业相机SDK,发现官方提供的Demo功能虽然全面,但代码复杂度较高,尤其对于新手来说,短时间内难以抓住核心逻辑。在尝试运行官方例程grabimg时,发现其仅能在控制台输出图像的分辨率信息(如Width[3072], Height[2048]),却无法真正获取和显示图像数据。这显然无法满足实际需求,因此我决定对代码进行魔改,重点改造线程函数,最终实现了实时图像捕获与显示。

1. 原始问题分析

官方例程的核心问题在于:

  1. 数据未解析 :通过MV_CC_GetOneFrameTimeout获取的图像数据是原始字节流(c_ubyte数组),未转换为OpenCV可处理的格式。
  2. 像素格式不匹配 :未根据相机实际输出的像素格式(如Mono8BayerRG8BGR8等)进行颜色空间转换。
  3. 显示逻辑缺失 :仅输出分辨率信息,未调用OpenCV显示图像。

2. 魔改思路

通过以下步骤实现图像显示:

  1. 获取原始数据 :将c_ubyte数组转换为NumPy数组。
  2. 动态适配像素格式 :根据相机实际输出格式(如BayerRG8)调整数据形状和颜色空间。
  3. 实时显示 :使用OpenCV的imshow函数展示图像。

3. 关键代码解析

3.1 获取像素格式

通过MV_CC_GetEnumValue获取相机当前的像素格式枚举值:

def get_pixel_format(cam):
    stEnumValue = MVCC_ENUMVALUE()
    ret = cam.MV_CC_GetEnumValue("PixelFormat", stEnumValue)
    return stEnumValue.nCurValue if ret == 0 else None
3.2 数据处理与颜色转换

根据不同的像素格式处理数据:

# 示例:BayerRG8格式转换
if pixel_format_code == 0x01080009:  # BayerRG8
    temp = temp.reshape((height, width))
    temp = cv2.cvtColor(temp, cv2.COLOR_BAYER_RG2RGB)
3.3 线程函数改造

改造后的线程函数work_thread3

def work_thread3(cam, pData, nDataSize):
    stFrameInfo = MV_FRAME_OUT_INFO_EX()
    cv2.namedWindow("image", cv2.WINDOW_NORMAL)

    # 获取像素格式
    pixel_format_code = get_pixel_format(cam)
    if pixel_format_code is None:
        print("使用默认像素格式处理(假设为 Mono8)")
        pixel_format_code = 0x01080001  # 默认 Mono8

    # 像素格式映射表
    pixel_format_map = {
        0x01080001: "Mono8",
        0x01100003: "Mono10",
        0x01100005: "Mono12",
        0x02180014: "RGB8",
        0x02180015: "BGR8",
        0x02100032: "YUV422_YUYV",
        0x0210001F: "YUV422Packed",
        0x01080009: "BayerRG8",
    }
    format_name = pixel_format_map.get(pixel_format_code, "未知格式")
    print(f"当前像素格式: {format_name} (0x{pixel_format_code:x})")

    while not g_bExit:
        ret = cam.MV_CC_GetOneFrameTimeout(pData, nDataSize, stFrameInfo, 1000)
        if ret != 0:
            print(f"获取帧失败,错误码: {hex(ret)}")
            continue

        width = stFrameInfo.nWidth
        height = stFrameInfo.nHeight
        temp = np.asarray(pData)

        try:
            # 根据格式处理数据
            if pixel_format_code == 0x01080001:  # Mono8
                temp = temp.reshape((height, width))  # 单通道灰度图

            elif pixel_format_code in [0x01100003, 0x01100005]:  # Mono10/Mono12
                # 转换为 16 位数组并调整动态范围
                temp = temp.view(np.uint16).reshape((height, width))
                temp = ((temp >> (12 - 8)) & 0xFF).astype(np.uint8)

            elif pixel_format_code == 0x01080009:  # BayerRG8
                temp = temp.reshape((height, width))
                temp = cv2.cvtColor(temp, cv2.COLOR_BAYER_RG2RGB)

            elif pixel_format_code == 0x02100032:  # YUV422_YUYV
                temp = temp.reshape((height, width, 2))
                temp = cv2.cvtColor(temp, cv2.COLOR_YUV2BGR_YUYV)

            # 新增 YUV422Packed 处理逻辑
            elif pixel_format_code == 0x0210001F:  # YUV422Packed
                temp = temp.reshape((height, width, 2))  # YUV422 数据
                temp = cv2.cvtColor(temp, cv2.COLOR_YUV2BGR_Y422)  # 转换为 BGR

            elif pixel_format_code in [0x02180014, 0x02180015]:  # RGB8/BGR8
                temp = temp.reshape((height, width, 3))
                if pixel_format_code == 0x02180015:  # BGR8 转 RGB
                    temp = cv2.cvtColor(temp, cv2.COLOR_BGR2RGB)

            else:
                print(f"不支持的格式: {hex(pixel_format_code)}")
                continue

            # 显示图像
            cv2.imshow("image", temp)

        except Exception as e:
            print(f"数据处理错误: {str(e)}")

        if cv2.waitKey(1) & 0xFF == ord('q'):
            break

    cv2.destroyAllWindows()

4. 完整代码

import sys
import threading
import msvcrt
import numpy as np
import cv2
from ctypes import *

sys.path.append(r'D:\MVS\MVS\Development\Samples\Python\MvImport')
from MvCameraControl_class import *

g_bExit = False


def get_pixel_format(cam):
    """获取当前像素格式的枚举值"""
    stEnumValue = MVCC_ENUMVALUE()
    memset(byref(stEnumValue), 0, sizeof(stEnumValue))
    ret = cam.MV_CC_GetEnumValue("PixelFormat", stEnumValue)
    if ret != 0:
        print(f"获取像素格式失败,错误码: {hex(ret)}")
        return None
    return stEnumValue.nCurValue


def set_pixel_format(cam, enum_value):
    """设置像素格式"""
    ret = cam.MV_CC_SetEnumValue("PixelFormat", enum_value)
    if ret != 0:
        print(f"设置像素格式失败,错误码: {hex(ret)}")
        return False
    return True



def work_thread3(cam, pData, nDataSize):
    stFrameInfo = MV_FRAME_OUT_INFO_EX()
    cv2.namedWindow("image", cv2.WINDOW_NORMAL)

    # 获取像素格式
    pixel_format_code = get_pixel_format(cam)
    if pixel_format_code is None:
        print("使用默认像素格式处理(假设为 Mono8)")
        pixel_format_code = 0x01080001  # 默认 Mono8

    # 像素格式映射表
    pixel_format_map = {
        0x01080001: "Mono8",
        0x01100003: "Mono10",
        0x01100005: "Mono12",
        0x02180014: "RGB8",
        0x02180015: "BGR8",
        0x02100032: "YUV422_YUYV",
        0x0210001F: "YUV422Packed",
        0x01080009: "BayerRG8",
    }
    format_name = pixel_format_map.get(pixel_format_code, "未知格式")
    print(f"当前像素格式: {format_name} (0x{pixel_format_code:x})")

    while not g_bExit:
        ret = cam.MV_CC_GetOneFrameTimeout(pData, nDataSize, stFrameInfo, 1000)
        if ret != 0:
            print(f"获取帧失败,错误码: {hex(ret)}")
            continue

        width = stFrameInfo.nWidth
        height = stFrameInfo.nHeight
        temp = np.asarray(pData)

        try:
            # 根据格式处理数据
            if pixel_format_code == 0x01080001:  # Mono8
                temp = temp.reshape((height, width))  # 单通道灰度图

            elif pixel_format_code in [0x01100003, 0x01100005]:  # Mono10/Mono12
                # 转换为 16 位数组并调整动态范围
                temp = temp.view(np.uint16).reshape((height, width))
                temp = ((temp >> (12 - 8)) & 0xFF).astype(np.uint8)

            elif pixel_format_code == 0x01080009:  # BayerRG8
                temp = temp.reshape((height, width))
                temp = cv2.cvtColor(temp, cv2.COLOR_BAYER_RG2RGB)

            elif pixel_format_code == 0x02100032:  # YUV422_YUYV
                temp = temp.reshape((height, width, 2))
                temp = cv2.cvtColor(temp, cv2.COLOR_YUV2BGR_YUYV)

            # 新增 YUV422Packed 处理逻辑
            elif pixel_format_code == 0x0210001F:  # YUV422Packed
                temp = temp.reshape((height, width, 2))  # YUV422 数据
                temp = cv2.cvtColor(temp, cv2.COLOR_YUV2BGR_Y422)  # 转换为 BGR

            elif pixel_format_code in [0x02180014, 0x02180015]:  # RGB8/BGR8
                temp = temp.reshape((height, width, 3))
                if pixel_format_code == 0x02180015:  # BGR8 转 RGB
                    temp = cv2.cvtColor(temp, cv2.COLOR_BGR2RGB)

            else:
                print(f"不支持的格式: {hex(pixel_format_code)}")
                continue

            # 显示图像
            cv2.imshow("image", temp)

        except Exception as e:
            print(f"数据处理错误: {str(e)}")

        if cv2.waitKey(1) & 0xFF == ord('q'):
            break

    cv2.destroyAllWindows()


if __name__ == "__main__":
    # 枚举设备
    deviceList = MV_CC_DEVICE_INFO_LIST()
    ret = MvCamera.MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE, deviceList)
    if ret != 0 or deviceList.nDeviceNum == 0:
        print("未找到设备")
        sys.exit()

    print(f"找到 {deviceList.nDeviceNum} 个设备")
    for i in range(deviceList.nDeviceNum):
        device = cast(deviceList.pDeviceInfo[i], POINTER(MV_CC_DEVICE_INFO)).contents
        if device.nTLayerType == MV_GIGE_DEVICE:
            print(f"GigE 设备 [{i}]:")
            model_name = ''.join([chr(c) for c in device.SpecialInfo.stGigEInfo.chModelName if c != 0])
            ip = '.'.join(str((device.SpecialInfo.stGigEInfo.nCurrentIp >> (24 - i * 8)) & 0xFF) for i in range(4))
            print(f"  型号: {model_name}, IP: {ip}")
        else:
            print(f"USB 设备 [{i}]:")
            model_name = ''.join([chr(c) for c in device.SpecialInfo.stUsb3VInfo.chModelName if c != 0])
            print(f"  型号: {model_name}")

    # 选择设备
    cam_index = int(input("请选择相机索引: "))
    if cam_index >= deviceList.nDeviceNum:
        print("输入错误")
        sys.exit()

    # 初始化相机
    cam = MvCamera()
    stDevice = cast(deviceList.pDeviceInfo[cam_index], POINTER(MV_CC_DEVICE_INFO)).contents
    ret = cam.MV_CC_CreateHandle(stDevice)
    if ret != 0:
        print(f"创建句柄失败: {hex(ret)}")
        sys.exit()

    ret = cam.MV_CC_OpenDevice(MV_ACCESS_Exclusive, 0)
    if ret != 0:
        print(f"打开设备失败: {hex(ret)}")
        sys.exit()

    # 设置网络包大小(仅GigE相机)
    if stDevice.nTLayerType == MV_GIGE_DEVICE:
        packet_size = cam.MV_CC_GetOptimalPacketSize()
        if packet_size > 0:
            cam.MV_CC_SetIntValue("GevSCPSPacketSize", packet_size)

    # 设置触发模式为关闭
    cam.MV_CC_SetEnumValue("TriggerMode", 0)

    # 设置目标像素格式
    target_pixel_format = 0x0210001F  # 修改为所需格式
    if set_pixel_format(cam, target_pixel_format):
        print(f"像素格式设置为: {hex(target_pixel_format)}")

    # 获取PayloadSize并开始取流
    stParam = MVCC_INTVALUE()
    ret = cam.MV_CC_GetIntValue("PayloadSize", stParam)
    if ret != 0:
        print(f"获取PayloadSize失败: {hex(ret)}")
        sys.exit()

    data_buf = (c_ubyte * stParam.nCurValue)()
    cam.MV_CC_StartGrabbing()

    # 启动线程
    try:
        thread = threading.Thread(target=work_thread3, args=(cam, data_buf, stParam.nCurValue))
        thread.start()
    except Exception as e:
        print(f"线程启动失败: {str(e)}")
        sys.exit()

    print("按 q 退出...")
    msvcrt.getch()
    g_bExit = True
    thread.join()

    # 释放资源
    cam.MV_CC_StopGrabbing()
    cam.MV_CC_CloseDevice()
    cam.MV_CC_DestroyHandle()

5. 注意事项

  1. 依赖安装 :需安装opencv-python(我这里使用的是4.8.0的版本)和海康SDK的Python绑定(海康官网上下载mvs)。
  2. 分辨率适配 :根据相机实际分辨率调整reshape参数(这一步要对应着相机本身的分辨率以及使用的像素格式 ,最开始没有考虑到这一点,一直就是写一个格式直接运行,最后测试的时候材发现给opencv相应数据时不通用)。
  3. 资源释放 :确保在退出时关闭相机并释放资源。

6. 总结

通过本次魔改,成功实现了将官方例程添加上切换颜色格式并且将数据转化为opencv能读取的数据。未来可进一步扩展功能,如:

  • 添加图像保存功能。
  • 集成图像处理算法(如边缘检测)。
  • 代码都已开源,喜欢的还请一键三连。

    感谢@叔均 大佬这片文章的帮助 利用python加opencv与海康工业相机交互。(得到供opencv处理的数据)_使用pyqt5开发海康工业相机-CSDN博客

    作者:i坤2025

    物联沃分享整理
    物联沃-IOTWORD物联网 » Python结合OpenCV与海康工业相机交互应用详解

    发表回复