在机器视觉项目开发中,相机的初始化、参数读取与设置是最基础也是最关键的环节。本文基于海康机器人(Hikrobot)提供的MVS SDK,使用C语言实现了一个简洁的控制程序,完成设备枚举、连接以及常用参数的获取与设置。
📌 功能概述
该程序主要实现了以下功能:
初始化SDK环境
自动枚举当前连接的所有相机设备(支持GigE、USB、CameraLink、CXP等类型)
打印每台设备的基本信息,如型号、IP地址、用户自定义名称等
用户可选择指定设备进行操作
连接设备后,对常见类型的参数进行读写操作:
整型参数:如图像高度(Height)
浮点型参数:如曝光时间(ExposureTime)
枚举型参数:如触发模式(TriggerMode)
布尔型参数:如图像翻转(ReverseX)
字符串型参数:如设备用户ID(DeviceUserID)
最后关闭设备并释放资源
#include <stdio.h> // 标准输入输出库,用于 printf, scanf 等
#include <string.h> // 字符串操作库,用于 memset 等
#include "MvCameraControl.h" // 海康机器人(Hikrobot)相机 SDK 头文件,包含所有相机控制函数和结构体定义
// =================================================================================================
// 函数:PressEnterToExit
// 用途:等待用户按回车键以退出程序(本程序中未实际调用,但可作为调试辅助)
// 说明:该函数用于阻塞程序,等待用户输入回车,常用于防止控制台程序闪退
// =================================================================================================
void PressEnterToExit(void)
{
int c;
// 清空输入缓冲区中的残留字符(如之前 scanf 留下的换行符)
while ( (c = getchar()) != '\n' && c != EOF );
// 提示用户按回车退出
fprintf( stderr, "\n请按回车键退出。\n");
// 再次等待用户按回车
while( getchar() != '\n');
}
// =================================================================================================
// 函数:PrintDeviceInfo
// 用途:根据设备类型打印相机的详细信息(型号、IP、用户自定义名称等)
// 参数:pstMVDevInfo - 指向设备信息结构体的指针
// 返回值:成功返回 true,失败返回 false
// 说明:根据设备的传输层类型(GigE、USB、CameraLink 等)使用不同的结构体成员打印信息
// =================================================================================================
bool PrintDeviceInfo(MV_CC_DEVICE_INFO* pstMVDevInfo)
{
// 检查指针是否为空,防止空指针访问
if (NULL == pstMVDevInfo)
{
printf("设备信息指针为空!\n");
return false;
}
// 根据设备的传输层类型进行分支处理
if (pstMVDevInfo->nTLayerType == MV_GIGE_DEVICE)
{
// GigE 网口相机:解析当前IP地址(32位整数转为点分十进制)
int nIp1 = ((pstMVDevInfo->SpecialInfo.stGigEInfo.nCurrentIp & 0xff000000) >> 24);
int nIp2 = ((pstMVDevInfo->SpecialInfo.stGigEInfo.nCurrentIp & 0x00ff0000) >> 16);
int nIp3 = ((pstMVDevInfo->SpecialInfo.stGigEInfo.nCurrentIp & 0x0000ff00) >> 8);
int nIp4 = (pstMVDevInfo->SpecialInfo.stGigEInfo.nCurrentIp & 0x000000ff);
// 打印 GigE 相机信息
printf("设备型号名称: %s\n", pstMVDevInfo->SpecialInfo.stGigEInfo.chModelName);
printf("当前IP地址: %d.%d.%d.%d\n" , nIp1, nIp2, nIp3, nIp4);
printf("用户自定义名称: %s\n\n" , pstMVDevInfo->SpecialInfo.stGigEInfo.chUserDefinedName);
}
else if (pstMVDevInfo->nTLayerType == MV_USB_DEVICE)
{
// USB3.0 相机:打印型号和用户自定义名称
printf("设备型号名称: %s\n", pstMVDevInfo->SpecialInfo.stUsb3VInfo.chModelName);
printf("用户自定义名称: %s\n\n", pstMVDevInfo->SpecialInfo.stUsb3VInfo.chUserDefinedName);
}
else if (pstMVDevInfo->nTLayerType == MV_GENTL_GIGE_DEVICE)
{
// GenTL 协议下的 GigE 相机:打印用户自定义名、序列号、型号
printf("用户自定义名称: %s\n", pstMVDevInfo->SpecialInfo.stGigEInfo.chUserDefinedName);
printf("序列号: %s\n", pstMVDevInfo->SpecialInfo.stGigEInfo.chSerialNumber);
printf("型号名称: %s\n\n", pstMVDevInfo->SpecialInfo.stGigEInfo.chModelName);
}
else if (pstMVDevInfo->nTLayerType == MV_GENTL_CAMERALINK_DEVICE)
{
// CameraLink 相机:打印用户自定义名、序列号、型号
printf("用户自定义名称: %s\n", pstMVDevInfo->SpecialInfo.stCMLInfo.chUserDefinedName);
printf("序列号: %s\n", pstMVDevInfo->SpecialInfo.stCMLInfo.chSerialNumber);
printf("型号名称: %s\n\n", pstMVDevInfo->SpecialInfo.stCMLInfo.chModelName);
}
else if (pstMVDevInfo->nTLayerType == MV_GENTL_CXP_DEVICE)
{
// CoaXPress (CXP) 相机:打印用户自定义名、序列号、型号
printf("用户自定义名称: %s\n", pstMVDevInfo->SpecialInfo.stCXPInfo.chUserDefinedName);
printf("序列号: %s\n", pstMVDevInfo->SpecialInfo.stCXPInfo.chSerialNumber);
printf("型号名称: %s\n\n", pstMVDevInfo->SpecialInfo.stCXPInfo.chModelName);
}
else if (pstMVDevInfo->nTLayerType == MV_GENTL_XOF_DEVICE)
{
// XoF (如光纤) 相机:打印用户自定义名、序列号、型号
printf("用户自定义名称: %s\n", pstMVDevInfo->SpecialInfo.stXoFInfo.chUserDefinedName);
printf("序列号: %s\n", pstMVDevInfo->SpecialInfo.stXoFInfo.chSerialNumber);
printf("型号名称: %s\n\n", pstMVDevInfo->SpecialInfo.stXoFInfo.chModelName);
}
else
{
// 不支持的设备类型
printf("设备类型不支持。\n");
}
return true;
}
// =================================================================================================
// 主函数:main
// 用途:演示如何使用海康相机 SDK 进行设备枚举、参数读写等基本操作
// 流程:
// 1. 初始化 SDK
// 2. 枚举所有连接的相机设备
// 3. 打印设备信息
// 4. 用户选择一个设备
// 5. 创建句柄并打开设备
// 6. 读取并设置各种类型的相机参数(int, float, enum, bool, string)
// 7. 关闭设备并销毁句柄
// 8. 反初始化 SDK
// =================================================================================================
int main()
{
int nRet = MV_OK; // 用于存储 SDK 函数调用的返回值,MV_OK 表示成功
void* handle = NULL; // 相机设备的句柄(类似于文件描述符),用于后续所有操作
// 使用 do-while(0) 结构实现“伪 goto”,便于在任意步骤出错时跳出并统一清理资源
do
{
// ================== 步骤 1:初始化 SDK ==================
nRet = MV_CC_Initialize();
if (MV_OK != nRet)
{
printf("初始化SDK失败!错误码 [0x%x]\n", nRet);
break; // 初始化失败,跳出循环
}
printf("SDK初始化成功。\n");
// 定义设备列表结构体,用于存储枚举到的设备信息
MV_CC_DEVICE_INFO_LIST stDeviceList;
memset(&stDeviceList, 0, sizeof(MV_CC_DEVICE_INFO_LIST)); // 结构体清零,防止野值
// ================== 步骤 2:枚举设备 ==================
// 枚举所有支持的设备类型(GigE, USB, CameraLink, CXP, XoF)
nRet = MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE | MV_GENTL_CAMERALINK_DEVICE | MV_GENTL_CXP_DEVICE | MV_GENTL_XOF_DEVICE, &stDeviceList);
if (MV_OK != nRet)
{
printf("枚举设备失败!错误码 [%x]\n", nRet);
break;
}
// 检查是否找到设备
if (stDeviceList.nDeviceNum > 0)
{
printf("共发现 %d 台设备:\n", stDeviceList.nDeviceNum);
for (int i = 0; i < stDeviceList.nDeviceNum; i++)
{
printf("[设备 %d]:\n", i);
MV_CC_DEVICE_INFO* pDeviceInfo = stDeviceList.pDeviceInfo[i]; // 获取第 i 个设备的信息指针
if (NULL == pDeviceInfo)
{
break;
}
PrintDeviceInfo(pDeviceInfo); // 调用函数打印设备信息
}
}
else
{
printf("未发现任何设备!\n");
break; // 无设备,跳出
}
// ================== 步骤 3:用户选择设备 ==================
printf("请输入要操作的相机序号: ");
unsigned int nIndex = 0;
if(0 == scanf("%d", &nIndex)) // 检查输入是否为有效整数
{
printf("输入格式错误!\n");
break;
}
// 检查用户输入的索引是否有效
if (nIndex >= stDeviceList.nDeviceNum)
{
printf("输入错误!序号超出范围。\n");
break;
}
// ================== 步骤 4:创建设备句柄 ==================
// 根据用户选择的设备信息创建句柄
nRet = MV_CC_CreateHandle(&handle, stDeviceList.pDeviceInfo[nIndex]);
if (MV_OK != nRet)
{
printf("创建设备句柄失败!错误码 [%x]\n", nRet);
break;
}
printf("设备句柄创建成功。\n");
// ================== 步骤 5:打开设备 ==================
// 打开指定的相机设备,建立通信
nRet = MV_CC_OpenDevice(handle);
if (MV_OK != nRet)
{
printf("打开设备失败!错误码 [%x]\n", nRet);
break;
}
printf("设备打开成功。\n");
// ================== 步骤 6:获取并设置各种类型的相机参数 ==================
// --- 6.1 获取 int 型参数:图像高度 (Height) ---
MVCC_INTVALUE stHeight = {0}; // 定义结构体存储 int 型参数信息
nRet = MV_CC_GetIntValue(handle, "Height", &stHeight);
if (MV_OK == nRet)
{
printf("图像高度 - 当前值: %d\n", stHeight.nCurValue);
printf("图像高度 - 最大值: %d\n", stHeight.nMax);
printf("图像高度 - 最小值: %d\n", stHeight.nMin);
printf("图像高度 - 增量: %d\n\n", stHeight.nInc);
}
else
{
printf("获取图像高度失败!错误码 [%x]\n\n", nRet);
}
// --- 6.2 设置 int 型参数:图像高度 ---
unsigned int nHeightValue = 0;
printf("请输入要设置的图像高度: ");
if(0 == scanf("%d", &nHeightValue))
{
printf("输入格式错误!\n");
break;
}
// 注意:某些相机的宽高设置有步进要求(如16的倍数)
nRet = MV_CC_SetIntValueEx(handle, "Height", nHeightValue);
if (MV_OK == nRet)
{
printf("设置图像高度成功!\n\n");
}
else
{
printf("设置图像高度失败!错误码 [%x]\n\n", nRet);
}
// --- 6.3 获取 float 型参数:曝光时间 (ExposureTime) ---
MVCC_FLOATVALUE stExposureTime = {0};
nRet = MV_CC_GetFloatValue(handle, "ExposureTime", &stExposureTime);
if (MV_OK == nRet)
{
printf("曝光时间 - 当前值: %f\n", stExposureTime.fCurValue);
printf("曝光时间 - 最大值: %f\n", stExposureTime.fMax);
printf("曝光时间 - 最小值: %f\n\n", stExposureTime.fMin);
}
else
{
printf("获取曝光时间失败!错误码 [%x]\n\n", nRet);
}
// --- 6.4 设置 float 型参数:曝光时间 ---
float fExposureTime = 0.0f;
printf("请输入要设置的曝光时间: ");
if(0 == scanf("%f", &fExposureTime))
{
printf("输入格式错误!\n");
break;
}
nRet = MV_CC_SetFloatValue(handle, "ExposureTime", fExposureTime);
if (MV_OK == nRet)
{
printf("设置曝光时间成功!\n\n");
}
else
{
printf("设置曝光时间失败!错误码 [%x]\n\n", nRet);
}
// --- 6.5 获取 enum 型参数:触发模式 (TriggerMode) ---
MVCC_ENUMVALUE stTriggerMode = {0};
nRet = MV_CC_GetEnumValue(handle, "TriggerMode", &stTriggerMode);
if (MV_OK == nRet)
{
printf("触发模式 - 当前值: %d\n", stTriggerMode.nCurValue);
printf("支持的触发模式数量: %d\n", stTriggerMode.nSupportedNum);
for (unsigned int i = 0; i < stTriggerMode.nSupportedNum; ++i)
{
printf("支持的触发模式 [%d]: %d\n", i, stTriggerMode.nSupportValue[i]);
}
printf("\n");
}
else
{
printf("获取触发模式失败!错误码 [%x]\n\n", nRet);
}
// --- 6.6 设置 enum 型参数:触发模式 ---
unsigned int nTriggerMode = 0;
printf("请输入要设置的触发模式: ");
if(0 == scanf("%d", &nTriggerMode))
{
printf("输入格式错误!\n");
break;
}
nRet = MV_CC_SetEnumValue(handle, "TriggerMode", nTriggerMode);
if (MV_OK == nRet)
{
printf("设置触发模式成功!\n\n");
}
else
{
printf("设置触发模式失败!错误码 [%x]\n\n", nRet);
}
// --- 6.7 获取 bool 型参数:图像X方向翻转 (ReverseX) ---
bool bGetBoolValue = false;
nRet = MV_CC_GetBoolValue(handle, "ReverseX", &bGetBoolValue);
if (MV_OK == nRet)
{
printf("图像X方向翻转 - 当前值: %s\n\n", bGetBoolValue ? "开启" : "关闭");
}
else
{
printf("获取图像X方向翻转状态失败!错误码 = [%x]\n\n", nRet);
}
// --- 6.8 设置 bool 型参数:图像X方向翻转 ---
int nSetBoolValue;
bool bSetBoolValue;
printf("请输入图像X方向翻转状态 (0=关闭, 1=开启): ");
if(0 == scanf("%d", &nSetBoolValue))
{
printf("输入格式错误!\n");
break;
}
bSetBoolValue = (nSetBoolValue != 0); // 将整数转换为 bool
nRet = MV_CC_SetBoolValue(handle, "ReverseX", bSetBoolValue);
if (MV_OK == nRet)
{
printf("设置图像X方向翻转成功!\n\n");
}
else
{
printf("设置图像X方向翻转失败!错误码 = [%x]\n\n", nRet);
}
// --- 6.9 获取 string 型参数:设备用户ID (DeviceUserID) ---
MVCC_STRINGVALUE stStringValue = {0};
nRet = MV_CC_GetStringValue(handle, "DeviceUserID", &stStringValue);
if (MV_OK == nRet)
{
printf("当前设备用户ID: [%s]\n\n", stStringValue.chCurValue);
}
else
{
printf("获取设备用户ID失败!错误码 = [%x]\n\n", nRet);
}
// --- 6.10 设置 string 型参数:设备用户ID ---
unsigned char strValue[256]; // 用于存储用户输入的字符串
printf("请输入要设置的设备用户ID (字符串): ");
if(0 == scanf("%s", strValue)) // 注意:此处有缓冲区溢出风险,实际应用中应使用 scanf_s 或 fgets
{
printf("输入格式错误!\n");
break;
}
nRet = MV_CC_SetStringValue(handle, "DeviceUserID", (char*)strValue);
if (MV_OK == nRet)
{
printf("设置设备用户ID成功!\n\n");
}
else
{
printf("设置设备用户ID失败!错误码 = [%x]\n\n", nRet);
}
// ================== 步骤 7:关闭设备 ==================
nRet = MV_CC_CloseDevice(handle);
if (MV_OK != nRet)
{
printf("关闭设备失败!错误码 [%x]\n", nRet);
break;
}
printf("设备已关闭。\n");
// ================== 步骤 8:销毁句柄 ==================
nRet = MV_CC_DestroyHandle(handle);
if (MV_OK != nRet)
{
printf("销毁设备句柄失败!错误码 [%x]\n", nRet);
break;
}
handle = NULL; // 避免悬空指针
printf("设备句柄已销毁。\n");
} while (0); // do-while(0) 结束,用于错误处理跳转
// ================== 步骤 9:异常清理 ==================
// 如果循环中出错导致 handle 不为 NULL,则在此处进行清理
if (handle != NULL)
{
MV_CC_DestroyHandle(handle);
handle = NULL;
}
// ================== 步骤 10:反初始化 SDK ==================
// 释放 SDK 占用的全局资源
MV_CC_Finalize();
printf("SDK反初始化完成。\n");
printf("程序退出。\n");
return 0; // 程序正常退出
}
makefile为:
Demo: SetParam.cpp
g++ -g -o SetParam SetParam.cpp -I../../../../../include -Wl,-rpath=$(MVCAM_COMMON_RUNENV)/64 -L$(MVCAM_COMMON_RUNENV)/64 -lMvCameraControl
clean:
rm SetParam -rf
SDK初始化成功。
共发现 2 台设备:
[设备 0]:
设备型号名称: MV-CE120-10GM
当前IP地址: 169.254.183.31
用户自定义名称:
[设备 1]:
设备型号名称: MV-CE120-10GM
当前IP地址: 169.254.183.31
用户自定义名称:
请输入要操作的相机序号: 0
设备句柄创建成功。
设备打开成功。
图像高度 - 当前值: 3036
图像高度 - 最大值: 3036
图像高度 - 最小值: 32
图像高度 - 增量: 2
请输入要设置的图像高度: 3036
设置图像高度成功!
曝光时间 - 当前值: 5000.000000
曝光时间 - 最大值: 1999733.000000
曝光时间 - 最小值: 34.000000
请输入要设置的曝光时间: 4000
设置曝光时间成功!
触发模式 - 当前值: 0
支持的触发模式数量: 2
支持的触发模式 [0]: 0
支持的触发模式 [1]: 1
请输入要设置的触发模式: 1
设置触发模式成功!
图像X方向翻转 - 当前值: 关闭
请输入图像X方向翻转状态 (0=关闭, 1=开启): 1
设置图像X方向翻转成功!
当前设备用户ID: []
请输入要设置的设备用户ID (字符串): 111
设置设备用户ID成功!
设备已关闭。
设备句柄已销毁。
SDK反初始化完成。
程序退出。
✅ 适用场景
本示例适用于以下场景:
工业相机二次开发入门学习
快速验证相机通信与参数配置
搭建相机控制模块的基础框架
🔧 使用说明
安装最新版 Hikrobot MVS SDK
将 MvCameraControl.h 和相关库文件加入工程
编译并运行程序
根据提示选择设备并设置参数
📝 结语
通过简洁明了的C语言代码,我们实现了对海康相机的核心控制功能。程序结构清晰,便于扩展为图像采集、多相机管理等功能模块。对于刚接触视觉开发的工程师来说,是一个不错的起点。
代码已将所有输出信息本地化为中文,提升调试体验,便于团队协作与现场部署。
完整源码已附在文中,可直接编译使用。
欢迎交流与优化!