前言:
由于实验需要使用双目相机同步采集图像,实验室准备的设备是海康威视的工业相机,对其进行二次开发,其中花了大部分时间查找资料,以及代码进行反复调试,最后到达了想要的效果,并写博客记录一下。
资料查找:
首先是资料的查找,我们要注意的是海康威视一共有两个官网,一个是海康威视:/cn/ ;另一个是海康威视机器人:/cn ;由于我们的项目使用的是海康威视CH系列工业相机,所以选择海康威视机器人官网。我们可以在官网找到相关的资料,如下图所示:
点击左侧的机器视觉;
点击工业相机,并找到自己使用的相机所对应的型号,里面有参品的相关介绍、参数以及资料。
官方示例:
在服务支持的下载中心里面,有官方的工业相机的客户端MVS,以及一些其他的安装包。
我的开发环境是ubuntu20.04,所以选择下载对应的Linux客户端以及SDK开发包。
将下载的文件及解压即可。
环境配置:
一、MVS安装
SDK有 5 个安装包,分别对应的环境为:x86_64 | i386 | armhf | aarch64 | arm-none,在不同的硬件平台上选择不同的安装包安装。我使用的是Linux系统,安装x86_64的文件就可以了,我选择的。
安装步骤:
安装 MVS 前,需获取系统 root 权限: sudo su 或 su root。
提供两种打包方式的安装包 xxxx.deb 和 xxxx.tar.gz 。安装方式分别如下:
a、安装 xxxx.deb 的安装包:打开 MVS 安装包所在文件夹,使用“ sudo dpkg -i xxxx.deb ”直接安装MVS客户端。
b、安装 xxxx.tar.gz 的安装包:打开 MVS 安装包所在文件夹,使用“ tar –xzvf xxxx.tar.gz ”对安装包进行解压,打开解压后生成的文件夹,运行安装脚本 “ source ./setup.sh “ 安装 MVS。
MVS安装在 opt/MVS 路径下,安装完成后,在 MVS文件夹中一般包含:bin、doc、driver、include、lib、logserver、Samples等文件夹(不同版本可能存在差异)。
运行“/opt/MVS/bin/MVS.sh ”(或者在/opt/MVS/bin 文件夹中运行“ ./MVS.sh ”)测试 MVS
是否安装成功。
二、MVS开发目录介绍
MVS默认安装在 opt/MVS路径下,安装完成后,在MVS文件夹下包含目录结构如下:
bin(执行文件)doc(工业相机SDK相关文档)driver (Gige相机驱动安装)Include(SDK的头文件)Lib(SDK的Lib文件)logserver (日志服务)Samples(示例程序)
MVS客户端界面如下图,由于写博客的时候没有连接相机,看不到图像采集的画面。
这里有一个注意点,我第一次安装的时候是打不开这个客户端的,后面我在下载的SDK文件夹下(/home/wk/下载/机器视觉工业相机SDK V3.2.0版本Runtime组件包(Linux)/MvCamCtrlSDK_Runtime-3.2.0_x86_64_0915/data/opt)运行了/setup.sh就可以用了,具体是什么原因不知道,我觉得应该是相机的一些驱动没有安装的原因。
项目开发
在安装完成之后,我们可以进入到doc文件里面找到工业相机SDK的相关文档。具体操作如下:
1.点击文件,点击最下面的其他位置。
2.点击计算机,然后找到opt文件,然后进入MVS文件,进入doc文件,里面就能找到一个html文件,打开该文件。
3.文件打开后如图所示:
在这里面我们可以看到官方的一些示例教程,在编程引导里面官方给出了采集图像的具体流程,以及代码实现,这个也是后面对相机同步采集编程的一个主要思想。
这里面还需要特别关注的有三个模块。
1.第一个是结构体的定义模块,这里我们只需要在后面看官方的示例程序以及自己进行编程的时候,查看对应的结构体的作用以及结构体所需要的参数。
2.第二个是官方的示例程序模块,里面包含了官方实现相机的各种功能的代码,点开之后也有相应功能的中文注释,这里就不一一解释每个模块的作用了。
3.第三个是错误码模块,里面有每一个错误码对应的含义,这个对我们在代码的调试的时候是非常有帮助的,可以在代码报错的时候快速定位我们的问题所在。
下面是采集图像的代码:
单目采集:
在单目采集里面,首先我是写了两个询问的语句,是否要进行标定图像的采集和连续采集图像。如果进行单目采集,只需要按w就可以保存图像,按q就可以退出,然后按回车就可以关闭相机。在连续采集的时候,按下y图像就会连续保存,按q就额可以退出采集,然后按回车就可以关闭相机。后面的双目采集也是如此。
#include<stdio.h>#include<iostream>#include<string.h>#include<unistd.h>#include<stdlib.h>#include<pthread.h>#include<termio.h> #include<fcntl.h>#include"MvCameraControl.h"#include"time.h"using namespace std;#define CAMERA_NUM 2bool g_bExit = false;int GrabNum = 10;int nRet = MV_OK;void *handle = NULL;bool isCalib = false,isSaveimg = false;char getcalibchar,getsavechar;bool PrintDeviceInfo(MV_CC_DEVICE_INFO *pstMVDevInfo);void PressEnterToExit(void);int kbhit(void);int SaveNum = 0;int SaveImageToFile(void* handle,unsigned char* p_BufAddr,unsigned short n_Width,unsigned short n_Height,enum MvGvspPixelType en_PixelType,unsigned int n_Framelen){int nRet = MV_OK;MV_SAVE_IMAGE_PARAM_EX stSaveFileParam;memset(&stSaveFileParam, 0, sizeof(MV_SAVE_IMAGE_PARAM_EX));stSaveFileParam.enImageType = MV_Image_Bmp;stSaveFileParam.pData = p_BufAddr;stSaveFileParam.nDataLen = n_Framelen;stSaveFileParam.enPixelType = en_PixelType;stSaveFileParam.nWidth = n_Width;stSaveFileParam.nHeight = n_Height;stSaveFileParam.nJpgQuality = 80;stSaveFileParam.nBufferSize = n_Framelen * 3 + 2048;unsigned char *pImage = (unsigned char *)malloc(n_Framelen * 3 + 2048);stSaveFileParam.pImageBuffer = pImage;char chImageNamel[256] = {0};string folderPathl = "../saveimg/0";string commandl;commandl = "mkdir -p " + folderPathl;system(commandl.c_str());if(isSaveimg || isCalib){nRet = MV_CC_SaveImageEx2(handle, &stSaveFileParam);if (nRet != MV_OK){return nRet;}sprintf(chImageNamel,"../saveimg/0/%06d.bmp", SaveNum);FILE *fp1 = fopen(chImageNamel, "wb");fwrite(pImage, 1, stSaveFileParam.nImageLen, fp1);fclose(fp1);return MV_OK;} return MV_OK;}char m_Name[512] = {0};int doGrabImage(void* handle, MV_FRAME_OUT stOutFrame){int nRet = MV_OK;nRet = MV_CC_GetImageBuffer(handle, &stOutFrame, 1000);if (nRet == MV_OK){SaveImageToFile(handle,stOutFrame.pBufAddr,stOutFrame.stFrameInfo.nWidth,stOutFrame.stFrameInfo.nHeight,stOutFrame.stFrameInfo.enPixelType,stOutFrame.stFrameInfo.nFrameLen);nRet = MV_CC_FreeImageBuffer(handle, &stOutFrame);if (nRet != MV_OK){return nRet;}return MV_OK;}return MV_OK;}int main(){cout<<"Do you want to obtain images for calibration?[y/n]"<<endl;cin >> getcalibchar;if(getcalibchar == 'y' || getcalibchar == 'Y'){isCalib = true;}if(!isCalib){cout<<"Do you want to save images continuously?[y/n]"<<endl;cin >> getsavechar;if(getsavechar == 'y' || getsavechar == 'Y'){isSaveimg = true;}else{return 0;}}MV_CC_DEVICE_INFO_LIST stDeviceList;//设备信息列表memset(&stDeviceList, 0, sizeof(MV_CC_DEVICE_INFO_LIST));//初始化内存//枚举设备nRet = MV_CC_EnumDevices(MV_USB_DEVICE, &stDeviceList);//查找USB设备,传入设备列表if(nRet != MV_OK){cout << "MV_CC_EnumDevices fail! nRet [" << nRet << "]" << endl;return -1;}if(stDeviceList.nDeviceNum > 0){for (int i = 0; i < stDeviceList.nDeviceNum;i++){cout << "[device " << i << "]:" << endl;MV_CC_DEVICE_INFO *pDeviceInfo = stDeviceList.pDeviceInfo[i];if(pDeviceInfo == NULL){break;}PrintDeviceInfo(pDeviceInfo);}}else{cout << "Find No Devices!" << endl;return -1;}cout << "Plsase Input camera index:" << endl;unsigned int nIndex = 0;//无符整型,只能取正数,作为相机索引cin >> nIndex;if(nIndex >= stDeviceList.nDeviceNum){cout << "Input error!" << endl;return 0;}//选择设备并创建句柄nRet = MV_CC_CreateHandle(&handle, stDeviceList.pDeviceInfo[nIndex]);if(nRet != MV_OK){cout << "MV_CC_CreateHandle fail! nRet [" << nRet << "]" << endl;MV_CC_DestroyHandle(handle);return -1;}//打开设备nRet = MV_CC_OpenDevice(handle);if(nRet != MV_OK){cout << "MV_CC_OpenDevice fail! nRet [" << nRet << "]" << endl;MV_CC_DestroyHandle(handle);return -1;}// 设置触发模式为on// nRet = MV_CC_SetEnumValue(handle, "TriggerMode", MV_TRIGGER_MODE_ON);// if(nRet != MV_OK)// {//cout << "MV_CC_SetTriggerMode fail! nRet [" << nRet << "]" << endl;//return 0;// }//设置触发模式为offnRet = MV_CC_SetEnumValue(handle, "TriggerMode", MV_TRIGGER_MODE_OFF);if(nRet != MV_OK){cout << "Cam[0]:MV_CCSetTriggerMode fail! nRet [" << nRet << "]" << endl;}//设置触发源// nRet = MV_CC_SetEnumValue(handle, "TriggerSource", MV_TRIGGER_SOURCE_LINE0);// if(nRet != MV_OK)// {//cout << "MV_CC_SetTriggerMode fail! nRet [" << nRet << "]" << endl;//return 0;// }//开始取流nRet = MV_CC_StartGrabbing(handle);if(nRet != MV_OK){cout << "Cam[0]:MV_CC_StartGrabbing fail! nRet [" << nRet << "]" << endl;return -1;}MV_FRAME_OUT stOutFrame = {0};memset(&stOutFrame, 0, sizeof(MV_FRAME_OUT));while(1){if(isCalib && kbhit()){char ch = getchar();cout << "Plase press 'w' or 'W' to save images or press 'q' or 'Q' to quit!"<<endl;if (ch == 'w'||ch == 'W') {nRet = doGrabImage(handle, stOutFrame);if(nRet != MV_OK){cout << "Cam[0]:doGrabImage failed! [" << nRet << "]" << endl;}SaveNum++;}if (ch == 'q'||ch == 'Q')break;}if(isSaveimg){nRet = doGrabImage(handle, stOutFrame);if(nRet != MV_OK){cout << "Cam[0]:doGrabImage failed! [" << nRet << "]" << endl;}SaveNum++;if (kbhit()) {char ch = getchar();cout << "Plase press 'q' or 'Q' to quit!"<<endl;if (ch == 'q'||ch == 'Q')break;}}}cout << "Plase Press Enter to stop grabbing!" << endl;PressEnterToExit();//停止取流nRet = MV_CC_StopGrabbing(handle);if(nRet != MV_OK){cout << "MV_CC_StopGrabbing fail! nRet [" << nRet << "]" << endl;return -1;}// 关闭设备nRet = MV_CC_CloseDevice(handle);if(nRet != MV_OK){cout << "MV_CC_CloseDevice fail! nRet [" << nRet << "]" << endl;return -1;}//销毁句柄nRet = MV_CC_DestroyHandle(handle);if(nRet != MV_OK){cout << "MV_CC_CloseDevice fail! nRet [" << nRet << "]" << endl;return -1;}cout << "exit" << endl;return 0;}bool PrintDeviceInfo(MV_CC_DEVICE_INFO *pstMVDevInfo){if(pstMVDevInfo == NULL){cout << "The Pointer of pstMVDevInfo is NULL!" << endl;return false;}if(pstMVDevInfo->nTLayerType == MV_USB_DEVICE){cout << "Device Model Name:" << pstMVDevInfo->SpecialInfo.stUsb3VInfo.chModelName << endl;cout << "UserDefinedName:" << pstMVDevInfo->SpecialInfo.stUsb3VInfo.chUserDefinedName << endl;}else{cout << "Not support." << endl;}return true;}void PressEnterToExit(void){int c;while((c = getchar()) != '\n' && c != EOF)fprintf(stderr, "Press enter to exit.\n");while(getchar() != '\n')g_bExit = true;sleep(1);}int kbhit(void) {struct termios oldt, newt;int ch;int oldf;tcgetattr(STDIN_FILENO, &oldt);newt = oldt;newt.c_lflag &= ~(ICANON | ECHO);tcsetattr(STDIN_FILENO, TCSANOW, &newt);oldf = fcntl(STDIN_FILENO, F_GETFL, 0);fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);ch = getchar();tcsetattr(STDIN_FILENO, TCSANOW, &oldt);fcntl(STDIN_FILENO, F_SETFL, oldf);if (ch != EOF) {ungetc(ch, stdin);return 1;}return 0;}
双目同步采集:
双目和单目有一点不同,使用双目相机采集的时候我们要求两个相机采集的图像是在同一时刻下进行采集的,对于如何确定两个相机是否是同步采集呢?我使用的方法是利用北京时间校准(直接百度,精确到毫秒),对其进行拍照,看左右相机得到的图片是否是一样的就可以了;这里我使用的触发模式是外部硬件触发的模式,使用相机的Line0作为硬件触发的信号源(Line2使用不了,不知道具体原因),具体的原理可以看相机的使用手册(下载方式在前面也有提到),里面对相机的介绍都是非常完整的。具体实现代码如下:
#include<stdio.h>#include<iostream>#include<string.h>#include<unistd.h>#include<stdlib.h>#include<pthread.h>#include<termio.h> #include<fcntl.h>#include"MvCameraControl.h"#include"time.h"using namespace std;#define CAMERA_NUM 2bool g_bExit = false;int nRet = MV_OK;void *handle[CAMERA_NUM] = {NULL};bool isCalib = false,isSaveimg = false;char getcalibchar,getsavechar;int savenum1=0,savenum2=1;bool PrintDeviceInfo(MV_CC_DEVICE_INFO *pstMVDevInfo);void PressEnterToExit(void);int kbhit(void);int SaveNum = 0;int SaveImageToFile(void* handle,unsigned char* p_BufAddr,unsigned short n_Width,unsigned short n_Height,enum MvGvspPixelType en_PixelType,unsigned int n_Framelen,int n){int nRet = MV_OK;MV_SAVE_IMAGE_PARAM_EX stSaveFileParam;memset(&stSaveFileParam, 0, sizeof(MV_SAVE_IMAGE_PARAM_EX));stSaveFileParam.enImageType = MV_Image_Bmp;stSaveFileParam.pData = p_BufAddr;stSaveFileParam.nDataLen = n_Framelen;stSaveFileParam.enPixelType = en_PixelType;stSaveFileParam.nWidth = n_Width;stSaveFileParam.nHeight = n_Height;stSaveFileParam.nJpgQuality = 80;stSaveFileParam.nBufferSize = n_Framelen * 3 + 2048;unsigned char *pImage = (unsigned char *)malloc(n_Framelen * 3 + 2048);stSaveFileParam.pImageBuffer = pImage;char chImageNamel[256] = {0};char chImageNamer[256] = {0};string folderPathl = "../saveimg/0";string folderPathr = "../saveimg/1";string commandl,commandr;commandl = "mkdir -p " + folderPathl;commandr = "mkdir -p " + folderPathr;system(commandl.c_str());system(commandr.c_str());if(isSaveimg || isCalib){nRet = MV_CC_SaveImageEx2(handle, &stSaveFileParam);if (nRet != MV_OK){return nRet;}sprintf(chImageNamel,"../saveimg/%d/%06d.bmp",n, SaveNum);FILE *fp1 = fopen(chImageNamel, "wb");fwrite(pImage, 1, stSaveFileParam.nImageLen, fp1);fclose(fp1);return MV_OK;} return MV_OK;}char m_Name[512] = {0};int doGrabImage(void* handle, MV_FRAME_OUT stOutFrame,int n){int nRet = MV_OK;nRet = MV_CC_GetImageBuffer(handle, &stOutFrame, 1000);if (nRet == MV_OK){SaveImageToFile(handle,stOutFrame.pBufAddr,stOutFrame.stFrameInfo.nWidth,stOutFrame.stFrameInfo.nHeight,stOutFrame.stFrameInfo.enPixelType,stOutFrame.stFrameInfo.nFrameLen,n);nRet = MV_CC_FreeImageBuffer(handle, &stOutFrame);if (nRet != MV_OK){return nRet;}return MV_OK;}return MV_OK;}int main(){cout<<"Do you want to obtain images for calibration?[y/n]"<<endl;cin >> getcalibchar;if(getcalibchar == 'y' || getcalibchar == 'Y'){isCalib = true;}if(!isCalib){cout<<"Do you want to save images continuously?[y/n]"<<endl;cin >> getsavechar;if(getsavechar == 'y' || getsavechar == 'Y'){isSaveimg = true;}else{return 0;}}MV_CC_DEVICE_INFO_LIST stDeviceList;//设备信息列表memset(&stDeviceList, 0, sizeof(MV_CC_DEVICE_INFO_LIST));//初始化内存//枚举设备nRet = MV_CC_EnumDevices(MV_USB_DEVICE, &stDeviceList);//查找USB设备,传入设备列表if(nRet != MV_OK){cout << "MV_CC_EnumDevices fail! nRet [" << nRet << "]" << endl;return -1;}unsigned int nIndex = 0;//无符整型,只能取正数,作为相机索引if(stDeviceList.nDeviceNum > 0){for (int i = 0; i < stDeviceList.nDeviceNum;i++){cout << "[device " << i << "]:" << endl;MV_CC_DEVICE_INFO *pDeviceInfo = stDeviceList.pDeviceInfo[i];if(pDeviceInfo == NULL){break;}PrintDeviceInfo(pDeviceInfo);} }else{cout << "Find No Devices!" << endl;return -1;}if(stDeviceList.nDeviceNum < CAMERA_NUM){cout << "only have" << stDeviceList.nDeviceNum << "camera" << endl;return -1;}//提示为多相机测试cout << "Start " << CAMERA_NUM << " camera Grabbing Image test" << endl;for (int i = 0; i < CAMERA_NUM;i++){cout << "Please Input Camera Index:";cin >> nIndex;//选择设备并创建句柄nRet = MV_CC_CreateHandle(&handle[i], stDeviceList.pDeviceInfo[nIndex]);if(nRet != MV_OK){cout << "MV_CC_CreateHandle fail! nRet [" << nRet << "]" << endl;MV_CC_DestroyHandle(handle[i]);return -1;}//打开设备nRet = MV_CC_OpenDevice(handle[i]);if(nRet != MV_OK){cout << "MV_CC_OpenDevice fail! nRet [" << nRet << "]" << endl;MV_CC_DestroyHandle(handle[i]);return -1;}}for (int i = 0; i < CAMERA_NUM;i++){// 设置触发模式为onnRet = MV_CC_SetEnumValue(handle[i], "TriggerMode", MV_TRIGGER_MODE_ON);if(nRet != MV_OK){cout << "MV_CC_SetTriggerMode fail! nRet [" << nRet << "]" << endl;break;}//设置触发模式为off// nRet = MV_CC_SetEnumValue(handle[i], "TriggerMode", MV_TRIGGER_MODE_OFF);// if(nRet != MV_OK)// {//cout << "Cam[0]:MV_CCSetTriggerMode fail! nRet [" << nRet << "]" << endl;// }//设置触发源nRet = MV_CC_SetEnumValue(handle[i], "TriggerSource", MV_TRIGGER_SOURCE_LINE0);if(nRet != MV_OK){cout << "MV_CC_SetTriggerMode fail! nRet [" << nRet << "]" << endl;break;}}//开始取流nRet = MV_CC_StartGrabbing(handle[0]);if(nRet != MV_OK){cout << "Cam[0]:MV_CC_StartGrabbing fail! nRet [" << nRet << "]" << endl;return -1;}nRet = MV_CC_StartGrabbing(handle[1]);if(nRet != MV_OK){cout << "Cam[1]:MV_CC_StartGrabbing fail! nRet [" << nRet << "]" << endl;return -1;}MV_FRAME_OUT stOutFrame = {0};memset(&stOutFrame, 0, sizeof(MV_FRAME_OUT));while(1){if(isCalib && kbhit()){char ch = getchar();cout << "Plase press 'w' or 'W' to save images or press 'q' or 'Q' to quit!"<<endl;if (ch == 'w'||ch == 'W') {nRet = doGrabImage(handle[0], stOutFrame,savenum1);if(nRet != MV_OK){cout << "Cam[0]:doGrabImage failed! [" << nRet << "]" << endl;}nRet = doGrabImage(handle[1], stOutFrame,savenum2);if(nRet != MV_OK){cout << "Cam[1]:doGrabImage failed! [" << nRet << "]" << endl;}SaveNum++;}if (ch == 'q'||ch == 'Q')break;}if(isSaveimg){nRet = doGrabImage(handle[0], stOutFrame,savenum1);if(nRet != MV_OK){cout << "Cam[0]:doGrabImage failed! [" << nRet << "]" << endl;}nRet = doGrabImage(handle[1], stOutFrame,savenum2);if(nRet != MV_OK){cout << "Cam[1]:doGrabImage failed! [" << nRet << "]" << endl;}SaveNum++;if (kbhit()) {char ch = getchar();cout << "Plase press 'q' or 'Q' to quit!"<<endl;if (ch == 'q'||ch == 'Q')break;}}}cout << "Plase Press Enter to stop grabbing!" << endl;PressEnterToExit();for (int i = 0; i < CAMERA_NUM;i++){//停止取流nRet = MV_CC_StopGrabbing(handle[i]);if(nRet != MV_OK){cout << "MV_CC_StopGrabbing fail! nRet [" << nRet << "]" << endl;return -1;}// 关闭设备nRet = MV_CC_CloseDevice(handle[i]);if(nRet != MV_OK){cout << "MV_CC_CloseDevice fail! nRet [" << nRet << "]" << endl;return -1;}//销毁句柄nRet = MV_CC_DestroyHandle(handle[i]);if(nRet != MV_OK){cout << "MV_CC_CloseDevice fail! nRet [" << nRet << "]" << endl;return -1;}}cout << "exit" << endl;return 0;}bool PrintDeviceInfo(MV_CC_DEVICE_INFO *pstMVDevInfo){if(pstMVDevInfo == NULL){cout << "The Pointer of pstMVDevInfo is NULL!" << endl;return false;}if(pstMVDevInfo->nTLayerType == MV_USB_DEVICE){cout << "Device Model Name:" << pstMVDevInfo->SpecialInfo.stUsb3VInfo.chModelName << endl;cout << "UserDefinedName:" << pstMVDevInfo->SpecialInfo.stUsb3VInfo.chUserDefinedName << endl;}else{cout << "Not support." << endl;}return true;}void PressEnterToExit(void){int c;while((c = getchar()) != '\n' && c != EOF)fprintf(stderr, "Press enter to exit.\n");while(getchar() != '\n')g_bExit = true;sleep(1);}int kbhit(void) {struct termios oldt, newt;int ch;int oldf;tcgetattr(STDIN_FILENO, &oldt);newt = oldt;newt.c_lflag &= ~(ICANON | ECHO);tcsetattr(STDIN_FILENO, TCSANOW, &newt);oldf = fcntl(STDIN_FILENO, F_GETFL, 0);fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);ch = getchar();tcsetattr(STDIN_FILENO, TCSANOW, &oldt);fcntl(STDIN_FILENO, F_SETFL, oldf);if (ch != EOF) {ungetc(ch, stdin);return 1;}return 0;}