大恒工业相机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 大恒相机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软件开发说明书
1 QT配置文件
在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
2 相机初始化
2.1 创建相机类
为了方便相机操作,创建相机类。
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;
至此相机参数配置完成,初始化完成。
2.2 相机采集回调函数
相机触发后进入回调函数,用户可以在这里完成不同需求。本例展示在回调函数中接收相机内存的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节工作线程中。
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格式的图片也采集到,可以结合要求进行处理
2.4 QLabel显示图片
使用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对图像进行后续处理。
除此之外,在后续开发过程中,要注意多线程开发中对全局图片变量的加锁处理,以防止多个线程对资源的争夺,可以采用信号量的方法建立图片缓存区,保证线程之间不冲突。