效果展示

       Dalsa线扫描相机的二次开发,因为官方只有MFC和命令行版本的,我需要使用QT进行开发,于是自己花时间研究,然后写了一个,效果如下:

        可能GIF动图有点模糊,在图片中,上面为实时画面,我使用的是4096*128分辨率,然后使用手机的闪光灯在相机旁边摇晃,加上没有调焦调距,效果确实是如此。下面是实时拼接的图,将结果缩小旋转90度,然后依次拼接起来,就是下面拼接图片的效果。

        拼接图的右边那个黑框是截图时候参数没有设置好,后面已经改好了。

经验汇总

1. 大家参考的时候,记得对应自己的相机参数,包括:品牌、灰度/彩色、相机实际分辨率、网口/采集卡相机、软件版本等信息。

2. 我的版本是:

        相机:HL-FM 采集卡版本灰度相机

        SDK版本为:SaperaLTSDKSetup_8.60、

        采集卡驱动为:xtium2-clhs_fx8lc_110010122、

        QT版本:5.12

        编译器:MSVC 2017 64bit

        当然,实际只要版本差不多就行,我后面会提供我这个版本的软件驱动。

3. 我自己开发的步骤为:

安装相机驱动和SDK 打开官方的 “Sapera CamExpert” 软件根据压缩包里面的 “HL-FM相机使用说明” 文件,进行相关配置,记得分辨率要调为自己的分辨率,不要完全按照文档来。点击 grab,拿着灯光在相机前晃,如果可以显示画面就正常。如上所示,就表示正常,因为我是灰度相机,就选择第一个了。点击这个软件的左上角 “file”  “save  as ” ,然后保存,如下图所示:上面的路径要记住,如果记不住,就点击 “Select Custom Directory”,自己保存在其他位置。这个很重要!!! 因为程序会使用这个配置文件,你必须记住路径,或者放在容易找到的路径才行!!!关闭软件(一定要关闭,因为会占用端口),打开 “ C:\Program Files\Teledyne DALSA\Sapera” ,这个是官方软件路径。会发现这么多文件夹。下面是解释:如果是MFC开发者,那爽了,直接参考和复制Demos文件夹的代码即可。如果是QT的开发者,一般来说,开发起来就很有难度了,因为里面有很多很多的坑,至少我之前在其他地方下载的代码还没跑起来过,要么就需要配置opencv,那样太麻烦了。(想偷懒的,下面的步骤可以不看,直接去看我的代码即可)我自己首先是参考了'Examples'文件夹的 'Class' 的 'GrabConsole' 示例,记得需要使用 VS2017和2017之前的版本,之后的版本很容易出问题。在官方软件路径的CamFiles文件夹里面新建User,将刚刚保存的配置文件放进去。看一下 'Examples'文件夹的“Binaries”文件夹里面的 “GrabCPP.exe”,运行一下,看一下官方案例的效果。需要依次输入1、 1 、1,就会弹出窗口,实时显示画面。我最开始运行官方的Demo,发现跑不起来,重装了一次就可以了。然后移植到QT,发现一样的代码,就是跑不起来,经过多次修改,测试需要MSVC 2017编译器。这里面坑很多,想尝试的可以自己去踩一遍这些坑,但是呢,我把很多需要的坑已经踩完了,大家也可以直接参考后面的代码即可。

4. 创建QT测试项目

注意:这里的Demo是在main文件里面的,只是一个测试使用的。提供大家以后测试参考想偷懒的,可以跳过这一步,去看下面已经初步封装好的代码。将官方软件路径里面的这三个文件夹:Classes、Include、Lib文件夹,直接复制拖进项目文件夹里面来,然后配置好pro路径。pro文件代码参考:就是加入main.cpp和那三个文件夹,其他的测试过程可不加。 QT += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

CONFIG += c++11

# The following define makes your compiler emit warnings if you use

# any Qt feature that has been marked deprecated (the exact warnings

# depend on your compiler). Please consult the documentation of the

# deprecated API in order to know how to port your code away from it.

DEFINES += QT_DEPRECATED_WARNINGS

#DEFINES += SAPERA_DOT_NET

DEFINES += COR_WIN64

#QMAKE_CXXFLAGS += -fno-case-insensitive

QMAKE_CXXFLAGS += -fno-code-hoisting

# You can also make your code fail to compile if it uses deprecated APIs.

# In order to do so, uncomment the following line.

# You can also select to disable deprecated APIs only up to a certain version of Qt.

#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0

SOURCES += \

main.cpp

# Default rules for deployment.

qnx: target.path = /tmp/$${TARGET}/bin

else: unix:!android: target.path = /opt/$${TARGET}/bin

!isEmpty(target.path): INSTALLS += target

win32: LIBS += -L$$PWD/Lib/Win64/ -lSapClassBasic

win32: LIBS += -L$$PWD/Lib/Win64/ -lcorapi

INCLUDEPATH += $$PWD/Include

DEPENDPATH += $$PWD/Include

INCLUDEPATH += $$PWD/Classes/Basic

DEPENDPATH += $$PWD/Classes/Basic

测试Demo源码:

#include

#include"SapClassBasic.h"

#include

#include

#include

#include

#include

//#include

#include

#include

#include

using namespace std;

SapManager* m_pManager;

SapAcquisition* m_Acquisition;

SapBufferWithTrash* m_Buffers;

SapTransfer* m_Xfer;

SapView* m_View;

SapAcqDevice *m_AcqDevice;

static int framcount = 0;

BYTE *pData;

static void XferCallBack(SapXferCallbackInfo* pInfo)

{

m_View->Show();

}

bool initDevice(char* m_serverName, const char*ccfpath)

{

printf("Sapera Console Grab Example (C++ version)\n");

SapLocation loc(m_serverName, 0);

if (SapManager::GetResourceCount(m_serverName, SapManager::ResourceAcq) > 0)

{

m_Acquisition = new SapAcquisition(loc, ccfpath);

m_Buffers = new SapBufferWithTrash(2, m_Acquisition);

m_View = new SapView(m_Buffers, SapHwndAutomatic);

m_Xfer = new SapAcqToBuf(m_Acquisition, m_Buffers, XferCallBack, m_View);

}

if (m_Acquisition && !*m_Acquisition && !m_Acquisition->Create()) return FALSE;

if (m_Buffers && !*m_Buffers)

{

if (!m_Buffers->Create())

{

return FALSE;

}

m_Buffers->Clear();

}

if (m_View && !*m_View && !m_View->Create())

{

return FALSE;

}

// Set next empty with trash cycle mode for transfer

if (m_Xfer && m_Xfer->GetPair(0))

{

if (!m_Xfer->GetPair(0)->SetCycleMode(SapXferPair::CycleNextWithTrash))

{

return FALSE;

}

}

// Create transfer object

if (m_Xfer && !*m_Xfer && !m_Xfer->Create())

{

return FALSE;

}

return true;

}

int main(int argc, char** argv)

{

//QApplication a(argc, argv);

char* m_SerName = new char[MAX_PATH];//采集卡名称

char* configFilename = new char[MAX_PATH];

SapBuffer sapBuffer;

int flag = 0;

string ccf_path = "C:\\Program Files\\Teledyne DALSA\\Sapera\\CamFiles\\User\\FrameGrabber.ccf";

uchar *imgData = new uchar[150 * 100/1.75*7000];

m_pManager->GetServerName(0, SapManager::ResourceAcq, m_SerName);

//初始化设备

if (initDevice(m_SerName, ccf_path.c_str()))

{

cout << "Open " << m_SerName << " Success!" << endl;

}

else

{

printf("m_SerName: %s \n",m_SerName);

printf("configFilename: %s \n",configFilename);

cout << "Open " << m_SerName << " Failed!" << endl;

return 0;

}

//开始采集

if (!m_Xfer->IsGrabbing())

{

m_Xfer->Grab();

flag = 1;

m_Buffers->GetAddress((void**)&pData);

//int width = m_Buffers->GetWidth();

//int height = m_Buffers->GetHeight();

}

while (true)

{

if (flag == 1)

{

std::cout << "Grab" << std::endl;

//Sleep(200);

std::stringstream ss;

ss << "D:\\test\\bmp\\" << framcount << ".bmp";

std::string name = ss.str();

const char* savename = name.c_str();

QImage image(4096 , 128, QImage::Format_Grayscale8);

m_Buffers->Clear();

if (!m_Xfer->IsGrabbing())

m_Xfer->Grab();

// 保存文件:

// m_Buffers->Save(savename, "-format bmp");

//m_Mats.push_back(m_Mat);

framcount++;

if (framcount > int(1000))

{

framcount = 0;

cout << "Grab Finished" << endl;//停止采集

m_Xfer->Freeze();

break;

}

}

}

delete imgData;

// Destroy transfer object

if (m_Xfer && *m_Xfer) m_Xfer->Destroy();

if (m_View && *m_View) m_View->Destroy();

if (m_Buffers && *m_Buffers) m_Buffers->Destroy();

if (m_Acquisition && *m_Acquisition) m_Acquisition->Destroy();

//Delete all pointer

if (m_View) delete m_View;

if (m_Buffers) delete m_Buffers;

if (m_Xfer) delete m_Xfer;

if (m_Acquisition) delete m_Acquisition;

//return a.exec();

return 0;

} 其中 ccf_path  路径需要配置为自己刚刚保存的路径,然后路径中需要使用两个\ 来分割。m_Buffers->Save(savename, "-format bmp"); 这行代码我注释起来了,因为会保存文件,想看保存文件的,可以打开注释,路径在D盘的test/bmp路径下,记得别跑太长时间,因为几秒钟保存的图片文件就有好几个G那么大了。 如果可以跑,大家可以使用这个代码来改写为自己的类。下面是我自己初步封装的QT的线程类,大家也可以修改。

5. 我自己封装的线程类:

        1. 新建 SapCameraDev 的类,继承自 Qthread,头文件源码如下:

#ifndef SAPCAMERADEV_H

#define SAPCAMERADEV_H

#include "SapClassBasic.h"

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

using namespace std;

class SapCameraDev : public QThread

{

Q_OBJECT

public:

explicit SapCameraDev(QObject *parent = nullptr);

~SapCameraDev();

void setMax(int v);

static void XferCallBack(SapXferCallbackInfo* pInfo);

bool initDevice(char* m_serverName, const char*ccfpath);

void setStopFlag(bool flag);

void setSaveFlag(bool flag);

void setFreezeFlag(bool flag);

void setCcf_path(QString s);

BYTE *getpData();

// static SapCameraDev* instance;

// static SapCameraDev* getSapCameraDevInstance()

// {

// if (instance == nullptr)

// {

// instance = new SapCameraDev();

// }

// return instance;

// }

signals:

// void getNewImage(QImage image); // 接受到新信号

void getNewImage(BYTE *pData);

void getNewImage_Image(QImage );

void imageOK(bool flag);

private:

void run() override;

private:

string saveImagePath = string("D:\\test\\bmp\\");

string ccf_path = string("C:\\Program Files\\Teledyne DALSA\\Sapera\\CamFiles\\User\\FrameGrabber.ccf");

int max = 1000;

bool isStop = false;

bool isSave = false;

bool isFreeze = false; // 停止

SapManager* m_pManager;

SapAcquisition* m_Acquisition;

SapBufferWithTrash* m_Buffers;

SapTransfer* m_Xfer;

SapView* m_View = nullptr;

SapAcqDevice *m_AcqDevice;

int framcount = 0;

BYTE *pData;

uchar *imgData = new uchar[150 * 100/1.75*7000];

};

#endif // SAPCAMERADEV_H

        2. SapCameraDev 的cpp文件源码如下:

#include "sapCameraDev.h"

//SapCameraDev* SapCameraDev::instance = nullptr;

//BYTE *temp_pData;

void SapCameraDev::XferCallBack(SapXferCallbackInfo* pInfo)

{

// printf("XferCallBack\n");

// SapCameraDev* temp = getSapCameraDevInstance();

// if(temp){

// QImage image(4096 , 128, QImage::Format_Grayscale8);

// memcpy(image.bits(), temp_pData, 4096 * 128);

// emit temp->getNewImage_Image(image);

// }

}

bool SapCameraDev::initDevice(char* m_serverName, const char*ccfpath)

{

printf("Sapera Console Grab Example (C++ version)\n");

SapLocation loc(m_serverName, 0);

if (SapManager::GetResourceCount(m_serverName, SapManager::ResourceAcq) > 0)

{

m_Acquisition = new SapAcquisition(loc, ccfpath);

m_Buffers = new SapBufferWithTrash(2, m_Acquisition);

// m_View = new SapView((SapBuffer*)m_Buffers, SapHwndAutomatic);

m_Xfer = new SapAcqToBuf(m_Acquisition, (SapBuffer*)m_Buffers, SapCameraDev::XferCallBack);

}

if (m_Acquisition && !*m_Acquisition && !m_Acquisition->Create()) return FALSE;

if (m_Buffers)

{

if (!m_Buffers->Create())

{

return FALSE;

}

m_Buffers->Clear();

}

if (m_Xfer && m_Xfer->GetPair(0))

{

if (!m_Xfer->GetPair(0)->SetCycleMode(SapXferPair::CycleNextWithTrash))

{

return FALSE;

}

}

// Create transfer object

if (m_Xfer && !*m_Xfer && !m_Xfer->Create())

{

return FALSE;

}

return true;

}

SapCameraDev::SapCameraDev(QObject *parent)

:QThread(parent)

{

}

void SapCameraDev::setStopFlag(bool flag)

{

isStop = flag;

}

void SapCameraDev::setSaveFlag(bool flag)

{

isSave = flag;

}

void SapCameraDev::setFreezeFlag(bool flag)

{

isFreeze = flag;

}

void SapCameraDev::setCcf_path(QString s)

{

ccf_path = s.toStdString();

}

BYTE * SapCameraDev::getpData()

{

return pData;

}

SapCameraDev::~SapCameraDev()

{

delete[] imgData;

// Destroy transfer object

if (m_Xfer && *m_Xfer) m_Xfer->Destroy();

if (m_View && *m_View) m_View->Destroy();

if (m_Buffers ) m_Buffers->Destroy();

if (m_Acquisition && *m_Acquisition) m_Acquisition->Destroy();

//Delete all pointer

if (m_View) delete m_View;

if (m_Buffers) delete m_Buffers;

if (m_Xfer) delete m_Xfer;

if (m_Acquisition) delete m_Acquisition;

}

void SapCameraDev::setMax(int v)

{

max = v;

}

void SapCameraDev::run()

{

char* m_SerName = new char[MAX_PATH];//采集卡名称

char* configFilename = new char[MAX_PATH];

int flag = 0;

//vector m_Mats;

// cv::Mat m_Mat_all = cv::Mat::zeros(cv::Size(700,85700), CV_8U);

m_pManager->GetServerName(0, SapManager::ResourceAcq, m_SerName);

//初始化设备

if (initDevice(m_SerName, ccf_path.c_str()))

{

cout << "Open " << m_SerName << " Success!" << endl;

}

else

{

printf("m_SerName: %s \n",m_SerName);

printf("configFilename: %s \n",ccf_path.c_str());

cout << "Open " << m_SerName << " Failed!" << endl;

return ;

}

//开始采集

if (!m_Xfer->IsGrabbing())

{

m_Xfer->Grab();

flag = 1;

m_Buffers->GetAddress((void**)&pData);

//temp_pData = pData;

//int width = m_Buffers->GetWidth();

//int height = m_Buffers->GetHeight();

}

std::cout << "Grab" << std::endl;

while (true)

{

if (flag == 1)

{

//std::cout << "Grab" << std::endl;

//Sleep(1);

std::stringstream ss;

ss << saveImagePath << framcount << ".bmp";

std::string name = ss.str();

const char* savename = name.c_str();

QImage image(4096 , 128, QImage::Format_Grayscale8);

if (isFreeze)

{

m_Xfer->Freeze();

emit imageOK(false);

}else{

//m_Buffers->Clear();

m_Xfer->Grab();

// 传递QT图片信号

// memcpy(image.bits(), pData, 4096 * 128);

// emit getNewImage_Image(image);

emit getNewImage(pData);

QThread::usleep(1000);

}

if(isSave){

m_Buffers->Save(savename, "-format bmp");

framcount++;

}

if(isStop){

framcount = 0;

cout << "Grab Finished" << endl;//停止采集

m_Xfer->Freeze();

break;

}

if (framcount > max)

{

framcount = 0;

isSave = false;

cout << "save Finished" << endl;//停止采集

//m_Xfer->Freeze();

//break;

}

}

}

}

        3. 为了更好的展示,我自己新建了一个界面:

        4. mainwindow.h 源码:

#ifndef MAINWINDOW_H

#define MAINWINDOW_H

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include "sapCameraDev.h"

namespace Ui {

class MainWindow;

}

class MainWindow : public QMainWindow

{

Q_OBJECT

public:

explicit MainWindow(QWidget *parent = nullptr);

~MainWindow();

private:

Ui::MainWindow *ui;

SapCameraDev *sap;

QImage *sapImage;

QImage *resultImage;

//QImage *middleImage;

QPixmap *pixmap;

QTimer *showTimer;

QPainter *painter;

int offset_x = 0;

bool isImageOK = false;

bool isSplic = false;

};

#endif // MAINWINDOW_H

        5. mainwindow.cpp源码:

#include "mainwindow.h"

#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :

QMainWindow(parent),

ui(new Ui::MainWindow)

{

ui->setupUi(this);

sap = new SapCameraDev(this);

// 绑定按钮的信号

connect(ui->pushButton_start,&QPushButton::clicked,this,[=](){

sap->setStopFlag(false);

sap->setFreezeFlag(false);

sap->start();

});

connect(ui->pushButton_freeze,&QPushButton::clicked,this,[=](){ //

sap->setFreezeFlag(true);

});

connect(ui->pushButton_stop,&QPushButton::clicked,this,[=](){

sap->setStopFlag(true);

});

connect(ui->pushButton_continue,&QPushButton::clicked,this,[=](){

sap->setFreezeFlag(false);

});

connect(ui->checkBox_saveFile,&QCheckBox::stateChanged,this,[=](int flag){

sap->setSaveFlag(flag);

});

connect(ui->checkBox_splic,&QCheckBox::stateChanged,this,[=](int flag){

isSplic = flag;

resultImage->fill(Qt::transparent);

});

// connect(sap,&SapCameraDev::imageOK,this,[=](bool flag){

// isImageOK = flag;

// offset_x = 0;

// });

// 实时图像:

sapImage = new QImage(4096, 128, QImage::Format_Grayscale8);

// 拼接图像:

resultImage = new QImage(1024,256,QImage::Format_Grayscale8);

resultImage->fill(Qt::transparent); // 透明色

painter = new QPainter(resultImage);

ui->label->setScaledContents(true); // 允许自动缩放

ui->label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // 水平方向上自动伸展,垂直方向上保持固定高度

ui->label_2->setScaledContents(true); // 允许自动缩放

ui->label_2->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // 水平方向上自动伸展,垂直方向上保持固定高度

ui->label_2->setPixmap(QPixmap::fromImage(*resultImage));

//ui->label->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); // 忽略大小策略

// 接收线程类发送来的信号进行图片渲染:

connect(sap,&SapCameraDev::getNewImage,this,[=](BYTE *pData){

// 显示实时画面:

memcpy(sapImage->bits(), pData, 4096 * 128);

ui->label->setPixmap(QPixmap::fromImage(*sapImage));

// 是否拼接:

if(isSplic){

// 显示拼接画面:

// 旋转:

QImage rotatedImage = sapImage->scaled(256, 8, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);

rotatedImage = rotatedImage.transformed(QTransform().rotate(90));

if(offset_x<1024){

painter->drawImage(offset_x,0,rotatedImage);

offset_x += rotatedImage.width();

ui->label_2->setPixmap(QPixmap::fromImage(*resultImage));

}else{

offset_x = 0;

}

}

});

}

MainWindow::~MainWindow()

{

delete ui;

sap->quit();

}

        OK,撒花完结!!!

        其实源码95%都已经给你们放出来了,有能力的自己已经可以做出来了,毕竟当时踩了很多坑,而且当时下载别人的案例,也花了一点小钱,而且他们的还运行不起来,我把我自己的源码放出来,收回一点成本可以吧。不求大富大贵,最起码回本啊,兄弟们,还望理解。

        

        额,不知道为什么,必须设置为免费的,还是0积分,晕死,我先看看如何设置再分享出来。

相关资源链接:

【免费】Dalsa线扫描相机资源分享(一)-安装驱动和配置说明资源-CSDN文库

【免费】Dalsa线扫描相机资源分享(二)-开发文档资源-CSDN文库

【免费】Dalsa线扫描相机资源分享(三)-简单的QT测试程序,未封装类资源-CSDN文库

参考文章:

Dalsa线扫相机SDK二次开发_dalsa相机二次开发-CSDN博客

参考链接

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。