海康工业相机的应用部署不是简简单单!?
作者:SkyXZ
CSDN:SkyXZ~-CSDN博客
博客园:SkyXZ - 博客园
笔者使用的设备及环境:WSL2-Ubuntu22.04+MV-CS016-10UC
不会吧?不会吧?不会还有人拿到海康工业相机还是一脸懵叭?不会还有人觉得海康相机的API使用很难叭?不用慌!这篇文章从官方文档涵盖了海康相机官方MVS软件的使用以及Linux海康SDK-API包的理解上手,**一篇文章带你走出海康相机使用新手村!!!**让你一文读完之后便可自信的对朋友说:“海康相机?简简单单!!!”
参考资料:
- 海康工业相机客户端MVS下载地址:海康机器人-机器视觉-下载中心
- 海康工业相机镜头选型平台地址:海康机器人-机器视觉-工具
- 海康机器人USB3.0工业面阵相机用户手册:海康机器人USB3.0工业面阵相机用户手册V3.0.1_9724.pdf
- 海康机器人千兆网口工业面阵相机用户手册:海康机器人千兆网口工业面阵相机用户手册V4.1.2_7307.pdf
一、海康官方MVS客户端使用教程
我们首先根据自己的系统在官网下载我们的MVS客户端,我的系统为Linux,因此我选择下载第二个Linux版本的客户端
下载完成后我们会得到一个ZIP压缩包,我们解压后可以看到有很多不同CPU架构的版本,我们选择适合自己系统的版本使用如下命令即可完成安装,安装完成后的客户端将在/opt/MVS
目录下
sudo dpkg -i MVS-***********.deb #自行替换适合自己的系统版本安装包
我们使用如下命令即可进入MVS安装环境并且启动我们的客户端
cd /opt/MVS/bin #进入软件目录
./MVS.sh #运行客户端
运行客户端后我们可以看到我们的客户端主界面如下,左边主要为我们的设备区在这个区域我们可以看到当前连接到我们电脑的所有海康相机设备,最上方为菜单栏提供文件、视图、设置、工具和帮助的功能,下方为控制工具条可以为相机参数保存等操作提供快捷入口
接着我们选中我们的相机可以看到我们进入了如下界面,在这个界面中左下方为接口和设备信息获取窗口可以显示设备详细信息,中间为图像预览窗口上方左一按钮点击后即可开启相机实时取流查看图像,右边为属性设置窗口,可以对相机的基本参数进行设置,如:触发模式、增益、曝光时间、帧率、像素格式等等
由于属性设置客户端中有中文介绍,那么我们接着主要开始讲解我们的SDK-API的使用方法叭
二、海康相机开发实战上手
在我们上一节安装的MVS目录下存在着海康相机驱动所需要的全部头文件以及链接库,我们首先在我们的工作空间下面新建一个文件夹,接着将MVS安装目录下的include
和lib
文件全部复制进来
mkdir -P HK_Camera/src #创建工作目录及源码文件夹
cd HK_Camera/ #进入工作目录
cp -r /opt/MVS/include/ HK_Camera/ #复制头文件
cp -r /opt/MVS/lib/ HK_Camera/ #复制链接库
touch CmakeLists.txt #创建Cmake文件
我们首先来看一下海康相机组件包的项目结构叭,要驱动海康工业相机需要有两个主要的驱动包include
和lib
,其具体的结构以及对应的作用请看下图:
HK_Camera/
├── CmakeLists.txt
├── include/
│ ├── CameraParams.h
│ ├── MvCameraControl.h
│ ├── MvErrorDefine.h
│ ├── MvISPErrorDefine.h
│ ├── MvObsoleteInterfaces.h
│ ├── ObsoleteCamParams.h
│ └── PixelType.h
├── lib/
│ ├── 32/
│ ├── 64/
│ └── CLProtocol/
└── src/
-
CameraParams.h :定义了相机相关的参数结构体和枚举类型,包含了相机的基本信息结构(如GigE相机信息、USB相机信息等),定义了图像采集相关的参数(如图像大小、像素格式等),定义了相机工作模式(如单帧模式、连续采集模式等),定义了网络传输相关的参数
-
MvCameraControl.h :定义了相机控制的主要API接口,包含相机的初始化、连接、断开等基本操作函数,包含图像采集相关的函数(开始采集、停止采集、获取图像等),包含参数设置和获取的函数,包含文件操作相关的函数,是SDK的主要接口文件
- MvErrorDefine.h :定义了SDK所有的错误码,包含通用错误码(0x80000000-0x800000FF)、GenICam错误码(0x80000100-0x800001FF)、GigE相关错误码(0x80000200-0x800002FF)、USB相关错误码(0x80000300-0x800003FF)、固件升级相关错误码(0x80000400-0x800004FF)
- MvISPErrorDefine.h :定义了图像处理(ISP)相关的错误码,包含通用错误码、内存相关错误码、图像格式相关错误码、降噪处理相关错误码、去污点相关错误码
- PixelType.h :定义了相机支持的所有像素格式类型(enum MvGvspPixelType),主要包含以下几类像素格式:Mono(单色): Mono8/10/12/16等、Bayer: BayerGR8/10/12, BayerRG8/10/12等、RGB: RGB8_Packed, BGR8_Packed等、YUV: YUV411/422/444等、3D点云相关格式
- ObsoleteCamParams.h :定义了一些已过时但仍支持的相机参数结构体
- MvObsoleteInterfaces.h :定义了一些已过时但仍支持的接口函数
由于海康的这些头文件里的API有着非常非常详细的双语介绍(如下图),因此我们将以海康工业相机MV-CS016-10UC来主要介绍使用海康相机的API使用及相机的使用流程,类似于OpenCV调用免驱摄像头一般,海康摄像头的使用也主要分为四步,分别是初始化相机——>设置相机参数——>开始取图——>停止采集和清理资源,接下来我们将一边介绍主要使用的API一边手把手带着大家使能我们的海康相机,首先我们创建我们的main.cc
touch src/main.cc #创建文件
接着我们开始编写我们的Cmake文件,由于Cmake比较简单,我们便不详细的展开说明了:
#step 1 设置我们的项目以及版本最小需求
cmake_minimum_required(VERSION 3.10)
project(HK_Camera)
#step 2 设置C++标准
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
#step 3 设置编译类型
if(NOT CMAKE_BUILD_TYPE)set(CMAKE_BUILD_TYPE Release)
endif()
#step 4 添加海康相机SDK路径
set(MVCAM_SDK_PATH "${PROJECT_SOURCE_DIR}/lib/64") # SDK库文件路径
set(MVCAM_INCLUDE_PATH "${PROJECT_SOURCE_DIR}/include") # SDK头文件路径
#step 5 添加头文件路径
include_directories(${PROJECT_SOURCE_DIR}/include${MVCAM_INCLUDE_PATH}
)
#step 6 添加库文件路径
link_directories(${MVCAM_SDK_PATH}
)
#step 7 添加源文件
add_executable(${PROJECT_NAME} src/main.cc
)
#step 8 链接海康相机库
target_link_libraries(${PROJECT_NAME}MvCameraControl # 海康相机主库pthread # 线程库
)
#step 9 设置编译选项
target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -O2)
#step 10 安装目标
install(TARGETS ${PROJECT_NAME}RUNTIME DESTINATION bin
)
(1)海康相机使用基本部署流程
Cmake编写完成后我们开始编写我们的代码,首先导入一些我们需要的头文件
#include <iostream>
#include <string>
#include <cstring> // for memset
#include "MvCameraControl.h"
#include "CameraParams.h" // for MVCC_INTVALUE
#include "PixelType.h" // for PixelType_Gvsp_BGR8_Packed
接着为了便于我们的使用,我们首先创建一个HKCamera类,其中包含有五个主要的函数分别是InitCamera()
、SetParameters()
、StartGrabbing()
、GetOneFrame()
、StopGrabbing()
分别用来初始化我们的相机、设置相机参数、开始取流、获取一帧图像以及停止采集释放资源
class HKCamera{public:bool InitCamera(); // 初始化相机bool SetParameters(); // 设置相机参数bool StartGrabbing(); // 开始取图bool GetOneFrame(); // 获取一帧图像void StopGrabbing() // 停止采集
}
我们开始首先完成初始化相机的函数,要使用我们的海康相机那么首先肯定就需要先知道当前设备链接了哪些相机,因此我们便需要先使用MV_CC_EnumDevices
函数来枚举当前链接上主机的网口orUSB海康相机,海康官方的API以及MV_CC_DEVICE_INFO_LIST结构体解析如下:
typedef struct _MV_CC_DEVICE_INFO_LIST_
{unsigned int nDeviceNum; ///< [OUT] \~chinese 在线设备数量 MV_CC_DEVICE_INFO* pDeviceInfo[MV_MAX_DEVICE_NUM];///< [OUT] \~chinese 支持最多256个设备
}MV_CC_DEVICE_INFO_LIST;
/********************************************************************//*** @~chinese* @brief 枚举设备* @param nTLayerType [IN] 枚举传输层, 参数定义参见CameraParams.h定义, 如: #define MV_GIGE_DEVICE 0x00000001 GigE设备* @param pstDevList [IN][OUT] 设备列表* @return 成功,返回MV_OK;错误,返回错误码 * @remarks 设备列表的内存是在SDK内部分配的,多线程调用该接口时会进行设备列表内存的释放和申请,建议尽量避免多线程枚举操作。* @remarks 参数枚举传输层,适配传入MV_GIGE_DEVICE、MV_1394_DEVICE、MV_USB_DEVICE、MV_CAMERALINK_DEVICE;MV_GIGE_DEVICE该参数传出所有GiGE相关的设备信息(包含虚拟GiGE和GenTL下的GiGE设备),MV_USB_DEVICE该参数传出所有USB设备,包含虚拟USB设备。************************************************************************/
MV_CAMCTRL_API int __stdcall MV_CC_EnumDevices(IN unsigned int nTLayerType, IN OUT MV_CC_DEVICE_INFO_LIST* pstDevList);
因此根据API的描述,我们首先需要创建一个MV_CC_DEVICE_INFO_LIST
类型的结构体,同时我们还需要定义一个nRet
变量来获取调用API后返回的值来判断我们是否成功使用了API,因此我们的代码应该为下述形式:
MV_CC_DEVICE_INFO_LIST stDeviceList;//定义MV_CC_DEVICE_INFO_LIST类型的结构体stDeviceList
memset(&stDeviceList, 0, sizeof(MV_CC_DEVICE_INFO_LIST));//将结构体stDeviceList内存清零,防止垃圾值影响
int nRet = MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE, &stDeviceList);// 枚举设备
if (MV_OK != nRet) { //判断返回值为MV_OK即正常运行std::cout << "枚举设备失败! nRet [" << nRet << "]" << std::endl;return false;
}
在枚举了相机之后如果相机数量不为零我们便可以选择开启我们的海康相机啦,我们首先查看一下打开相机的API介绍
/********************************************************************//*** @~chinese* @brief 打开设备* @param handle [IN] 设备句柄* @param nAccessMode [IN] 访问权限, 参数定义参见CameraParams.h定义, 如:#define MV_ACCESS_Exclusive 1 (仅对 MV_GIGE_DEVICE/MV_GENTL_GIGE_DEVICE 类型的设备有效)* @param nSwitchoverKey[IN] 切换访问权限时的密钥(仅对 MV_GIGE_DEVICE 类型的设备有效)* @return 成功,返回MV_OK;错误,返回错误码* @remarks 根据设置的设备参数,找到对应的设备,连接设备, 调用接口时可不传入nAccessMode和nSwitchoverKey,此时默认设备访问模式为独占权限。MV_GIGE_DEVICE 类型设备,目前相机固件暂不支持MV_ACCESS_ExclusiveWithSwitch、MV_ACCESS_ControlWithSwitch、MV_ACCESS_ControlSwitchEnable、MV_ACCESS_ControlSwitchEnableWithKey这四种抢占模式, SDK接口支持设置MV_GENTL_GIGE_DEVICE 设备只支持 nAccessMode 是 MV_ACCESS_Exclusive 、MV_ACCESS_Control 、MV_ACCESS_Monitor权限对于U3V设备,CXP,Cameralink(MV_CAMERALINK_DEVICE、MV_GENTL_CAMERALINK_DEVICE), Xof设备, 虚拟GEV, 虚拟U3V设备:nAccessMode、nSwitchoverKey这两个参数无效; 默认以控制权限打开设备;该接口支持网口设备不枚举直接打开,不支持U口和GenTL设备不枚举打开设备************************************************************************/
#ifndef __cplusplus //用于区分C和C++编译环境
// C语言版本的函数声明
MV_CAMCTRL_API int __stdcall MV_CC_OpenDevice(IN void* handle, IN unsigned int nAccessMode, IN unsigned short nSwitchoverKey);
#else
// C++语言版本的函数声明(带默认参数)
MV_CAMCTRL_API int __stdcall MV_CC_OpenDevice(IN void* handle, IN unsigned int nAccessMode = MV_ACCESS_Exclusive, IN unsigned short nSwitchoverKey = 0);
#endif
根据这个API的说明我们可以知道我们在使用这个函数前还需要利用MV_CC_CreateHandle
这个API创建一个句柄同时一个设备同时只能被一个进程以独占方式打开,并且关闭设备时需要调用MV_CC_CloseDevice
函数,同时我们了解到这个API还有一个可选参数nAccessMode
,可以用来设置访问权限,分别有MV_ACCESS_Exclusive
独占权限、MV_ACCESS_Control
控制权限、MV_ACCESS_Monitor
监控权限、为了打开我们的摄像头我们还需再次查看MV_CC_CreateHandle
API介绍
/********************************************************************//*** @~chinese* @brief 创建设备句柄* @param handle [IN][OUT] 设备句柄* @param pstDevInfo [IN] 设备信息结构体* @return 成功,返回MV_OK;错误,返回错误码 * @remarks 根据输入的设备信息,创建库内部必须的资源和初始化内部模块通过该接口创建句柄,调用SDK接口,会默认生成SDK日志文件,如果不需要生成日志文件,可以将日志配置文件中的日志等级改成off************************************************************************/
MV_CAMCTRL_API int __stdcall MV_CC_CreateHandle(IN OUT void ** handle, IN const MV_CC_DEVICE_INFO* pstDevInfo);
根据这个API的说明,我们需要使用之前创建的MV_CC_DEVICE_INFO_LIST
类型结构体stDeviceList
中的成员变量pDeviceInfo
,于是我们的代码便应该如下:
if (stDeviceList.nDeviceNum > 0) {nRet = MV_CC_CreateHandle(&handle, stDeviceList.pDeviceInfo[0]); // 创建句柄if (MV_OK != nRet) {std::cout << "创建句柄失败! nRet [" << nRet << "]" << std::endl;return false;}nRet = MV_CC_OpenDevice(handle); // 打开设备if (MV_OK != nRet) {std::cout << "打开设备失败! nRet [" << nRet << "]" << std::endl;return false;}
} else {std::cout << "未找到设备!" << std::endl;return false;
}
至此我们的InitCamera()
函数便搭建完成啦,完整代码如下:
bool InitCamera() {MV_CC_DEVICE_INFO_LIST stDeviceList;memset(&stDeviceList, 0, sizeof(MV_CC_DEVICE_INFO_LIST));// 枚举设备int nRet = MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE, &stDeviceList);if (MV_OK != nRet) {std::cout << "枚举设备失败! nRet [" << nRet << "]" << std::endl;return false;}if (stDeviceList.nDeviceNum > 0) {// 创建句柄nRet = MV_CC_CreateHandle(&handle, stDeviceList.pDeviceInfo[0]);if (MV_OK != nRet) {std::cout << "创建句柄失败! nRet [" << nRet << "]" << std::endl;return false;}// 打开设备nRet = MV_CC_OpenDevice(handle);if (MV_OK != nRet) {std::cout << "打开设备失败! nRet [" << nRet << "]" << std::endl;return false;}} else {std::cout << "未找到设备!" << std::endl;return false;}return true;
}
完成了初始化相机的函数接下来我们便来完成我们SetParameters()
设置相机基本参数的函数,首先我们根据海康的用户手册可以知道我们有几个基本的参数可以进行设置,分别是相机的触发模式、像素格式、曝光时间、增益Buff、相机帧率等,我们在这里只对这四个常见参数进行设定,通过查阅API手册我们可以知道这四个参数设置分别对应以下两个APIMV_CC_SetEnumValue
、MV_CC_SetFloatValue
同时我们可以通过MV_CC_GetFloatValue
API来获取每一个属性键值的可调节参数范围,这些函数的API以及参数列表中涉及到的结构体介绍如下:
/********************************************************************//*** @~chinese* @brief 设置Enum型属性值* @param handle [IN] 设备句柄/采集卡句柄* @param strKey [IN] 属性键值,如获取像素格式信息则为"PixelFormat"* @param nValue [IN] 想要设置的设备的属性值* @return 成功,返回MV_OK,失败,返回错误码* @remarks 连接设备之后调用该接口可以设置Enum类型的指定节点的值。************************************************************************/
MV_CAMCTRL_API int __stdcall MV_CC_SetEnumValue(IN void* handle,IN const char* strKey,IN unsigned int nValue);/********************************************************************//*** @~chinese* @brief 设置float型属性值* @param handle [IN] 设备句柄/采集卡句柄* @param strKey [IN] 属性键值* @param fValue [IN] 想要设置的设备的属性值* @return 成功,返回MV_OK,失败,返回错误码* @remarks 连接设备之后调用该接口可以设置float类型的指定节点的值。************************************************************************/
MV_CAMCTRL_API int __stdcall MV_CC_SetFloatValue(IN void* handle,IN const char* strKey,IN float fValue);/********************************************************************//*** @~chinese* @brief 获取Float属性值* @param handle [IN] 设备句柄/采集卡句柄* @param strKey [IN] 属性键值* @param pstFloatValue [IN][OUT] 返回给调用者有关设备属性结构体指针* @return 成功,返回MV_OK,失败,返回错误码* @remarks 连接设备之后调用该接口可以获取float类型的指定节点的值。************************************************************************/
MV_CAMCTRL_API int __stdcall MV_CC_GetFloatValue(IN void* handle,IN const char* strKey,IN OUT MVCC_FLOATVALUE *pstFloatValue);/// \~chinese Float类型值 \~english Float Value
typedef struct _MVCC_FLOATVALUE_T
{float fCurValue; ///< [OUT] \~chinese 当前值 float fMax; ///< [OUT] \~chinese 最大值 float fMin; ///< [OUT] \~chinese 最小值 unsigned int nReserved[4]; ///< \~chinese 预留
}MVCC_FLOATVALUE;
我们通过查阅用户手册可以知道海康工业相机的属性键值遵循GenICam标准,因此我们能调整和获取的常用属性键值有如下几个:
"AcquisitionFrameRate" // 采集帧率
"AcquisitionFrameRateEnable" // 帧率控制使能
"ExposureTime" // 曝光时间
"ExposureAuto" // 自动曝光
"Gain" // 增益
"GainAuto" // 自动增益
"TriggerMode" // 触发模式
"TriggerSource" // 触发源
"Width" // 图像宽度
"Height" // 图像高度
因此,根据上述的分析,我们首先设置我们相机的触发模式为OFF内触发模式,同时设置我们的像素格式为BGR8
int nRet;
nRet = MV_CC_SetEnumValue(handle, "TriggerMode", 0);// 设置触发模式为off
if (MV_OK != nRet) {std::cout << "设置触发模式失败! nRet [" << nRet << "]" << std::endl;return false;
}
// 设置像素格式为BGR8
nRet = MV_CC_SetEnumValue(handle, "PixelFormat", PixelType_Gvsp_BGR8_Packed);
if (MV_OK != nRet) {std::cout << "设置像素格式失败! nRet [" << nRet << "]" << std::endl;return false;
}
std::cout << "设置像素格式为BGR8_Packed" << std::endl;
接着为了避免我们的参数设置有问题以及为了便于自检,我们首先利用MV_CC_GetFloatValue
来获取当前我们使用的相机的每一个属性参数的可调节范围,再根据获取到的可调节范围来检查我们设置参数的可用性并进一步修改我们的参数,因此我们以曝光时间的设置为例子代码应该为如下形式:我们首先创建一个MVCC_FLOATVALUE
形式的结构体stExposureTime
用来获取曝光时间的可调节信息,接着判定我们设置的exposureTime
是否超过了stExposureTime.fMin
和stExposureTime.fMax
的范围,如果没有那么我们便将exposureTime
使用MV_CC_SetFloatValue
API进行设置
// 获取和设置曝光时间
MVCC_FLOATVALUE stExposureTime = {0};
nRet = MV_CC_GetFloatValue(handle, "ExposureTime", &stExposureTime);
if (MV_OK == nRet) {float exposureTime = 10000.0f; // 默认10msif (exposureTime < stExposureTime.fMin) exposureTime = stExposureTime.fMin;if (exposureTime > stExposureTime.fMax) exposureTime = stExposureTime.fMax;std::cout << "曝光时间范围: [" << stExposureTime.fMin << ", " << stExposureTime.fMax << "], 当前设置: " << exposureTime << std::endl;nRet = MV_CC_SetFloatValue(handle, "ExposureTime", exposureTime);if (MV_OK != nRet) {std::cout << "设置曝光时间失败! nRet [" << nRet << "]" << std::endl;return false;}
}
至此我们的InitCamera()
函数便搭建完成啦,完整代码如下:
bool SetParameters() {// 设置相机参数int nRet;nRet = MV_CC_SetEnumValue(handle, "TriggerMode", 0);// 设置触发模式为offif (MV_OK != nRet) {std::cout << "设置触发模式失败! nRet [" << nRet << "]" << std::endl;return false;}// 设置像素格式为BGR8nRet = MV_CC_SetEnumValue(handle, "PixelFormat", PixelType_Gvsp_BGR8_Packed);if (MV_OK != nRet) {std::cout << "设置像素格式失败! nRet [" << nRet << "]" << std::endl;return false;}std::cout << "设置像素格式为BGR8_Packed" << std::endl;// 获取和设置曝光时间MVCC_FLOATVALUE stExposureTime = {0};nRet = MV_CC_GetFloatValue(handle, "ExposureTime", &stExposureTime);if (MV_OK == nRet) {float exposureTime = 10000.0f; // 默认10msif (exposureTime < stExposureTime.fMin) exposureTime = stExposureTime.fMin;if (exposureTime > stExposureTime.fMax) exposureTime = stExposureTime.fMax;std::cout << "曝光时间范围: [" << stExposureTime.fMin << ", " << stExposureTime.fMax << "], 当前设置: " << exposureTime << std::endl;nRet = MV_CC_SetFloatValue(handle, "ExposureTime", exposureTime);if (MV_OK != nRet) {std::cout << "设置曝光时间失败! nRet [" << nRet << "]" << std::endl;return false;}}// 获取和设置增益MVCC_FLOATVALUE stGain = {0};nRet = MV_CC_GetFloatValue(handle, "Gain", &stGain);if (MV_OK == nRet) {std::cout << "增益范围: [" << stGain.fMin << ", " << stGain.fMax << "], 当前值: " << stGain.fCurValue << std::endl;if (stGain.fCurValue < stGain.fMin || stGain.fCurValue > stGain.fMax) {float gain = stGain.fMin; // 使用最小值nRet = MV_CC_SetFloatValue(handle, "Gain", gain);if (MV_OK != nRet) {std::cout << "设置增益失败! nRet [" << nRet << "]" << std::endl;return false;}}}// 获取和设置帧率MVCC_FLOATVALUE stFrameRate = {0};nRet = MV_CC_GetFloatValue(handle, "AcquisitionFrameRate", &stFrameRate);if (MV_OK == nRet) {float frameRate = 30.0f; // 默认30fpsif (frameRate < stFrameRate.fMin) frameRate = stFrameRate.fMin;if (frameRate > stFrameRate.fMax) frameRate = stFrameRate.fMax;std::cout << "帧率范围: [" << stFrameRate.fMin << ", " << stFrameRate.fMax << "], 当前设置: " << frameRate << std::endl;nRet = MV_CC_SetFloatValue(handle, "AcquisitionFrameRate", frameRate);if (MV_OK != nRet) {std::cout << "设置帧率失败! nRet [" << nRet << "]" << std::endl;// 这个错误不影响主要功能,可以继续}}return true;
}
完成了相机基本参数的设置我们便可以开始取流啦!!!这部分有同学就会问了,欸?我们前面就已经打开摄像头了,为什么这里还会有取流和取帧两个函数呢?这就是海康摄像头和OpenCV使用免驱摄像头逻辑上的区别了,我们可以把这个过程类比于我们电动抽水机,前面打开摄像头的函数可以理解为给抽水机上电,而我们这里的开始取流StartGrabbing()
便是让图像暂存在相机内部的缓存中,可以理解为让抽水机开始工作一直出水,而我们的取帧函数GetOneFrame()
便是从相机的缓存中读取一帧图像并将图像数据复制到我们准备好的内存中,可以理解为拿一个水桶来接水,这样我们便可以随接随用
理解了为什么我们便开始讲解取流函数StartGrabbing()
,在海康的API里面有一个专门的函数便是用来取流的,他的API介绍如下:
/********************************************************************//*** @~chinese* @brief 开始取流* @param handle [IN] 设备句柄* @return 成功,返回MV_OK;错误,返回错误码* @remarks 该接口不支持MV_CAMERALINK_DEVICE 类型的设备。***********************************************************************/
MV_CAMCTRL_API int __stdcall MV_CC_StartGrabbing(IN void* handle);
可以看到使用这个函数非常的简单,和我们上面的操作非常类似,只要将我们创建的句柄传入其中即可,按照如下代码调用了之后我们的相机便会开始取流并将图像存储在相机内部的缓存里面临时存储
int nRet = MV_CC_StartGrabbing(handle);// 开始取流
if (MV_OK != nRet) {std::cout << "开始取流失败! nRet [" << nRet << "]" << std::endl;return false;
}
接着我们便可以获取当前属性参数下的图像数据包的大小,由于这个属性参数不变那么每帧的图像数据大小的范围便也不会变,因此我们只需要获取一次数据包的大小便可以,所以我们将数据包大小获取放在开始取流的函数里面,我们看到他的API介绍如下:
/************************************************************************* @fn MV_CAMCTRL_API int __stdcall MV_CC_GetIntValue(IN void* handle,IN const char* strKey,OUT MVCC_INTVALUE *pIntValue);* @brief 获取Integer属性值(建议改用MV_CC_GetIntValueEx接口)* @param void* handle [IN] 相机句柄* @param char* strKey [IN] 属性键值,如获取宽度信息则为"Width"* @param MVCC_INTVALUE* pstValue [IN][OUT] 返回给调用者有关相机属性结构体指针* @return 成功,返回MV_OK,失败,返回错误码************************************************************************/
MV_CAMCTRL_API int __stdcall MV_CC_GetIntValue(IN void* handle,IN const char* strKey,OUT MVCC_INTVALUE *pIntValue);
其中还涉及到了一个数据包结构体MVCC_INTVALUE
,求定义如下:
typedef struct _MVCC_INTVALUE_T
{unsigned int nCurValue; ///< [OUT] \~chinese 当前值 unsigned int nMax; ///< [OUT] \~chinese 最大值 unsigned int nMin; ///< [OUT] \~chinese 最小值 unsigned int nInc; ///< [OUT] \~chinese unsigned int nReserved[4];///< \~chinese 预留
}MVCC_INTVALUE;
了解了其定义后我们便可以使用这个API来获取数据包的大小并用malloc
函数来分配我们主机的内存
// 获取数据包大小
MVCC_INTVALUE stParam;
nRet = MV_CC_GetIntValue(handle, "PayloadSize", &stParam);
if (MV_OK != nRet) {std::cout << "获取数据包大小失败! nRet [" << nRet << "]" << std::endl;return false;
}
// 分配资源
nDataSize = stParam.nCurValue;
pData = (unsigned char*)malloc(nDataSize);
if (pData == nullptr) {std::cout << "内存分配失败!" << std::endl;return false;
}
至此我们的StartGrabbing()
函数便搭建完成啦,完整代码如下:
bool StartGrabbing() {// 开始取流int nRet = MV_CC_StartGrabbing(handle);if (MV_OK != nRet) {std::cout << "开始取流失败! nRet [" << nRet << "]" << std::endl;return false;}// 获取数据包大小MVCC_INTVALUE stParam;nRet = MV_CC_GetIntValue(handle, "PayloadSize", &stParam);if (MV_OK != nRet) {std::cout << "获取数据包大小失败! nRet [" << nRet << "]" << std::endl;return false;}// 分配资源nDataSize = stParam.nCurValue;pData = (unsigned char*)malloc(nDataSize);if (pData == nullptr) {std::cout << "内存分配失败!" << std::endl;return false;}return true;
}
接着我们再来编写我们的取帧函数GetOneFrame()
,海康给了我们一个专门的获取一帧图像的API,具体其介绍如下,我们可以看到这个API采用的是超时机制,因此SDK内部会一直等待直到有数据时才会返回一帧图像
/********************************************************************//*** @~chinese* @brief 采用超时机制获取一帧图片,SDK内部等待直到有数据时返回* @param handle [IN] 设备句柄* @param pData [IN][OUT] 图像数据接收指针* @param nDataSize [IN] 接收缓存大小* @param pstFrameInfo [IN][OUT] 图像信息结构体* @param nMsec [IN] 等待超时时间* @return 成功,返回MV_OK;错误,返回错误码* @remarks 调用该接口获取图像数据帧之前需要先调用MV_CC_StartGrabbing启动图像采集该接口为主动式获取帧数据,上层应用程序需要根据帧率,控制好调用该接口的频率该接口支持设置超时时间,SDK内部等待直到有数据时返回,可以增加取流平稳性,适合用于对平稳性要求较高的场合该接口对于U3V、GIGE设备均可支持该接口不支持CameraLink设备。***********************************************************************/
MV_CAMCTRL_API int __stdcall MV_CC_GetOneFrameTimeout(IN void* handle, IN OUT unsigned char* pData , IN unsigned int nDataSize, IN OUT MV_FRAME_OUT_INFO_EX* pstFrameInfo, IN unsigned int nMsec);
因此我们便可以用如下的方式来使用这个API,同时我们在这里再加上一次错误判定,以防止句柄或者分配的内存地址指针为空:
if (handle == nullptr || pData == nullptr) {return false;
}
int nRet = MV_CC_GetOneFrameTimeout(handle, pData, nDataSize, &stImageInfo, 1000);
if (MV_OK != nRet) {std::cout << "获取一帧图像失败! nRet [" << nRet << "]" << std::endl;return false;
}
std::cout << "获取一帧图像成功: Width[" << stImageInfo.nWidth << "] Height[" << stImageInfo.nHeight << "] FrameNum[" << stImageInfo.nFrameNum << "]" << std::endl;
return true;
至此我们的StartGrabbing()
函数便搭建完成啦,完整代码如下:
// 获取一帧图像
bool GetOneFrame() {if (handle == nullptr || pData == nullptr) {return false;}int nRet = MV_CC_GetOneFrameTimeout(handle, pData, nDataSize, &stImageInfo, 1000);if (MV_OK != nRet) {std::cout << "获取一帧图像失败! nRet [" << nRet << "]" << std::endl;return false;}std::cout << "获取一帧图像成功: Width[" << stImageInfo.nWidth << "] Height[" << stImageInfo.nHeight << "] FrameNum[" << stImageInfo.nFrameNum << "]" << std::endl;return true;
}
最后我们只需要编写释放资源的函数StopGrabbing()
便大功告成啦!释放资源比较简单,我们只需要按照我们开始的顺序倒叙关闭即可,即:停止取流、关闭相机、摧毁句柄,最后释放我们主机的内存即可,他们对应的API介绍如下:
/********************************************************************//*** @~chinese* @brief 停止取流* @param handle [IN] 设备句柄* @return 成功,返回MV_OK;错误,返回错误码* @remarks 该接口不支持MV_CAMERALINK_DEVICE 类型的设备。***********************************************************************/
MV_CAMCTRL_API int __stdcall MV_CC_StopGrabbing(IN void* handle);
/********************************************************************//*** @~chinese* @brief 关闭设备* @param handle [IN] 设备句柄* @return 成功,返回MV_OK;错误,返回错误码* @remarks 通过MV_CC_OpenDevice连接设备后,可以通过该接口断开设备连接,释放资源***********************************************************************/
MV_CAMCTRL_API int __stdcall MV_CC_CloseDevice(IN void* handle);
/********************************************************************//*** @~chinese* @brief 销毁设备句柄* @param handle [IN] 设备句柄* @return 成功,返回MV_OK;错误,返回错误码 * @remarks MV_CC_DestroyHandle 如果传入采集卡句柄,其效果和 MV_CC_DestroyInterface 相同;************************************************************************/
MV_CAMCTRL_API int __stdcall MV_CC_DestroyHandle(IN void * handle);
因此我们最后的函数的代码长这样:
// 停止采集
void StopGrabbing() {if (handle != nullptr) {MV_CC_StopGrabbing(handle);MV_CC_CloseDevice(handle);MV_CC_DestroyHandle(handle);handle = nullptr;}if (pData != nullptr) {free(pData);pData = nullptr;}
}
最后我们只需要在主函数依次进行调用即可,这部分代码比较简单,我相信来了解海康工业相机的同学这部分不需要再展开细讲啦:
int main() {HKCamera camera;// 初始化相机if (!camera.InitCamera()) {std::cout << "相机初始化失败!" << std::endl;return -1;}std::cout << "相机初始化成功!" << std::endl;// 设置参数if (!camera.SetParameters()) {std::cout << "设置相机参数失败!" << std::endl;return -1;}std::cout << "设置相机参数成功!" << std::endl;// 开始取图if (!camera.StartGrabbing()) {std::cout << "开始取图失败!" << std::endl;return -1;}std::cout << "开始取图成功!" << std::endl;// 获取10帧图像for (int i = 0; i < 10; i++) {if (!camera.GetOneFrame()) {break;}}// 停止采集camera.StopGrabbing();std::cout << "停止采集完成!" << std::endl;return 0;
}
最后我们完整的代码及运行效果如下:(但是又有同学要问了,我们该如何显示图像呢?请继续翻到下面,我将在下面进行介绍)
#include <iostream>
#include <string>
#include <cstring> // for memset
#include "MvCameraControl.h"
#include "CameraParams.h" // for MVCC_INTVALUE
#include "PixelType.h" // for PixelType_Gvsp_BGR8_Packed
class HKCamera {
private:void* handle = nullptr;MVCC_INTVALUE stParam;MV_FRAME_OUT_INFO_EX stImageInfo = {0};unsigned char* pData = nullptr;unsigned int nDataSize = 0;
public:bool InitCamera() {MV_CC_DEVICE_INFO_LIST stDeviceList;memset(&stDeviceList, 0, sizeof(MV_CC_DEVICE_INFO_LIST));// 枚举设备int nRet = MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE, &stDeviceList);if (MV_OK != nRet) {std::cout << "枚举设备失败! nRet [" << nRet << "]" << std::endl;return false;}if (stDeviceList.nDeviceNum > 0) {// 创建句柄nRet = MV_CC_CreateHandle(&handle, stDeviceList.pDeviceInfo[0]);if (MV_OK != nRet) {std::cout << "创建句柄失败! nRet [" << nRet << "]" << std::endl;return false;}// 打开设备nRet = MV_CC_OpenDevice(handle);if (MV_OK != nRet) {std::cout << "打开设备失败! nRet [" << nRet << "]" << std::endl;return false;}} else {std::cout << "未找到设备!" << std::endl;return false;}return true;}// 设置相机参数bool SetParameters() {int nRet;// 设置触发模式为offnRet = MV_CC_SetEnumValue(handle, "TriggerMode", 0);if (MV_OK != nRet) {std::cout << "设置触发模式失败! nRet [" << nRet << "]" << std::endl;return false;}// 设置像素格式为BGR8nRet = MV_CC_SetEnumValue(handle, "PixelFormat", PixelType_Gvsp_BGR8_Packed);if (MV_OK != nRet) {std::cout << "设置像素格式失败! nRet [" << nRet << "]" << std::endl;return false;}std::cout << "设置像素格式为BGR8_Packed" << std::endl;// 获取和设置曝光时间MVCC_FLOATVALUE stExposureTime = {0};nRet = MV_CC_GetFloatValue(handle, "ExposureTime", &stExposureTime);if (MV_OK == nRet) {float exposureTime = 10000.0f; // 默认10msif (exposureTime < stExposureTime.fMin) exposureTime = stExposureTime.fMin;if (exposureTime > stExposureTime.fMax) exposureTime = stExposureTime.fMax;std::cout << "曝光时间范围: [" << stExposureTime.fMin << ", " << stExposureTime.fMax << "], 当前设置: " << exposureTime << std::endl;nRet = MV_CC_SetFloatValue(handle, "ExposureTime", exposureTime);if (MV_OK != nRet) {std::cout << "设置曝光时间失败! nRet [" << nRet << "]" << std::endl;return false;}}// 获取和设置增益MVCC_FLOATVALUE stGain = {0};nRet = MV_CC_GetFloatValue(handle, "Gain", &stGain);if (MV_OK == nRet) {std::cout << "增益范围: [" << stGain.fMin << ", " << stGain.fMax << "], 当前值: " << stGain.fCurValue << std::endl;if (stGain.fCurValue < stGain.fMin || stGain.fCurValue > stGain.fMax) {float gain = stGain.fMin; // 使用最小值nRet = MV_CC_SetFloatValue(handle, "Gain", gain);if (MV_OK != nRet) {std::cout << "设置增益失败! nRet [" << nRet << "]" << std::endl;return false;}}}// 获取和设置帧率MVCC_FLOATVALUE stFrameRate = {0};nRet = MV_CC_GetFloatValue(handle, "AcquisitionFrameRate", &stFrameRate);if (MV_OK == nRet) {float frameRate = 30.0f; // 默认30fpsif (frameRate < stFrameRate.fMin) frameRate = stFrameRate.fMin;if (frameRate > stFrameRate.fMax) frameRate = stFrameRate.fMax;std::cout << "帧率范围: [" << stFrameRate.fMin << ", " << stFrameRate.fMax << "], 当前设置: " << frameRate << std::endl;nRet = MV_CC_SetFloatValue(handle, "AcquisitionFrameRate", frameRate);if (MV_OK != nRet) {std::cout << "设置帧率失败! nRet [" << nRet << "]" << std::endl;// 这个错误不影响主要功能,可以继续}}return true;}// 开始取图bool StartGrabbing() {// 开始取流int nRet = MV_CC_StartGrabbing(handle);if (MV_OK != nRet) {std::cout << "开始取流失败! nRet [" << nRet << "]" << std::endl;return false;}// 获取数据包大小MVCC_INTVALUE stParam;nRet = MV_CC_GetIntValue(handle, "PayloadSize", &stParam);if (MV_OK != nRet) {std::cout << "获取数据包大小失败! nRet [" << nRet << "]" << std::endl;return false;}// 分配资源nDataSize = stParam.nCurValue;pData = (unsigned char*)malloc(nDataSize);if (pData == nullptr) {std::cout << "内存分配失败!" << std::endl;return false;}return true;}// 获取一帧图像bool GetOneFrame() {if (handle == nullptr || pData == nullptr) {return false;}int nRet = MV_CC_GetOneFrameTimeout(handle, pData, nDataSize, &stImageInfo, 1000);if (MV_OK != nRet) {std::cout << "获取一帧图像失败! nRet [" << nRet << "]" << std::endl;return false;}std::cout << "获取一帧图像成功: Width[" << stImageInfo.nWidth << "] Height[" << stImageInfo.nHeight << "] FrameNum[" << stImageInfo.nFrameNum << "]" << std::endl;return true;}// 停止采集void StopGrabbing() {if (handle != nullptr) {MV_CC_StopGrabbing(handle);MV_CC_CloseDevice(handle);MV_CC_DestroyHandle(handle);handle = nullptr;}if (pData != nullptr) {free(pData);pData = nullptr;}}~HKCamera() {StopGrabbing();}
};
int main() {HKCamera camera;// 初始化相机if (!camera.InitCamera()) {std::cout << "相机初始化失败!" << std::endl;return -1;}std::cout << "相机初始化成功!" << std::endl;// 设置参数if (!camera.SetParameters()) {std::cout << "设置相机参数失败!" << std::endl;return -1;}std::cout << "设置相机参数成功!" << std::endl;// 开始取图if (!camera.StartGrabbing()) {std::cout << "开始取图失败!" << std::endl;return -1;}std::cout << "开始取图成功!" << std::endl;// 获取10帧图像for (int i = 0; i < 10; i++) {if (!camera.GetOneFrame()) {break;}}// 停止采集camera.StopGrabbing();std::cout << "停止采集完成!" << std::endl;return 0;
}
(2)海康相机+OpenCV实时取流部署教程
如果我们要显示采集的图像的话我们有两个方法,首先便是我们的OpenCV,再然后便是海康提供给我们的图像显示API,我们首先介绍大家都会的OpenCV的方案,我们先新引入OpenCV的头文件:
#include <opencv2/opencv.hpp>
接着我们在构造函数部分新增CV图像显示的Frame以及我们在构造函数中初始化我们的输出帧和数据包大小结构体:
class HKCamera {private:cv::Mat frame; // OpenCV图像public:HKCamera() {// 在构造函数中初始化结构体memset(&stParam, 0, sizeof(MVCC_INTVALUE));memset(&stImageInfo, 0, sizeof(MV_FRAME_OUT_INFO_EX));}
然后我们把GetOneFrame()
函数变为GetOneFrameAndShow()
然后在函数里面新增OpenCV获取帧的代码,将海康与OpenCV进行桥接,由于我们前面设置的采集格式为BGR因此我们可以直接使用BGR数据创建Mat:
bool GetOneFrameAndShow() {if (handle == nullptr || pData == nullptr) {return false;}int nRet = MV_CC_GetOneFrameTimeout(handle, pData, nDataSize, &stImageInfo, 1000);if (MV_OK != nRet) {std::cout << "获取一帧图像失败! nRet [" << nRet << "]" << std::endl;return false;}std::cout << "获取一帧图像成功: Width[" << stImageInfo.nWidth << "] Height[" << stImageInfo.nHeight << "] FrameNum[" << stImageInfo.nFrameNum << "]" << std::endl;
/*---------------------添加以下图像转换及显示代码-----------------------*/// 转换为OpenCV格式并显示if (stImageInfo.enPixelType == PixelType_Gvsp_BGR8_Packed) {// 直接使用BGR数据创建Matframe = cv::Mat(stImageInfo.nHeight, stImageInfo.nWidth, CV_8UC3, pData);// 简单的图像增强cv::Mat temp;// 轻微提升对比度frame.convertTo(temp, -1, 1.1, 0);frame = temp;} else {std::cout << "不支持的像素格式: 0x" << std::hex << stImageInfo.enPixelType << std::dec << std::endl;return false;}if (!frame.empty()) {cv::imshow("Camera", frame);cv::waitKey(1);}
/*---------------------添加以上图像转换及显示代码-----------------------*/return true;
}
然后我们在主函数里把原先的获取10帧图像修改为while循环持续获得图像,这样便可以实时显示我们摄像头获取的图像:
// 持续获取并显示图像,直到按下ESC键
while (true) {if (!camera.GetOneFrameAndShow()) {break;}// 检查ESC键是否按下char key = cv::waitKey(1);if (key == 27) { // ESC键的ASCII码break;}
最后我们在CmakeLists.txt中添加OpenCV的依赖即可:
# 查找OpenCV包
find_package(OpenCV REQUIRED)
include_directories(${PROJECT_SOURCE_DIR}/include${MVCAM_INCLUDE_PATH}${OpenCV_INCLUDE_DIRS}
)
# 链接海康相机库和OpenCV库
target_link_libraries(${PROJECT_NAME}MvCameraControl # 海康相机主库pthread # 线程库${OpenCV_LIBS} # OpenCV库
)
最后我们OpenCV显示图像的完整的代码及显示效果如下:
#include <iostream>
#include <string>
#include <cstring> // for memset
#include <opencv2/opencv.hpp>
#include "MvCameraControl.h"
#include "CameraParams.h" // for MVCC_INTVALUE
#include "PixelType.h" // for PixelType_Gvsp_BGR8_Packedclass HKCamera {
private:void* handle = nullptr;MVCC_INTVALUE stParam;MV_FRAME_OUT_INFO_EX stImageInfo = {0};unsigned char* pData = nullptr;unsigned int nDataSize = 0;cv::Mat frame; // OpenCV图像
public:HKCamera() {// 在构造函数中初始化结构体memset(&stParam, 0, sizeof(MVCC_INTVALUE));memset(&stImageInfo, 0, sizeof(MV_FRAME_OUT_INFO_EX));}bool InitCamera() {MV_CC_DEVICE_INFO_LIST stDeviceList;memset(&stDeviceList, 0, sizeof(MV_CC_DEVICE_INFO_LIST));// 枚举设备int nRet = MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE, &stDeviceList);if (MV_OK != nRet) {std::cout << "枚举设备失败! nRet [" << nRet << "]" << std::endl;return false;}if (stDeviceList.nDeviceNum > 0) {// 创建句柄nRet = MV_CC_CreateHandle(&handle, stDeviceList.pDeviceInfo[0]);if (MV_OK != nRet) {std::cout << "创建句柄失败! nRet [" << nRet << "]" << std::endl;return false;}// 打开设备nRet = MV_CC_OpenDevice(handle);if (MV_OK != nRet) {std::cout << "打开设备失败! nRet [" << nRet << "]" << std::endl;return false;}} else {std::cout << "未找到设备!" << std::endl;return false;}return true;}// 设置相机参数bool SetParameters() {int nRet;// 设置触发模式为offnRet = MV_CC_SetEnumValue(handle, "TriggerMode", 0);if (MV_OK != nRet) {std::cout << "设置触发模式失败! nRet [" << nRet << "]" << std::endl;return false;}// 设置像素格式为BGR8nRet = MV_CC_SetEnumValue(handle, "PixelFormat", PixelType_Gvsp_BGR8_Packed);if (MV_OK != nRet) {std::cout << "设置像素格式失败! nRet [" << nRet << "]" << std::endl;return false;}std::cout << "设置像素格式为BGR8_Packed" << std::endl;// 获取和设置曝光时间MVCC_FLOATVALUE stExposureTime = {0};nRet = MV_CC_GetFloatValue(handle, "ExposureTime", &stExposureTime);if (MV_OK == nRet) {float exposureTime = 10000.0f; // 默认10msif (exposureTime < stExposureTime.fMin) exposureTime = stExposureTime.fMin;if (exposureTime > stExposureTime.fMax) exposureTime = stExposureTime.fMax;std::cout << "曝光时间范围: [" << stExposureTime.fMin << ", " << stExposureTime.fMax << "], 当前设置: " << exposureTime << std::endl;nRet = MV_CC_SetFloatValue(handle, "ExposureTime", exposureTime);if (MV_OK != nRet) {std::cout << "设置曝光时间失败! nRet [" << nRet << "]" << std::endl;return false;}}// 获取和设置增益MVCC_FLOATVALUE stGain = {0};nRet = MV_CC_GetFloatValue(handle, "Gain", &stGain);if (MV_OK == nRet) {std::cout << "增益范围: [" << stGain.fMin << ", " << stGain.fMax << "], 当前值: " << stGain.fCurValue << std::endl;if (stGain.fCurValue < stGain.fMin || stGain.fCurValue > stGain.fMax) {float gain = stGain.fMin; // 使用最小值nRet = MV_CC_SetFloatValue(handle, "Gain", gain);if (MV_OK != nRet) {std::cout << "设置增益失败! nRet [" << nRet << "]" << std::endl;return false;}}}// 获取和设置帧率MVCC_FLOATVALUE stFrameRate = {0};nRet = MV_CC_GetFloatValue(handle, "AcquisitionFrameRate", &stFrameRate);if (MV_OK == nRet) {float frameRate = 30.0f; // 默认30fpsif (frameRate < stFrameRate.fMin) frameRate = stFrameRate.fMin;if (frameRate > stFrameRate.fMax) frameRate = stFrameRate.fMax;std::cout << "帧率范围: [" << stFrameRate.fMin << ", " << stFrameRate.fMax << "], 当前设置: " << frameRate << std::endl;nRet = MV_CC_SetFloatValue(handle, "AcquisitionFrameRate", frameRate);if (MV_OK != nRet) {std::cout << "设置帧率失败! nRet [" << nRet << "]" << std::endl;// 这个错误不影响主要功能,可以继续}}return true;}// 开始取图bool StartGrabbing() {// 开始取流int nRet = MV_CC_StartGrabbing(handle);if (MV_OK != nRet) {std::cout << "开始取流失败! nRet [" << nRet << "]" << std::endl;return false;}// 获取数据包大小MVCC_INTVALUE stParam;nRet = MV_CC_GetIntValue(handle, "PayloadSize", &stParam);if (MV_OK != nRet) {std::cout << "获取数据包大小失败! nRet [" << nRet << "]" << std::endl;return false;}// 分配资源nDataSize = stParam.nCurValue;pData = (unsigned char*)malloc(nDataSize);if (pData == nullptr) {std::cout << "内存分配失败!" << std::endl;return false;}return true;}// 获取一帧图像并显示bool GetOneFrameAndShow() {if (handle == nullptr || pData == nullptr) {return false;}int nRet = MV_CC_GetOneFrameTimeout(handle, pData, nDataSize, &stImageInfo, 1000);if (MV_OK != nRet) {std::cout << "获取一帧图像失败! nRet [" << nRet << "]" << std::endl;return false;}std::cout << "获取一帧图像成功: Width[" << stImageInfo.nWidth << "] Height[" << stImageInfo.nHeight << "] PixelType[0x" << std::hex << stImageInfo.enPixelType << std::dec<< "] FrameNum[" << stImageInfo.nFrameNum << "]" << std::endl;// 转换为OpenCV格式并显示if (stImageInfo.enPixelType == PixelType_Gvsp_BGR8_Packed) {// 直接使用BGR数据创建Matframe = cv::Mat(stImageInfo.nHeight, stImageInfo.nWidth, CV_8UC3, pData);// 简单的图像增强cv::Mat temp;// 轻微提升对比度frame.convertTo(temp, -1, 1.1, 0);frame = temp;} else {std::cout << "不支持的像素格式: 0x" << std::hex << stImageInfo.enPixelType << std::dec << std::endl;return false;}if (!frame.empty()) {cv::imshow("Camera", frame);cv::waitKey(1);}return true;}// 停止采集void StopGrabbing() {if (handle != nullptr) {MV_CC_StopGrabbing(handle);MV_CC_CloseDevice(handle);MV_CC_DestroyHandle(handle);handle = nullptr;}if (pData != nullptr) {free(pData);pData = nullptr;}}~HKCamera() {StopGrabbing();}
};
int main() {HKCamera camera;// 初始化相机if (!camera.InitCamera()) {std::cout << "相机初始化失败!" << std::endl;return -1;}std::cout << "相机初始化成功!" << std::endl;// 设置参数if (!camera.SetParameters()) {std::cout << "设置相机参数失败!" << std::endl;return -1;}std::cout << "设置相机参数成功!" << std::endl;// 开始取图if (!camera.StartGrabbing()) {std::cout << "开始取图失败!" << std::endl;return -1;}std::cout << "开始取图成功!" << std::endl;// 持续获取并显示图像,直到按下ESC键while (true) {if (!camera.GetOneFrameAndShow()) {break;}// 检查ESC键是否按下char key = cv::waitKey(1);if (key == 27) { // ESC键的ASCII码break;}}// 停止采集camera.StopGrabbing();std::cout << "停止采集完成!" << std::endl;return 0;
}
(3)海康相机官方简易API+X11窗口实时取流部署教程
但是我们会发现,如果我们摄像头采集的是BGR格式那么用OpenCV会比较方便,但是如果我们采集的不是BGR格式呢?那用OpenCV来显示图像便还需要进行图像的转换,非常的复杂!!!但是如果我们用海康官方的API再加Linux的OpenGL来显示图像那么我们便无需关心图像格式的转换问题啦!接下来我们便开始用海康官方显示API+Linux-OpenGL-X11来实时显示我们的图像,海康API中有关显示的函数有三个(其中一个即将被废除),具体介绍如下:
/********************************************************************//*** @~chinese* @brief 显示一帧图像* @param handle [IN] 设备句柄* @param pstDisplayInfo [IN] 图像信息* @return 成功,返回MV_OK;错误,返回错误码 * @remarks 与设备类型无关,渲染模式为D3D时,支持的最大分辨率为16384 * 163840***********************************************************************/
MV_CAMCTRL_API int __stdcall MV_CC_DisplayOneFrame(IN void* handle, IN MV_DISPLAY_FRAME_INFO* pstDisplayInfo);
/********************************************************************//*** @~chinese* @brief 显示一帧图像* @param handle [IN] 设备句柄* @param hWnd [IN] 窗口句柄* @param pstDisplayInfo [IN] 图像信息* @return 成功,返回MV_OK;错误,返回错误码* @remarks 该接口支持渲染宽高大小至int类型* 渲染模式为D3D时,支持的最大分辨率为16384 * 163840***********************************************************************/
MV_CAMCTRL_API int __stdcall MV_CC_DisplayOneFrameEx(IN void* handle, IN void* hWnd, IN MV_DISPLAY_FRAME_INFO_EX* pstDisplayInfo);/********************************************************************//*** @~chinese* @brief 显示一帧图像* @param handle [IN] 设备句柄* @param hWnd [IN] 窗口句柄* @param pstImage [IN] 图像信息* @param enRenderMode [IN] 渲染方式,Windows:0-GDI 1-D3D 2-OpenGL Linux:0-OpenGL * @return 成功,返回MV_OK;错误,返回错误码* @remarks 可选择OpenGL渲染模式,支持PixelType_Gvsp_RGB8_Packed,PixelType_Gvsp_BGR8_Packed,PixelType_Gvsp_Mono8三种像素格式图像大小超过4GB的渲染,其他渲染模式不支持。若图像大小未超过4GB,支持宽高大小至int类型调用时需要输入MV_CC_IMAGE结构体中nImageLen的值渲染模式为D3D时,支持的最大分辨率为16384 * 163840***********************************************************************/
MV_CAMCTRL_API int __stdcall MV_CC_DisplayOneFrameEx2(IN void* handle, IN void* hWnd, IN MV_CC_IMAGE* pstImage, unsigned int enRenderMode);
MV_CC_DisplayOneFrame()
函数为基础函数他的功能最简单但分辨率限制较大并且即将被废除、MV_CC_DisplayOneFrameEx()
函数为扩展函数,他支持更大分辨率也支持Windows上的D3D渲染但是不支持超大图像而MV_CC_DisplayOneFrameEx2()
函数支持选择渲染模式支持超大图像也支持更多像素格式且性能最好
接下来我们将以最简单的MV_CC_DisplayOneFrame()
函数来完成示例,**我们使用X11窗口来显示图像,**首先我们导入X11包,接着我们在类中新增创建窗口句柄函数和显示函数并且修改我们的取帧函数:
#include <X11/Xlib.h>
class HKCamera {
private:void* displayHandle = nullptr; // 显示窗口句柄
public:HKCamera() {// 在构造函数中正确初始化结构体memset(&stParam, 0, sizeof(MVCC_INTVALUE));memset(&stImageInfo, 0, sizeof(MV_FRAME_OUT_INFO_EX));}bool SetDisplayWindow(void* windowHandle) //创建显示窗口句柄bool DisplayOneFrame() //新建显示bool GetOneFrameAndShow()//修改GetOneFrame()函数
接着我们需要利用我们的SetDisplayWindow(void* windowHandle)
函数来创建一个X11窗口的句柄:
// 设置显示窗口
bool SetDisplayWindow(void* windowHandle) {displayHandle = windowHandle;return true;
}
然后我们来创建我们的DisplayOneFrame()
函数,根据我们上面的函数介绍,我们需要在使用MV_CC_DisplayOneFrame()
函数前先定义一个MV_DISPLAY_FRAME_INFO
类型的结构体
typedef struct _MV_DISPLAY_FRAME_INFO_
{void* hWnd; ///< [IN] \~chinese 窗口句柄 \~english HWNDunsigned char* pData; ///< [IN] \~chinese 显示的数据 \~english Data Bufferunsigned int nDataLen; ///< [IN] \~chinese 数据长度 \~english Data Sizeunsigned short nWidth; ///< [IN] \~chinese 图像宽 \~english Widthunsigned short nHeight; ///< [IN] \~chinese 图像高 \~english Heightenum MvGvspPixelType enPixelType; ///< [IN] \~chinese 像素格式 \~english Pixel formatunsigned int enRenderMode; /// [IN] \~chinese 图像渲染方式Windows:0-GDI(默认), 1-D3D, 2-OPENGL Linux: 0-OPENGL(默认) unsigned int nRes[3]; ///< \~chinese 保留 \~english Reserved
}MV_DISPLAY_FRAME_INFO;
根据上述结构体的定义,我们先行配置我们的stDisplayInfo
参数
MV_DISPLAY_FRAME_INFO stDisplayInfo = {0};
stDisplayInfo.hWnd = displayHandle;
stDisplayInfo.pData = pData;
stDisplayInfo.nDataLen = stImageInfo.nFrameLen;
stDisplayInfo.nWidth = stImageInfo.nWidth;
stDisplayInfo.nHeight = stImageInfo.nHeight;
stDisplayInfo.enPixelType = stImageInfo.enPixelType;
在定义完这个结构体之后我们便可以使用显示函数API啦,具体的使用代码如下:
// 显示一帧图像
bool DisplayOneFrame() {if (handle == nullptr || pData == nullptr || displayHandle == nullptr) {return false;}// 准备显示信息MV_DISPLAY_FRAME_INFO stDisplayInfo = {0};stDisplayInfo.hWnd = displayHandle;stDisplayInfo.pData = pData;stDisplayInfo.nDataLen = stImageInfo.nFrameLen;stDisplayInfo.nWidth = stImageInfo.nWidth;stDisplayInfo.nHeight = stImageInfo.nHeight;stDisplayInfo.enPixelType = stImageInfo.enPixelType;// 显示图像int nRet = MV_CC_DisplayOneFrame(handle, &stDisplayInfo);if (MV_OK != nRet) {std::cout << "显示图像失败! nRet [" << nRet << "]" << std::endl;return false;}return true;
}
接着我们将原来的GetOneFrame()
函数中间添加图像显示函数DisplayOneFrame()
即可
// 获取一帧图像并显示
bool GetOneFrameAndShow() {if (handle == nullptr || pData == nullptr) {return false;}int nRet = MV_CC_GetOneFrameTimeout(handle, pData, nDataSize, &stImageInfo, 1000);if (MV_OK != nRet) {std::cout << "获取一帧图像失败! nRet [" << nRet << "]" << std::endl;return false;}std::cout << "获取一帧图像成功: Width[" << stImageInfo.nWidth << "] Height[" << stImageInfo.nHeight << "] FrameNum[" << stImageInfo.nFrameNum << "]" << std::endl;/* --------------显示图像 -----------------------*/if (displayHandle != nullptr) {return DisplayOneFrame();}/* --------------显示图像 -----------------------*/return true;
}
之后便开始修改我们的主函数啦首先在主函数中创建X11窗口并且捕获屏幕信息:
// 创建X11窗口
Display* display = XOpenDisplay(NULL);
if (!display) {std::cout << "无法连接到X服务器!" << std::endl;return -1;
}
// 获取屏幕信息
int screen = DefaultScreen(display);
Window root = DefaultRootWindow(display);
接着我们设置窗口的基本参数,并设置窗口标题和窗口关闭事件
// 创建窗口
Window window = XCreateSimpleWindow(display, // Displayroot, // 父窗口0, 0, // 位置1440, 1080, // 大小(使用相机分辨率)1, // 边框宽度BlackPixel(display, screen), // 边框颜色WhitePixel(display, screen) // 背景颜色
);
// 设置窗口标题
XStoreName(display, window, "Camera Display");
// 设置窗口接收关闭事件
Atom wmDeleteMessage = XInternAtom(display, "WM_DELETE_WINDOW", False);
XSetWMProtocols(display, window, &wmDeleteMessage, 1);
最后我们显示窗口并持续捕获图像并使用X11事件显示即可
// 显示窗口
XMapWindow(display, window);
XFlush(display);
// 设置显示窗口
camera.SetDisplayWindow((void*)window);
// 开始取图
if (!camera.StartGrabbing()) {std::cout << "开始取图失败!" << std::endl;XCloseDisplay(display);return -1;
}
std::cout << "开始取图成功!" << std::endl;
// 持续获取并显示图像
bool running = true;
while (running) {if (!camera.GetOneFrameAndShow()) {break;}// 处理X11事件while (XPending(display)) {XEvent event;XNextEvent(display, &event);// 处理窗口关闭事件if (event.type == ClientMessage) {if (event.xclient.data.l[0] == wmDeleteMessage) {running = false;}}}
}
接着我们修改一下我们的Cmake即可:
# 查找X11包
find_package(X11 REQUIRED)
include_directories(${PROJECT_SOURCE_DIR}/include${MVCAM_INCLUDE_PATH}${OpenCV_INCLUDE_DIRS}
)
# 链接海康相机库和X11库
target_link_libraries(${PROJECT_NAME}MvCameraControl # 海康相机主库pthread # 线程库${X11_LIBRARIES} # X11库
)
最后我们X11串口+海康的显示API的完整的代码及效果如下:
#include <iostream>
#include <string>
#include <cstring> // for memset
#include <X11/Xlib.h>
#include "MvCameraControl.h"
#include "CameraParams.h" // for MVCC_INTVALUE
#include "PixelType.h" // for PixelType_Gvsp_BGR8_Packed
class HKCamera {
private:void* handle = nullptr;MVCC_INTVALUE stParam;MV_FRAME_OUT_INFO_EX stImageInfo = {0};unsigned char* pData = nullptr;unsigned int nDataSize = 0;void* displayHandle = nullptr; // 显示窗口句柄
public:HKCamera() {// 在构造函数中正确初始化结构体memset(&stParam, 0, sizeof(MVCC_INTVALUE));memset(&stImageInfo, 0, sizeof(MV_FRAME_OUT_INFO_EX));}bool InitCamera() {MV_CC_DEVICE_INFO_LIST stDeviceList;memset(&stDeviceList, 0, sizeof(MV_CC_DEVICE_INFO_LIST));// 枚举设备int nRet = MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE, &stDeviceList);if (MV_OK != nRet) {std::cout << "枚举设备失败! nRet [" << nRet << "]" << std::endl;return false;}if (stDeviceList.nDeviceNum > 0) {// 创建句柄nRet = MV_CC_CreateHandle(&handle, stDeviceList.pDeviceInfo[0]);if (MV_OK != nRet) {std::cout << "创建句柄失败! nRet [" << nRet << "]" << std::endl;return false;}// 打开设备nRet = MV_CC_OpenDevice(handle);if (MV_OK != nRet) {std::cout << "打开设备失败! nRet [" << nRet << "]" << std::endl;return false;}} else {std::cout << "未找到设备!" << std::endl;return false;}return true;}// 设置相机参数bool SetParameters() {int nRet;// 设置触发模式为offnRet = MV_CC_SetEnumValue(handle, "TriggerMode", 0);if (MV_OK != nRet) {std::cout << "设置触发模式失败! nRet [" << nRet << "]" << std::endl;return false;}// 设置像素格式为BGR8nRet = MV_CC_SetEnumValue(handle, "PixelFormat", PixelType_Gvsp_BGR8_Packed);if (MV_OK != nRet) {std::cout << "设置像素格式失败! nRet [" << nRet << "]" << std::endl;return false;}std::cout << "设置像素格式为BGR8_Packed" << std::endl;// 获取和设置曝光时间MVCC_FLOATVALUE stExposureTime = {0};nRet = MV_CC_GetFloatValue(handle, "ExposureTime", &stExposureTime);if (MV_OK == nRet) {float exposureTime = 10000.0f; // 默认10msif (exposureTime < stExposureTime.fMin) exposureTime = stExposureTime.fMin;if (exposureTime > stExposureTime.fMax) exposureTime = stExposureTime.fMax;std::cout << "曝光时间范围: [" << stExposureTime.fMin << ", " << stExposureTime.fMax << "], 当前设置: " << exposureTime << std::endl;nRet = MV_CC_SetFloatValue(handle, "ExposureTime", exposureTime);if (MV_OK != nRet) {std::cout << "设置曝光时间失败! nRet [" << nRet << "]" << std::endl;return false;}}// 获取和设置增益MVCC_FLOATVALUE stGain = {0};nRet = MV_CC_GetFloatValue(handle, "Gain", &stGain);if (MV_OK == nRet) {std::cout << "增益范围: [" << stGain.fMin << ", " << stGain.fMax << "], 当前值: " << stGain.fCurValue << std::endl;if (stGain.fCurValue < stGain.fMin || stGain.fCurValue > stGain.fMax) {float gain = stGain.fMin; // 使用最小值nRet = MV_CC_SetFloatValue(handle, "Gain", gain);if (MV_OK != nRet) {std::cout << "设置增益失败! nRet [" << nRet << "]" << std::endl;return false;}}}// 获取和设置帧率MVCC_FLOATVALUE stFrameRate = {0};nRet = MV_CC_GetFloatValue(handle, "AcquisitionFrameRate", &stFrameRate);if (MV_OK == nRet) {float frameRate = 30.0f; // 默认30fpsif (frameRate < stFrameRate.fMin) frameRate = stFrameRate.fMin;if (frameRate > stFrameRate.fMax) frameRate = stFrameRate.fMax;std::cout << "帧率范围: [" << stFrameRate.fMin << ", " << stFrameRate.fMax << "], 当前设置: " << frameRate << std::endl;nRet = MV_CC_SetFloatValue(handle, "AcquisitionFrameRate", frameRate);if (MV_OK != nRet) {std::cout << "设置帧率失败! nRet [" << nRet << "]" << std::endl;// 这个错误不影响主要功能,可以继续}}return true;}// 开始取图bool StartGrabbing() {// 开始取流int nRet = MV_CC_StartGrabbing(handle);if (MV_OK != nRet) {std::cout << "开始取流失败! nRet [" << nRet << "]" << std::endl;return false;}// 获取数据包大小MVCC_INTVALUE stParam;nRet = MV_CC_GetIntValue(handle, "PayloadSize", &stParam);if (MV_OK != nRet) {std::cout << "获取数据包大小失败! nRet [" << nRet << "]" << std::endl;return false;}// 分配资源nDataSize = stParam.nCurValue;pData = (unsigned char*)malloc(nDataSize);if (pData == nullptr) {std::cout << "内存分配失败!" << std::endl;return false;}return true;}// 设置显示窗口bool SetDisplayWindow(void* windowHandle) {displayHandle = windowHandle;return true;}// 显示一帧图像bool DisplayOneFrame() {if (handle == nullptr || pData == nullptr || displayHandle == nullptr) {return false;}// 准备显示信息MV_DISPLAY_FRAME_INFO stDisplayInfo = {0};stDisplayInfo.hWnd = displayHandle;stDisplayInfo.pData = pData;stDisplayInfo.nDataLen = stImageInfo.nFrameLen;stDisplayInfo.nWidth = stImageInfo.nWidth;stDisplayInfo.nHeight = stImageInfo.nHeight;stDisplayInfo.enPixelType = stImageInfo.enPixelType;// 显示图像int nRet = MV_CC_DisplayOneFrame(handle, &stDisplayInfo);if (MV_OK != nRet) {std::cout << "显示图像失败! nRet [" << nRet << "]" << std::endl;return false;}return true;}// 获取一帧图像并显示bool GetOneFrameAndShow() {if (handle == nullptr || pData == nullptr) {return false;}int nRet = MV_CC_GetOneFrameTimeout(handle, pData, nDataSize, &stImageInfo, 1000);if (MV_OK != nRet) {std::cout << "获取一帧图像失败! nRet [" << nRet << "]" << std::endl;return false;}std::cout << "获取一帧图像成功: Width[" << stImageInfo.nWidth << "] Height[" << stImageInfo.nHeight << "] FrameNum[" << stImageInfo.nFrameNum << "]" << std::endl;// 显示图像if (displayHandle != nullptr) {return DisplayOneFrame();}return true;}// 停止采集void StopGrabbing() {if (handle != nullptr) {MV_CC_StopGrabbing(handle);MV_CC_CloseDevice(handle);MV_CC_DestroyHandle(handle);handle = nullptr;}if (pData != nullptr) {free(pData);pData = nullptr;}}~HKCamera() {StopGrabbing();}
};
int main() {HKCamera camera;// 初始化相机if (!camera.InitCamera()) {std::cout << "相机初始化失败!" << std::endl;return -1;}std::cout << "相机初始化成功!" << std::endl;// 设置参数if (!camera.SetParameters()) {std::cout << "设置相机参数失败!" << std::endl;return -1;}std::cout << "设置相机参数成功!" << std::endl;// 创建X11窗口Display* display = XOpenDisplay(NULL);if (!display) {std::cout << "无法连接到X服务器!" << std::endl;return -1;}// 获取屏幕信息int screen = DefaultScreen(display);Window root = DefaultRootWindow(display);// 创建窗口Window window = XCreateSimpleWindow(display, // Displayroot, // 父窗口0, 0, // 位置1440, 1080, // 大小(使用相机分辨率)1, // 边框宽度BlackPixel(display, screen), // 边框颜色WhitePixel(display, screen) // 背景颜色);// 设置窗口标题XStoreName(display, window, "Camera Display");// 设置窗口接收关闭事件Atom wmDeleteMessage = XInternAtom(display, "WM_DELETE_WINDOW", False);XSetWMProtocols(display, window, &wmDeleteMessage, 1);// 显示窗口XMapWindow(display, window);XFlush(display);// 设置显示窗口camera.SetDisplayWindow((void*)window);// 开始取图if (!camera.StartGrabbing()) {std::cout << "开始取图失败!" << std::endl;XCloseDisplay(display);return -1;}std::cout << "开始取图成功!" << std::endl;// 持续获取并显示图像bool running = true;while (running) {if (!camera.GetOneFrameAndShow()) {break;}// 处理X11事件while (XPending(display)) {XEvent event;XNextEvent(display, &event);// 处理窗口关闭事件if (event.type == ClientMessage) {if (event.xclient.data.l[0] == wmDeleteMessage) {running = false;}}}}// 停止采集camera.StopGrabbing();std::cout << "停止采集完成!" << std::endl;// 关闭X11连接XCloseDisplay(display);return 0;
}
有任何问题大家都可以在评论区询问哦,我看到了便会及时回复哒!!!
相关文章:

海康工业相机的应用部署不是简简单单!?
作者:SkyXZ CSDN:SkyXZ~-CSDN博客 博客园:SkyXZ - 博客园 笔者使用的设备及环境:WSL2-Ubuntu22.04MV-CS016-10UC 不会吧?不会吧?不会还有人拿到海康工业相机还是一脸懵叭?不会还有人…...

Windows电脑安装File Browser与cpolar轻松搭建本地云盘
文章目录 前言1.下载安装File Browser2.启动访问File Browser3.安装cpolar内网穿透3.1 注册账号3.2 下载cpolar客户端3.3 登录cpolar web ui管理界面3.4 创建公网地址 4.固定公网地址访问 前言 无论是个人用户还是企业团队,都希望能够有一个高效、安全的解决方案来…...

mac配置 iTerm2 使用lrzsz与服务器传输文件
mac配置 1. 安装支持rz和sz命令的lrzsz brew install lrzsz2. 下载iterm2-send-zmodem.sh和iterm2-recv-zmodem.sh两个脚本 # 克隆仓库 git clone https://github.com/aikuyun/iterm2-zmodem ~/iterm2-zmodem# 进入到仓库目录 cd ~/iterm2-zmodem# 设置脚本文件可执行权限 c…...

【HBuilderX 中 Git 的使用】
目录: 一:安装必要的版本控制工具二:把Github上的项目克隆到本地三:将本地的项目上传到Github上 一:安装必要的版本控制工具 1️⃣ 安装 TortoiseGit 工具,下载地址:https://tortoisegit.org/do…...

Golang结合MySQL和DuckDB提高查询性能
要在Golang中组合MySQL和DuckDB以提高查询性能,请考虑使用混合查询执行方法。这种方法利用了MySQL强大的事务管理和DuckDB闪电般的分析处理能力。本文介绍如何充分利用两者的方法。 各取所长 用MySQL处理事务,用DuckDB处理分析 MySQL应该处理常规的INS…...

学技术学英语:TCP的三次握手和四次挥手
单词 汉语意思 音标 acknowledge 承认,确认 /əkˈnɒl.ɪdʒ/ acknowledgment 确认,承认 /əkˈnɒl.ɪdʒ.mənt/ duplex 双向的 /ˈdjuː.pleks/ establish 建立 /ɪˈstb.lɪʃ/ handshake 握手,握手协议 /ˈhnd.ʃeɪk…...

xiao esp32 S3播放SD卡wav音频
本文旨在使用xiao esp32 S3 播放SD卡上的音频文件 1 硬件准备 SD卡 2 代码实现 2.1 依赖库 ESP32-audioI2S-master 2.2 代码 #include "Arduino.h" #include "Audio.h" #include "SD.h"// Digital I/O used #define I2S_DOUT 6 #defi…...

Unity中实现伤害跳字效果(简单好抄)
第一步骤安装并导入Dotween插件(也可以不用导入之后直接下载我的安装包) 官网DOTween - 下载 第二步: 制作跳字预制体 建议把最佳适应打开,这样就不怕数字太大显示不全了。 第三步:创建一个空对象并编写脚本JumpNumbe…...
GaussDB日常维护操作
GaussDB日常维护操作 日常维护检查操作系统参数数据库健康状态日志收集日志清理应用连接数表的例行维护索引重建慢SQL诊断 日常维护检查 实例状态检查: #检查集群实例状态 gs_check -U omm -i CheckClusterStatecm_ctl query -Cvipd#检查主备DN的角色和同步状态 g…...

redis实现限流
令牌桶逻辑 计算逻辑: 代码: import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool;/*** ClassName RedisRateLimiterTokenBucket* Description TODO* Author zhang zhengdong* DATE 2025/1/17 20:22* Version 1.0*/ public class…...

基于SpringBoot和PostGIS的各国及所属机场信息检索及可视化实现
目录 前言 一、空间数据简介 1、全球国家信息表 2、机场信息表 3、国家机场检索实现 二、SpringBoot后台实现 1、模型层实现 2、控制层实现 三、WebGIS可视化实现 1、Leaflet界面实现 2、国家及其机场可视化成果 3、全球机场数量排行榜 四、总结 前言 新春佳节即将…...

python http server运行Angular 单页面路由时重定向,解决404问题
问题 当Angular在本地ng server运行时候,可以顺利访问各级路由。 但是运行ng build后,在dist 路径下的打包好的额index.html 必须要在服务器下运行才能加载。 在服务器下我们第一次访问路由页面时是没有问题的,但是尝试刷新页面或手动输入路…...
GPT-4o背后的语音技术
GPT-4o背后的语音技术 GPT-4o是一个any2any的多模态模型,能够接受文本、音频、图像、视频等多模态输入,也能够生成包含文本、语音、图像和视频等混合内容的多模态输出。本文主要谈语音多模态的实现,并分享一些对于语音研究未来发展的看法。 GPT-4o (“o” 代表 “omni”) …...

微透镜阵列精准全检,白光干涉3D自动量测方案提效70%
广泛应用的微透镜阵列 微透镜是一种常见的微光学元件,通过设计微透镜,可对入射光进行扩散、光束整形、光线均分、光学聚焦、集成成像等调制,进而实现许多传统光学元器件难以实现的特殊功能。 微透镜阵列(Microlens Array&#x…...
Spring boot框架下的RocketMQ消息中间件
1. RocketMQ 基础概念 1.1 核心概念 以下是 RocketMQ 核心概念在 Spring Boot 的 Java 后端代码中的实际使用方式: Producer(生产者) 定义:Producer 是负责发送消息到 RocketMQ 的组件。它可以将消息发送到指定的 Topic。 实…...

记录一次 centos 启动失败
文章目录 现场1分析1现场2分析2搜索实际解决过程 现场1 一次断电,导致 之前能正常启动的centos 7.7 起不来了有部分log , 关键信息如下 [1.332724] XFS(sda3): Internal error xfs ... at line xxx of fs/xfs/xfs_trans.c [1.332724] XFS(sda3): Corruption of in-memory data…...

C++学习第五天
创作过程中难免有不足,若您发现本文内容有误,恳请不吝赐教。 提示:以下是本篇文章正文内容,下面案例可供参考 一、构造函数 问题1 关于编译器生成的默认成员函数,很多童鞋会有疑惑:不实现构造函数的情况下…...

openharmony标准系统方案之瑞芯微RK3568移植案例
标准系统方案之瑞芯微RK3568移植案例 本文章是基于瑞芯微RK3568芯片的DAYU200开发板,进行标准系统相关功能的移植,主要包括产品配置添加,内核启动、升级,音频ADM化,Camera,TP,LCD,…...
深入理解 SSH 端口转发:本地 vs 远程 vs 动态转发
🌟 简介 SSH 端口转发(SSH Port Forwarding)作为一种强大而灵活的技术,不仅可以帮助我们安全地访问远程服务,还能轻松突破网络限制。本文将带你深入了解 SSH 端口转发的原理、类型和实战应用。 🌈 目录 &a…...

postman请求参数化
postman界面介绍 一、使用环境变量(Environment Variables)进行参数化 1、在请求中使用环境变量 在请求的url、请求头(Headers)、请求体(Body)等部分都可以使用环境变量。 URL 部分示例 点击 Postman 界面右上角的 “眼睛” 图标(Environment Quick Look)打开环境管理…...

接口测试中缓存处理策略
在接口测试中,缓存处理策略是一个关键环节,直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性,避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明: 一、缓存处理的核…...
day52 ResNet18 CBAM
在深度学习的旅程中,我们不断探索如何提升模型的性能。今天,我将分享我在 ResNet18 模型中插入 CBAM(Convolutional Block Attention Module)模块,并采用分阶段微调策略的实践过程。通过这个过程,我不仅提升…...
FFmpeg 低延迟同屏方案
引言 在实时互动需求激增的当下,无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作,还是游戏直播的画面实时传输,低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架,凭借其灵活的编解码、数据…...

遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...

Debian系统简介
目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版ÿ…...

抖音增长新引擎:品融电商,一站式全案代运营领跑者
抖音增长新引擎:品融电商,一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中,品牌如何破浪前行?自建团队成本高、效果难控;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...
CRMEB 框架中 PHP 上传扩展开发:涵盖本地上传及阿里云 OSS、腾讯云 COS、七牛云
目前已有本地上传、阿里云OSS上传、腾讯云COS上传、七牛云上传扩展 扩展入口文件 文件目录 crmeb\services\upload\Upload.php namespace crmeb\services\upload;use crmeb\basic\BaseManager; use think\facade\Config;/*** Class Upload* package crmeb\services\upload* …...

JVM虚拟机:内存结构、垃圾回收、性能优化
1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...

VSCode 使用CMake 构建 Qt 5 窗口程序
首先,目录结构如下图: 运行效果: cmake -B build cmake --build build 运行: windeployqt.exe F:\testQt5\build\Debug\app.exe main.cpp #include "mainwindow.h"#include <QAppli...

Unity-ECS详解
今天我们来了解Unity最先进的技术——ECS架构(EntityComponentSystem)。 Unity官方下有源码,我们下载源码后来学习。 ECS 与OOP(Object-Oriented Programming)对应,ECS是一种完全不同的编程范式与数据架构…...