大恒MER-1070-10GC相机 LINUX环境 QT开发记录

大恒工业相机MER-1070-10GC开发记录

目录

前言

实验室正好有一个大恒型号为MER-1070-10GC的面阵相机,于是便用该相机学习工业相机的开发,写下本篇博客作为开发记录,也当作学习之路的一次小小实践。
本次开发环境为Ubuntu18,开发软件为QT5,配合Opencv进行图像处理。主要完成相机的基本配置、初始化及打开,并且通过回调函数的方法获得图片,显示在UI界面。同时Opencv获取到图片,可以根据自己的要求进行图像处理。
大恒MER-1070-10GC相机 LINUX环境 QT开发记录

相机参数介绍

详情见大恒相机官方文档 水星系列GIgE应用说明书

名称 参数
型号 MER-1070-10GC
分辨率 3840 × \times × 2748
像素深度 8bit、12bit
快门时间 42 μ \mu μ ~ 1s
图像数据格式 Bayer GR8 / Bayer GR12
输入输出接口 1个光耦输入接口,一个光耦输出接口,2个双向GPIO
数据接口 百兆以太网或千兆以太网

光谱相应图

大恒MER-1070-10GC相机 LINUX环境 QT开发记录

前期准备

SDK下载

首先前往大恒相机官网下载相应软件开发SDK 大恒相机SDK下载地址。根据需求选择相应的选项,本次开发采用C/C++语言,因此下载第一个选项。
大恒MER-1070-10GC相机 LINUX环境 QT开发记录下载完成后在Linux中解压,在opt目录下有Galaxy_Linux-x86_Gige-U3_32bits-64bits_1.2.1911.9122文件夹,运行.run文件进行安装。bin文件夹内有客户端程序GalaxyView和IP配置软件GxGigeIPConfig,还有动态链接库文件头文件在inc文件夹内,doc文件夹内有C软件开发说明书,里面有详细操作及编程介绍至此,SDK和客户端软件下载完成
大恒MER-1070-10GC相机 LINUX环境 QT开发记录

客户端软件调试

相机上电,通过网线连接电脑,首先通过IP配置软件给相机配置IP地址,然后打开GalaxyView测试相机。在该软件中,可以调整相机的曝光、增益、白平衡、ROI区域、触发模式、采集模式等参数,最后可以导出配置文件,为后续开发提供便捷。
大恒MER-1070-10GC相机 LINUX环境 QT开发记录

开发流程

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帧图片,测试成功。
大恒MER-1070-10GC相机 LINUX环境 QT开发记录
此时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);

编译运行,调试结果如下
大恒MER-1070-10GC相机 LINUX环境 QT开发记录

总结

以上就是关于大恒相机的C语言SDK下的初步开发流程,我们完成了采集图片,抓取图片,转换为Opencv::Mat格式,显示图片的功能。Mat格式的图片方便利用Opencv对图像进行后续处理。
除此之外,在后续开发过程中,要注意多线程开发中对全局图片变量的加锁处理,以防止多个线程对资源的争夺,可以采用信号量的方法建立图片缓存区,保证线程之间不冲突。

上一篇:【渝粤题库】国家开放大学2021春1070组织行为学题目


下一篇:PAT甲级 1070(C++)