大恒工业相机MER-1070-10GC开发记录
实验室正好有一个大恒型号为MER-1070-10GC的面阵相机,于是便用该相机学习工业相机的开发,写下本篇博客作为开发记录,也当作学习之路的一次小小实践。
本次开发环境为Ubuntu18,开发软件为QT5,配合Opencv进行图像处理。主要完成相机的基本配置、初始化及打开,并且通过回调函数的方法获得图片,显示在UI界面。同时Opencv获取到图片,可以根据自己的要求进行图像处理。
详情见大恒相机官方文档 水星系列GIgE应用说明书
名称 | 参数 |
---|---|
型号 | MER-1070-10GC |
分辨率 | 3840 × \times × 2748 |
像素深度 | 8bit、12bit |
快门时间 | 42 μ \mu μ ~ 1s |
图像数据格式 | Bayer GR8 / Bayer GR12 |
输入输出接口 | 1个光耦输入接口,一个光耦输出接口,2个双向GPIO |
数据接口 | 百兆以太网或千兆以太网 |
光谱相应图
首先前往大恒相机官网下载相应软件开发SDK 大恒相机SDK下载地址。根据需求选择相应的选项,本次开发采用C/C++语言,因此下载第一个选项。
下载完成后在Linux中解压,在opt目录下有Galaxy_Linux-x86_Gige-U3_32bits-64bits_1.2.1911.9122文件夹,运行.run文件进行安装。bin文件夹内有客户端程序GalaxyView和IP配置软件GxGigeIPConfig,还有动态链接库文件,头文件在inc文件夹内,doc文件夹内有C软件开发说明书,里面有详细操作及编程介绍至此,SDK和客户端软件下载完成
相机上电,通过网线连接电脑,首先通过IP配置软件给相机配置IP地址,然后打开GalaxyView测试相机。在该软件中,可以调整相机的曝光、增益、白平衡、ROI区域、触发模式、采集模式等参数,最后可以导出配置文件,为后续开发提供便捷。
C软件开发说明书已经上传到我的资源 大恒相机C软件开发说明书
在QT正式开发前,需要将上述下载的API库加入.pro配置文件,否则是无法在程序中调用相机SDK的,同时将Opencv库加入配置文件。
# camera lib LIBS += -L$$PWD/../../../../../usr/lib/libgxiapi.so -lgxiapi INCLUDEPATH += $$PWD/../../../../../opt/Galaxy_Linux-x86_Gige-U3_32bits-64bits_1.2.1911.9122/Galaxy_camera/inc DEPENDPATH += $$PWD/../../../../../opt/Galaxy_Linux-x86_Gige-U3_32bits-64bits_1.2.1911.9122/Galaxy_camera/inc # opecncv LIBS += -L/usr/lib/x86_64-linux-gnu/ -lopencv_core LIBS += -L/usr/lib/x86_64-linux-gnu/ -lopencv_highgui LIBS += -L/usr/lib/x86_64-linux-gnu/ -lopencv_imgcodecs LIBS += -L/usr/lib/x86_64-linux-gnu/ -lopencv_video LIBS += -L/usr/lib/x86_64-linux-gnu/ -lopencv_videoio LIBS += -L/usr/lib/x86_64-linux-gnu/ -lopencv_shape LIBS += -L/usr/lib/x86_64-linux-gnu/ -lopencv_imgproc INCLUDEPATH += /usr/include/opencv2 DEPENDPATH += /usr/include/opencv2
为了方便相机操作,创建相机类。
class Camera { public: Camera(); uint32_t Enum_Camera(); //枚举相机 bool Open_Camera(); //打开相机 bool Close_Camera(); //关闭相机 bool Init_Camera(); //初始化相机 /*注册回调函数*/ bool Register_Callback_Fun(); /*注销回调函数*/ bool Unregister_Callback_Fun(); GX_DEV_HANDLE cam_handle = nullptr; //相机句柄 };
定义成员函数
#include "camera.h" #include <QDebug> #define DEVICE_INDEX_TO_OPEN "1" extern void GX_STDC OnFrameCallbackFun(GX_FRAME_CALLBACK_PARAM* pFrame); Camera::Camera() { } uint32_t Camera::Enum_Camera() { GX_STATUS status = GX_STATUS_SUCCESS; status = GXInitLib(); //初始化库 uint32_t nDeviceNum = 0; status = GXUpdateDeviceList(&nDeviceNum, 1000); if(status == GX_STATUS_SUCCESS && nDeviceNum > 0) { GX_DEVICE_BASE_INFO *pBaseinfo = new GX_DEVICE_BASE_INFO[nDeviceNum]; size_t nSize = nDeviceNum * sizeof(GX_DEVICE_BASE_INFO); status = GXGetAllDeviceBaseInfo(pBaseinfo, &nSize); if(status == GX_STATUS_SUCCESS) { for(uint32_t i=0; i<nDeviceNum; i++) { qDebug() << "No: " << i+1 << " camera enum successfully!"; qDebug() << "szDeviceID: " << pBaseinfo[i].szDeviceID; qDebug() << "szDisplayName: " << pBaseinfo[i].szDisplayName; } delete []pBaseinfo; return nDeviceNum; } } else qDebug() << "enum error!"; return 1; } bool Camera::Open_Camera() { GX_STATUS status = GX_STATUS_SUCCESS; GX_OPEN_PARAM stOpenParam; stOpenParam.accessMode = GX_ACCESS_EXCLUSIVE; stOpenParam.openMode = GX_OPEN_INDEX; stOpenParam.pszContent = DEVICE_INDEX_TO_OPEN; status = GXOpenDevice(&stOpenParam, &cam_handle); if(status == GX_STATUS_SUCCESS) { qDebug() << ">>>>>open camera success!>>>>>"; return true; } else return false; } bool Camera::Close_Camera() { GX_STATUS status = GX_STATUS_SUCCESS; status = GXUnregisterCaptureCallback(cam_handle); //注销采集回调函数 if(status != GX_STATUS_SUCCESS) qDebug() << "unregister failed!!!" << endl; status =GXCloseDevice(cam_handle); if(status == GX_STATUS_SUCCESS) { qDebug() << ">>>>>close camera success>>>>>"; GXCloseLib(); //注销库 return true; } else return false; } bool Camera::Init_Camera() { uint32_t nDeviceNum = Enum_Camera(); if(nDeviceNum == 0) { qDebug() << ">>>>>init failed!>>>>>"; return false; } if(Open_Camera()) { qDebug() << ">>>>>init success!>>>>>"; return true; } qDebug() << ">>>>>init failed!>>>>>"; return false; } bool Camera::Register_Callback_Fun() { GX_STATUS status = GX_STATUS_SUCCESS; status = GXRegisterCaptureCallback(cam_handle, nullptr, OnFrameCallbackFun); if(status == GX_STATUS_SUCCESS) return true; else return false; } bool Camera::Unregister_Callback_Fun() { GX_STATUS status = GX_STATUS_SUCCESS; status = GXUnregisterCaptureCallback(cam_handle); if(status == GX_STATUS_SUCCESS) return true; else return false; }
在初始化相机的时候可以设置相机采集图像的大小
GXSetInt(my_cam->cam_handle, GX_INT_WIDTH, 1024); GXSetInt(my_cam->cam_handle, GX_INT_HEIGHT, 768);
设置相机采集模式及触发模式,这里设置为连续采集且无触发,一旦发送开采命令,相机将连续采图,直到发送停采命令。
GX_STATUS status = GX_STATUS_SUCCESS; status = GXSetEnum(my_cam->cam_handle, GX_ENUM_ACQUISITION_MODE,GX_ACQ_MODE_CONTINUOUS); if(GX_STATUS_SUCCESS != status) qDebug() << "set acquisition mode failed..." << status << endl; status = GXSetEnum(my_cam->cam_handle, GX_ENUM_TRIGGER_MODE, GX_TRIGGER_MODE_OFF); if(GX_STATUS_SUCCESS != status) qDebug() << "set trigger mode failed...." << status << endl;
至此相机参数配置完成,初始化完成。
相机触发后进入回调函数,用户可以在这里完成不同需求。本例展示在回调函数中接收相机内存的BAYER图,通过DxRaw8toRGB24
函数转换成RGB图,再转换到opencv::Mat类型的图像数据,在工作线程中通过cv::imshow显示图像。由于是连续触发,所以图片帧连续进入回调函数,我们在界面所看到的其实是视频流的形式。
因为回调函数频繁地进入,所以我将显示图片过程放在下面2.3节的工作线程中,防止在回调函数中imshow函数调用花费大量时间导致图片帧卡死。回调函数里只做相机内存和计算机内存间图像的拷贝工作。
#include "thread.h" #include <QDebug> #include "widget.h" int64_t nPayLoadSize; unsigned char *pRGB24Buf; unsigned char *pRawBuf; cv::Mat img_buff; cv::Mat img; int count = 0; /*图像回调处理函数*/ void GX_STDC OnFrameCallbackFun(GX_FRAME_CALLBACK_PARAM* pFrame) { if(pFrame->status == GX_FRAME_STATUS_SUCCESS) { qDebug() << ">>>>>entered the callback fun!>>>>>"; /*打印图片长宽信息*/ // qDebug() << "width: " << pFrame->nWidth << "height" << pFrame->nHeight; memcpy(pRawBuf, pFrame->pImgBuf, pFrame->nImgSize); DxRaw8toRGB24(pRawBuf, pRGB24Buf, pFrame->nWidth,pFrame->nHeight, RAW2RGB_NEIGHBOUR,BAYERRG, false); memcpy(img.data, pRGB24Buf, 1024*768*3); //img为cv::Mat格式 qDebug() << "frame: " << count++; //显示已拍摄的帧数 } return; }
全局变量 *pRGB24Buf
和 *pRawBuf
的初始化见2.3节工作线程中。
建立thread.h头文件,创建工作线程类,以及定义回调函数(回调函数中也是开启一个单独的线程进行处理,故这里将它算在线程文件中)
#include "thread.h" #include <QDebug> #include "widget.h" int64_t nPayLoadSize; unsigned char *pRGB24Buf; unsigned char *pRawBuf; cv::Mat img_buff; cv::Mat img; int count = 0; Start_Work::Start_Work(QObject *parent) : QThread (parent) { my_cam = new Camera(); } Start_Work::~Start_Work() { if(my_cam != nullptr) delete my_cam; } void Start_Work::run() { bool IsOk = false; IsOk = my_cam->Init_Camera(); if(!IsOk) qDebug() << "init camera failed" << endl; GX_STATUS status = GX_STATUS_SUCCESS; GXSetInt(my_cam->cam_handle, GX_INT_WIDTH, 1024); GXSetInt(my_cam->cam_handle, GX_INT_HEIGHT, 768); GXGetInt(my_cam->cam_handle, GX_INT_PAYLOAD_SIZE, &nPayLoadSize); pRawBuf = new unsigned char[nPayLoadSize]; pRGB24Buf = new unsigned char[1024*768*3]; img.create(768, 1024, CV_8UC3); //创建cv::Mat IsOk = my_cam->Register_Callback_Fun(); if(!IsOk) qDebug() << "register failed!" << endl; /*发送采集命令*/ status = GXSendCommand(my_cam->cam_handle, GX_COMMAND_ACQUISITION_START); if(GX_STATUS_SUCCESS != status) qDebug() << "send command error..." << endl; qDebug() << "start_work thread going...." << endl; /*连续显示300帧图片,然后退出循环,发送停采命令*/ while(1) { cv::imshow("test", img); if(count == 300) break; } status = GXSendCommand(my_cam->cam_handle, GX_COMMAND_ACQUISITION_STOP); if(status == GX_STATUS_SUCCESS) { IsOk = my_cam->Close_Camera(); if(!IsOk) qDebug() << "close camera error!!!!" << endl; else qDebug() << "close camera success" << endl; } /*释放内存!*/ if(pRawBuf != nullptr) { delete [] pRawBuf; pRawBuf = nullptr; } if(pRGB24Buf != nullptr) { delete [] pRGB24Buf; pRGB24Buf = nullptr; } }
进入300次回调函数,显示300帧图片,测试成功。
此时cv::Mat格式的图片也采集到,可以结合要求进行处理
使用QT开发时肯定不希望用opencv的imshow来显示,将图片显示在UI界面才是正确的。接下来就来完成这件事。首先在widget.ui放置一个QLabel,然后在widget.h和widget.cpp中申明定义slot槽函数:showimage(cv::Mat)
,参数是cv::Mat变量。这个函数负责在收到emit信号后在QLabel对象pic_show上显示图片。
void Widget::showimage(cv::Mat img) { cv::cvtColor(img, img, CV_BGRA2RGB); const unsigned char *pSrc = (const unsigned char*)img.data; QImage image(pSrc, img.cols, img.rows, img.step, QImage::Format_RGB888); pix = QPixmap::fromImage(image.scaled(ui->pic_show->width(),ui->pic_show->height(),Qt::KeepAspectRatio)); ui->pic_show->setPixmap(pix); ui->pic_show->show(); }
在thread.h的Start_Work类中申明信号send_image(cv::Mat)
signals: void send_image(cv::Mat);
同时在widget类的构造函数中绑定信号和槽
connect(start_thread, SIGNAL(send_image(cv::Mat)), this, SLOT(showimage(cv::Mat)),Qt::BlockingQueuedConnection);
最后一步替换2.3节中Start_Work::run()
中while(1)中的内容,在循环中emit发送信号即可
while(1) emit send_image(img);
编译运行,调试结果如下
以上就是关于大恒相机的C语言SDK下的初步开发流程,我们完成了采集图片,抓取图片,转换为Opencv::Mat格式,显示图片的功能。Mat格式的图片方便利用Opencv对图像进行后续处理。
除此之外,在后续开发过程中,要注意多线程开发中对全局图片变量的加锁处理,以防止多个线程对资源的争夺,可以采用信号量的方法建立图片缓存区,保证线程之间不冲突。