当前位置: 首页 > news >正文

海康工业相机的应用部署不是简简单单!?

作者: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版本的客户端

image-20250118041419982

        下载完成后我们会得到一个ZIP压缩包,我们解压后可以看到有很多不同CPU架构的版本,我们选择适合自己系统的版本使用如下命令即可完成安装,安装完成后的客户端将在/opt/MVS目录下

微信截图_20250117134813

sudo dpkg -i MVS-***********.deb #自行替换适合自己的系统版本安装包

        我们使用如下命令即可进入MVS安装环境并且启动我们的客户端

cd /opt/MVS/bin #进入软件目录
./MVS.sh #运行客户端

        运行客户端后我们可以看到我们的客户端主界面如下,左边主要为我们的设备区在这个区域我们可以看到当前连接到我们电脑的所有海康相机设备,最上方为菜单栏提供文件、视图、设置、工具和帮助的功能,下方为控制工具条可以为相机参数保存等操作提供快捷入口

image-20250118042210593

        接着我们选中我们的相机可以看到我们进入了如下界面,在这个界面中左下方为接口和设备信息获取窗口可以显示设备详细信息,中间为图像预览窗口上方左一按钮点击后即可开启相机实时取流查看图像,右边为属性设置窗口,可以对相机的基本参数进行设置,如:触发模式、增益、曝光时间、帧率、像素格式等等

image-20250118042654848

        由于属性设置客户端中有中文介绍,那么我们接着主要开始讲解我们的SDK-API的使用方法叭

二、海康相机开发实战上手

        在我们上一节安装的MVS目录下存在着海康相机驱动所需要的全部头文件以及链接库,我们首先在我们的工作空间下面新建一个文件夹,接着将MVS安装目录下的includelib文件全部复制进来

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文件

        我们首先来看一下海康相机组件包的项目结构叭,要驱动海康工业相机需要有两个主要的驱动包includelib,其具体的结构以及对应的作用请看下图:

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的主要接口文件

image-20250117141841904

  • 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_CreateHandleAPI介绍

/********************************************************************//***  @~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_SetEnumValueMV_CC_SetFloatValue同时我们可以通过MV_CC_GetFloatValueAPI来获取每一个属性键值的可调节参数范围,这些函数的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.fMinstExposureTime.fMax的范围,如果没有那么我们便将exposureTime使用MV_CC_SetFloatValueAPI进行设置

// 获取和设置曝光时间
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;
}

        最后我们完整的代码及运行效果如下:(但是又有同学要问了,我们该如何显示图像呢?请继续翻到下面,我将在下面进行介绍)

image-20250117235331337

#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显示图像的完整的代码及显示效果如下:

image-20250118034644685

image-20250118034858385

#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的完整的代码及效果如下:

image-20250118034359365

image-20250118034341634

#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;
}

        有任何问题大家都可以在评论区询问哦,我看到了便会及时回复哒!!!

相关文章:

海康工业相机的应用部署不是简简单单!?

作者&#xff1a;SkyXZ CSDN&#xff1a;SkyXZ&#xff5e;-CSDN博客 博客园&#xff1a;SkyXZ - 博客园 笔者使用的设备及环境&#xff1a;WSL2-Ubuntu22.04MV-CS016-10UC 不会吧&#xff1f;不会吧&#xff1f;不会还有人拿到海康工业相机还是一脸懵叭&#xff1f;不会还有人…...

SAP POC 项目完工进度 - 收入确认方式【工程制造行业】【新准则下工程项目收入确认】

1. SAP POC收入确认基础概念 1.1 定义与原则 SAP POC&#xff08;Percentage of Completion&#xff09;收入确认方式是一种基于项目完工进度来确认收入的方法。其核心原则是根据项目实际完成的工作量或成本投入占预计总工作量或总成本的比例&#xff0c;来确定当期应确认的收…...

【Elasticsearch 】 聚合分析:聚合概述

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编…...

【算法】二分

二分 1.二分查找1.在排序数组中查找元素的第一个和最后一个位置2.牛可乐和魔法封印3.A-B 数对4.烦恼的高考志愿 2.二分答案1.木材加工2.砍树3.跳石头 1.二分查找 当我们的解具有二段性&#xff08;根据最终答案所在的位置判断是否具有二段性&#xff09;时&#xff0c;就可以使…...

如何将自己本地项目开源到github上?

环境&#xff1a; LLMB项目 问题描述&#xff1a; 如何将自己本地项目开源到github上&#xff1f; 解决方案&#xff1a; 步骤 1: 准备本地项目 确保项目整洁 确认所有的文件都在合适的位置&#xff0c;并且项目的 README.md 文件已经完善。检查是否有敏感信息&#xff0…...

编辑器Vim基本模式和指令 --【Linux基础开发工具】

文章目录 一、编辑器Vim 键盘布局二、Linux编辑器-vim使用三、vim的基本概念正常/普通/命令模式(Normal mode)插入模式(Insert mode)末行模式(last line mode) 四、vim的基本操作五、vim正常模式命令集插入模式从插入模式切换为命令模式移动光标删除文字复制替换撤销上一次操作…...

Scade 表达式 - 使用索引的迭代器

Scade 表达式中的 map, fold, mapfold&#xff0c;会对输入数组参数中的元素逐个作处理&#xff0c;不需要数组元素的索引信息。若在处理数组元素时&#xff0c;需要数组元素相应的索引信息&#xff0c;则可使用迭代器算子 mapi, foldi, mapfoldi。 mapi 算子 mapi 算子的行为…...

K8s学习

Kubernetes 1. Kubernetes介绍 1.1 应用部署方式演变 在部署应用程序的方式上&#xff0c;主要经历了三个时代&#xff1a; 传统部署&#xff1a;互联网早期&#xff0c;会直接将应用程序部署在物理机上 优点&#xff1a;简单&#xff0c;不需要其它技术的参与 缺点&#xf…...

面试--你的数据库中密码是如何存储的?

文章目录 三种分类使用 MD5 加密存储加盐存储Base64 编码:常见的对称加密算法常见的非对称加密算法https 传输加密 在开发中需要存储用户的密码&#xff0c;这个密码一定是加密存储的&#xff0c;如果是明文存储那么如果数据库被攻击了&#xff0c;密码就泄露了。 我们要对数据…...

微服务学习-快速搭建

1. 速通版 1.1. git clone 拉取项目代码&#xff0c;导入 idea 中 git clone icoolkj-microservices-code: 致力于搭建微服务架构平台 1.2. git checkout v1.0.1版本 链接地址&#xff1a;icoolkj-microservices-code 标签 - Gitee.com 2. 项目服务结构 3. 实现重点步骤 …...

兼职全职招聘系统架构与功能分析

2015工作至今&#xff0c;10年资深全栈工程师&#xff0c;CTO&#xff0c;擅长带团队、攻克各种技术难题、研发各类软件产品&#xff0c;我的代码态度&#xff1a;代码虐我千百遍&#xff0c;我待代码如初恋&#xff0c;我的工作态度&#xff1a;极致&#xff0c;责任&#xff…...

【云岚到家】-day03-门户缓存实现实战

【云岚到家】-day03-门户缓存实现实战 1.定时任务更新缓存 1.1 搭建XXL-JOB环境 1.1.1 分布式调度平台XXL-JOB介绍 对于开通区域列表的缓存数据需要由定时任务每天凌晨更新缓存&#xff0c;如何实现定时任务呢&#xff1f; 1.使用jdk提供的Timer定时器 示例代码如下&#xf…...

Ubuntu 24.04 LTS 开启 SMB 服务,并通过 windows 访问

Ubuntu 24.04 LTS 背景资料 Ubuntu服务器折腾集Ubuntu linux 文件权限Ubuntu 空闲硬盘挂载到 文件管理器的 other locations Ubuntu开启samba和window共享文件 Ubuntu 配置 SMB 服务 安装 Samba 确保 Samba 已安装。如果未安装&#xff0c;运行以下命令进行安装&#xff…...

“AI人工智能内容辅助创作平台:让创意不再“卡壳”

在如今这个信息爆炸的时代&#xff0c;内容创作成了每个人的“必修课”。无论是自媒体大V、文案策划&#xff0c;还是普通学生写作文&#xff0c;大家都会遇到一个让人抓狂的问题——“创意枯竭”。有时候&#xff0c;脑袋里空空如也&#xff0c;一个字都写不出来&#xff0c;那…...

mac 安装 node

brew versions node // 安装 node brew versions node14 // 安装指定版本 卸载node: sudo npm uninstall npm -g sudo rm -rf /usr/local/lib/node /usr/local/lib/node_modules /var/db/receipts/org.nodejs.* sudo rm -rf /usr/local/include/node /Users/$USER/.npm su…...

VUE之Router使用及工作模式

1、路由的使用 【两个注意点】 1)路由组件通常放在pages 或 views文件夹,一般组件通常放在components文件夹。 2)通过点击导航,视觉效果上"消失"了的路由组件,默认是被"卸载"掉的,需要的时候再去挂载。 // 创建一个路由器,并暴露出去// 第一步:…...

day25_HTML

今日内容 零、 复习昨日 一、HTML 零、 复习昨日 一、Web开发 前端 HTML ,页面展现CSS , 样式JS (JavaScript) , 动起来 二、HTML 2.1 HTML概念 ​ 网页&#xff0c;是网站中的一个页面&#xff0c;通常是网页是构成网站的基本元素&#xff0c;是承载各种网站应用的平台。通俗…...

(开源)基于Django+Yolov8+Tensorflow的智能鸟类识别平台

1 项目简介&#xff08;开源地址在文章结尾&#xff09; 系统旨在为了帮助鸟类爱好者、学者、动物保护协会等群体更好的了解和保护鸟类动物。用户群体可以通过平台采集野外鸟类的保护动物照片和视频&#xff0c;甄别分类、实况分析鸟类保护动物&#xff0c;与全世界各地的用户&…...

【AI日记】25.01.20

【AI论文解读】【AI知识点】【AI小项目】【AI战略思考】【AI日记】【读书与思考】 AI kaggle 比赛&#xff1a;Forecasting Sticker Sales 读书 书名&#xff1a;自由宪章阅读原因&#xff1a;作者哈耶克&#xff0c;诺贝尔经济学奖得主&#xff0c;之前读过他的 《通往奴役…...

基于机器学习的用户健康风险分类及预测分析

完整源码项目包获取→点击文章末尾名片&#xff01; 背景描述 在这个日益注重健康与体能的时代&#xff0c;健身已成为许多人追求健康生活的重要组成部分。 本数据集包含若干健身房会员的详细信息&#xff0c;包括年龄、性别、体重、身高、心率、锻炼类型、身体脂肪比例等多项关…...

AI生成内容——JavaScript中的Promise、async和wait

一、Promise *1. 概念&#xff1a; Promise 是 JavaScript 中处理异步操作的一种对象&#xff0c;它表示一个异步操作的最终完成&#xff08;或失败&#xff09;及其结果值。一个 Promise 对象处于以下三种状态之一&#xff1a; Pending&#xff08;进行中&#xff09;&#…...

Java基于SSM框架的社区团购系统小程序设计与实现(附源码,文档,部署)

Java基于SSM框架的社区团购系统小程序设计与实现 博主介绍&#xff1a;✌程序猿徐师兄、8年大厂程序员经历。全网粉丝15w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f4…...

Git原理与应用(三)【远程操作 | 理解分布式 | 推送拉取远程仓库 | 标签管理】

Git 理解分布式版本控制系统远程仓库新建远程仓库克隆远程仓库向远程仓库推送配置Git忽略特殊文件 标签管理理解标签创建标签操作标签删除标签 理解分布式版本控制系统 我们⽬前所说的所有内容&#xff08;工作区&#xff0c;暂存区&#xff0c;版本库等等&#xff09;&#x…...

【esp32小程序】小程序篇02——连接git

一、创建仓库 进入gitee官网&#xff0c;登录&#xff08;如果没有gitee账号的就自行注册一下&#xff09;。 点击号-->新建仓库 填写好必填信息&#xff0c;然后点击“创建” 二、微信开发者工具配置 在微信开发者工具打开我们的项目。按下面的步骤依次点击 三、验证 点…...

MongoDB基本操作

一、实验目的 1. 熟悉MongoDB的基本操作&#xff0c;包括CRUD&#xff08;增加、读取、更新、删除&#xff09;。 2. 理解MongoDB的文档型数据库特性和Shell的使用。 3. 培养学生通过命令行操作数据库的能力。 4. 强化数据库操作的实际应用能力。 二、实验环境准备 1.…...

Brooks MagnaTran LEAP User Manual 指导半导体机械手

Brooks MagnaTran LEAP User Manual 指导半导体机械手...

【Red Hat8】:搭建DHCP服务器

1、新建挂载文件 2、挂载 3、关闭防火墙 4、搭建yum源 &#xff08;搭建的时候用vim 自行定义文件名.repo或者是vi 自行定义文件名.repo&#xff09; 5、安装dhcp-server 6、复制模板文件 dhcpd.conf 是DHCP服务的配置文件&#xff0c;DHCP服务所有参数都是通过修改dhcpd.co…...

JupyterLab 安装以及部分相关配置

安装 JupyterLab pip install jupyter启动 JupyterLab jupyter lab [--port <指定的端口号>] [--no-browser] # --port 指定端口 # --no-browser 启动时不打开浏览器安装中文 首先安装中文包 pip install jupyterlab-language-pack-zh-CN安装完成后重启 JupyterLab 选…...

深圳桂湾公园的花海

工作日的午休时间我经常骑行到桂湾公园&#xff0c;时不时都能碰上一些阿姨问&#xff1a;小伙子你知道桂湾公园的花海在哪里吗&#xff1f;我找了半天了哈。我发现不少找花海的人是从桂湾地铁或前湾地铁下车&#xff0c;然后在偌大的桂湾公园找寻。其实只要定位前海紫荆园就好…...

寒假刷题Day10

一、220. 存在重复元素 III 两种解法&#xff1a;并没有弄懂&#xff0c;待复盘 class Solution { public:bool containsNearbyAlmostDuplicate(vector<int>& nums, int k, int t) {set<long> st;for (int i 0; i < nums.size(); i) {auto lb st.lower_…...

【Java-图片存储方案】

Java功能相关文章 一、Minio存储大体量图片 上传到Minio指定路径&#xff0c;前端预览时,需要生成临时访问凭证的URL import io.minio.MinioClient; import io.minio.errors.MinioException; import io.minio.http.Method; import io.minio.GetPresignedObjectUrlArgs; impo…...

机器人传动力系统介绍

以下是对机器人驱动系统的分析、最新科技应用以及世界顶级公司机器人型号使用的技术&#xff1a; 机器人驱动系统分析 液压驱动&#xff1a;利用液体压力来传递动力&#xff0c;通过液压泵将液压油从油箱抽出&#xff0c;送至液压缸&#xff0c;推动活塞运动&#xff0c;进而…...

DDD - 微服务落地的技术实践

文章目录 Pre概述如何发挥微服务的优势怎样提供微服务接口原则微服务的拆分与防腐层的设计 去中心化的数据管理数据关联查询的难题Case 1Case 2Case 3 总结 Pre DDD - 软件退化原因及案例分析 DDD - 如何运用 DDD 进行软件设计 DDD - 如何运用 DDD 进行数据库设计 DDD - 服…...

《Vue3 十》Vue 底层原理

命令式编程和声明式编程&#xff1a; 以计时器为例&#xff1a; // 原生 JavaScript 实现计数器&#xff0c;是命令式编程 <div><h1>当前数字&#xff1a;<span class"count"></span></h1><button class"add" click&qu…...

GMM高斯混合聚类算法(Matlab)

目录 效果一览基本介绍程序设计参考资料 效果一览 基本介绍 GMM高斯混合聚类算法 matlab2023b语言&#xff0c;一键出图&#xff0c;直接运行 1.代码注释清晰&#xff0c;自行解读容易。 2…输出图例如图所示包括&#xff1a;聚类图(聚类结果图)&#xff0c;协方差矩阵类型…...

【Leetcode 每日一题】2266. 统计打字方案数

问题背景 Alice 在给 Bob 用手机打字。数字到字母的 对应 如下图所示。 为了 打出 一个字母&#xff0c;Alice 需要 按 对应字母 i i i 次&#xff0c; i i i 是该字母在这个按键上所处的位置。 比方说&#xff0c;为了按出字母 ‘s’ &#xff0c;Alice 需要按 ‘7’ 四次…...

多线程杂谈:惊群现象、CAS、安全的单例

引言 本文是一篇杂谈&#xff0c;帮助大家了解多线程可能会出现的面试题。 目录 引言 惊群现象 结合条件变量 CAS原子操作&#xff08;cmp & swap&#xff09; 线程控制&#xff1a;两个线程交替打印奇偶数 智能指针线程安全 单例模式线程安全 最简单的单例&…...

Nginx调优

Nginx 是一个高性能的反向代理服务器和负载均衡器&#xff0c;在处理大量并发请求时表现出色。但是&#xff0c;随着系统负载的增加&#xff0c;Nginx 的性能可能受到多方面的影响&#xff0c;因此进行适当的调优至关重要。以下是 Nginx 调优的几个方向和关键点&#xff1a; 1…...

自定义UITableViewCell

很多时候&#xff0c;我们是不能直接使用系统自带的UITableViewCell&#xff0c;因为自带的比较简单只有一个UIImageView和两个UILabel&#xff0c;假设需要多个UIImageView或者两个以上UILabel&#xff0c;那就需要自定义了。本文就实现如何自定义UITableViewCell。 假设我们现…...

Java 基于微信小程序的原创音乐小程序设计与实现(附源码,部署,文档)

大家好&#xff0c;我是stormjun&#xff0c;今天为大家带来的是Java实战项目-基于微信小程序的原创音乐小程序设计与实现。该系统采用 Java 语言 开发&#xff0c;MySql 作为数据库&#xff0c;系统功能完善 &#xff0c;实用性强 &#xff0c;可供大学生实战项目参考使用。 博…...

MySQL —— 事务

概念 事务把组SQL语句打包成为个整体&#xff0c;在这组SQL的执行过程中&#xff0c;要么全部成功&#xff0c;要么全部失败。 这组SQL语句可以是条也可以是多条。 ACID 特性 原子性 Atomicity(原子性)&#xff1a;一个事务中的所有操作&#xff0c;要么全部成功&#xff0…...

【大模型】ChatGPT 高效处理图片技巧使用详解

目录 一、前言 二、ChatGPT 4 图片处理介绍 2.1 ChatGPT 4 图片处理概述 2.1.1 图像识别与分类 2.1.2 图像搜索 2.1.3 图像生成 2.1.4 多模态理解 2.1.5 细粒度图像识别 2.1.6 生成式图像任务处理 2.1.7 图像与文本互动 2.2 ChatGPT 4 图片处理应用场景 三、文生图操…...

SpringBoot注入配置文件application.properties中的信息

问题&#xff1a;将可能会变动的配置信息硬编码在代码中&#xff0c;在修改时难以定位&#xff0c;且过于繁琐&#xff0c;怎么办&#xff1f; 解决&#xff1a;自定义application.properties配置文件中的信息&#xff0c;注意严格遵循ab的形式&#xff0c;不要加引号&#xf…...

大数据,Hadoop,HDFS的简单介绍

大数据 海量数据&#xff0c;具有高增长率、数据类型多样化、一定时间内无法使用常规软件工具进行捕捉、管理和处理的数据集 合 大数据的特征: 4V Volume : 巨大的数据量 Variety : 数据类型多样化 结构化的数据 : 即具有固定格式和有限长度的数据 半结构化的数据 : 是…...

第15章:Python TDD应对货币类开发变化(二)

写在前面 这本书是我们老板推荐过的&#xff0c;我在《价值心法》的推荐书单里也看到了它。用了一段时间 Cursor 软件后&#xff0c;我突然思考&#xff0c;对于测试开发工程师来说&#xff0c;什么才更有价值呢&#xff1f;如何让 AI 工具更好地辅助自己写代码&#xff0c;或许…...

黑马点评之导入初始项目(java)

&#xff01;&#xff01;&#xff01;由于我一开始是在网盘上下载的资源&#xff0c;后面忙活半天&#xff0c;发现代码是不完整的&#xff0c;才知道需要在github上面拉取初始代码。 然后第二点是我的本地环境是jdk21&#xff0c;但是他原本的代码为jdk8&#xff0c;所以在换…...

Erlang语言的语法糖

Erlang语言的语法糖&#xff1a;简化编程的灵活工具 Erlang是一种功能强大的编程语言&#xff0c;最初由爱立信&#xff08;Ericsson&#xff09;为电信系统开发。它以其高并发性、容错性和分布式特性而闻名&#xff0c;特别适合构建实时系统。然而&#xff0c;Erlang的语法相…...

数据库基础知识:记录、表、字段、数据类型、约束、主键、外键、规范化、索引、序列

数据库是由一个或多个有组织的数据集合组成&#xff0c;而数据库管理系统&#xff08;DBMS&#xff09;是操作数据库的软件&#xff0c;包括很多人本科上课学过的SQL Server&#xff0c;现在常用的MySQL、Postgresql等&#xff0c;用于提供数据的存储、访问、运行和维护等。学习…...

Centos 8 交换空间管理

新增swap 要增加 Linux 系统的交换空间&#xff0c;可以按照以下步骤操作&#xff1a; 1. 创建一个交换文件 首先&#xff0c;选择文件路径和大小&#xff08;例如&#xff0c;增加 1 GB 交换空间&#xff09;。 sudo fallocate -l 1G /swapfile如果 fallocate 不可用&…...

迈向 “全能管家” 之路:机器人距离终极蜕变还需几步?

【图片来源于网络&#xff0c;侵删】 这是2024年初Figure公司展示的人形机器人Figure 01&#xff0c;他可以通过观看人类的示范视频&#xff0c;在10小时内经过训练学会煮咖啡&#xff0c;并且这个过程是完全自主没有人为干涉的&#xff01; 【图片来源于网络&#xff0c;侵删】…...