简介:面向刚接触 Dalsa 工业相机与 Sapera LT 的开发者,这份中文文档围绕 Sapera++ 图像处理库的初级开发流程展开,适合需要快速建立采集程序框架、理解基础类职责与工程配置方法的读者。压缩包内仅有 1 个 doc 文件,大小约 102KB,篇幅精炼,便于离线查阅与随项目备查。内容从基础应用类与 GUI 类的划分讲起,说明 SapAcquisition、SapBuffer、SapTransfer、SapView 等常用类的用途,并给出加载动态链接库、初始化采集、操作 buffer、释放资源等步骤,还配有类图与示例思路。已有 1431 人学习,可作为 Sapera++ 入门阶段的速查手册,帮助读者少走环境配置与采集流程的弯路,并为后续查阅帮助文档和 Demo 打下基础。
1. 从一块跑不起来的采集卡说起:Dalsa Sapera 初级开发第一步该装什么
第一次接触 Dalsa Sapera 的人,很多都卡在同一个地方:驱动装好了,相机在采集软件里能看到实时图,可自己新建的 VC 工程一编译,屏幕上全是 LNK2019 链接错误。我见过最典型的一次,是有人只装了运行库版本,硬盘上根本没有 Demos 和 Help 目录,照着中文的开发步骤文档抄了三天,连 GrabDemo 长什么样都没见过。Sapera LT 这套工具并不难在 API 多复杂,难在安装选项、库文件路径、运行时库这三处,只要有一处对不上,编译器给出的提示基本没有参考价值。下面按初级开发步骤走一遍:装哪个包、路径怎么加、五个类按什么顺序初始化、报错怎么定位,适合刚拿到采集卡、想先出第一张图的人。
2. 安装 Sapera LT:Full 安装选项与 C:\DALSA 目录结构
装 Sapera 这件事,选错安装包比装错版本更麻烦。运行库包只给你跑别人编好的程序所需的东西,开发要用的头文件、导入库、Demo 源码、帮助文档一个都没有。等工程里#include <SapClassBasic.h>报“找不到文件”时才发现,那会很折腾。
2.1 Runtime 与 Full 的取舍:开发机一律选 Full
安装向导里通常会出现组件选择页,常见做法是把 Sapera LT 主程序、SDK/Classes、Demos、Documentation 全部勾上。原文里那句“如须获得完整的 demo 与帮助文档,选择 full 安装,并选择全部”,说的就是这一步。开发机不需要犹豫,直接全选;只有产线跑成品程序的机器,才用精简安装省空间。
判断装没装全,不用打开向导翻记录,直接看目录就行:
| 路径 | 内容 | 缺了会怎样 |
|---|---|---|
C:\DALSA\Sapera\Classes\Basic | Basic Classes 头文件 | 编译期找不到SapClassBasic.h |
C:\DALSA\Sapera\Classes\Gui | GUI 类头文件 | 用对话框类时找不到SapClassGui.h |
C:\DALSA\Sapera\Lib | SapClassBasic.lib、SapClassBasicD.lib、SapClassGui.lib、SapClassGuiD.lib | 链接期一片 LNK2019 |
C:\DALSA\Sapera\Demos\Classes\VC | 各 Demo 的 VC 工程 | 没有参照物,只能自己摸索 |
C:\DALSA\Sapera\Help | User's Manual、Programmer's Manual | 查函数签名全靠猜 |
带D后缀的是 Debug 版导入库,不带的是 Release 版,这个对应关系后面配运行库时还要用到,先记着。
2.2 装完先认目录:从 CamExpert 到 GrabDemo 的顺序
安装结束后不要急着打开 VC。按这个顺序走一遍,能提前排掉一半问题:
- 打开 Sapera 自带的相机配置工具,确认能枚举到采集卡和相机,能出图。这一步不通,后面写多少代码都没用。
- 用配置工具导出一份相机配置文件(
.ccf)。后面SapAcquisition初始化要的就是它,名字和路径记下来。 - 进
C:\DALSA\Sapera\Demos\Classes\VC,双击GrabDemo\Release\GrabDemo.exe,看能不能连续采集。能跑,说明驱动、板卡、相机这条链路是通的,问题一定在你自己的工程里。 - 最后再打开
Help目录,把 User's Manual 里 Acquiring Images 那一章过一遍。
Demo 目录值得单独看一眼,它基本覆盖了初级开发会遇到的所有场景,也是后面验证环节的对照标准:
| Demo | 路径片段 | 用途 |
|---|---|---|
| GrabDemo | GrabDemo\Release | 对话框界面的基本采集,连续/单帧 |
| GrabDemoSDI | GrabDemoSDI\Release | 单文档界面版本 |
| GrabDemoMDI | GrabDemoMDI\Release | 多文档界面,可多窗口同步看不同相机 |
| SeqGrabDemo | SeqGrabDemo\Release | 序列采集到内存,可存 AVI |
| BayerDemo | BayerDemo\Release | Bayer 转换与参数设置 |
| FlatFieldDemo | FlatFieldDemo\Release | 平场校正,含 FPN、PRNU、阴影校正 |
| IODemo | IODemo\Release | 一般 I/O 监视与中断计数 |
| GigeCameraDemo | GigeCameraDemo\Release | GigE Vision 相机取图与配置导入 |
提示:安装路径默认是
C:\DALSA\Sapera。如果装在别的盘符或目录,后面所有路径都得跟着改,建议开发机上就用默认值,少一个变量。
3. VC 工程配置:include 路径、lib 链接与运行库三处必改项
新建一个空工程,把采集代码贴进去,然后编译,大概率会失败。原因几乎全集中在这三处:头文件搜索路径没加、导入库没进工程、运行时库选错。原文给的是 VC6 的菜单路径,现在多数人用 VS2010 以后的版本,两套界面对照着来。
3.1 Basic Classes 的五个配置动作
VC6 时代的路径是Project | Settings…,新版 VS 是右键项目 → 属性。逐条对应:
第一步,加头文件目录。VC6 在C/C++ | Preprocessor | Additional include directories,新版在“C/C++ → 常规 → 附加包含目录”,填:
C:\DALSA\Sapera\Classes\Basic第二步,把导入库加进工程。VC6 是Project | Add to Project | Files,新版可以直接在“链接器 → 输入 → 附加依赖项”里写库名,再配一个库目录:
C:\DALSA\Sapera\Lib第三步最容易漏:Project | Settings… | General里把SapClassBasic.lib设为Exclude file from build。原因是这个工程同时引了 Release 和 Debug 两个导入库,如果不排除其中一个,链接器会同时看到两份符号定义直接报重复。新版 VS 没有这个勾选项,常见做法是干脆只往依赖项里放一个,或者用#pragma comment(lib, ...)配合宏区分。
第四步,选运行时库。C/C++ | Code Generation | Use run-time library,Release 选Multithreaded DLL,Debug 选Debug Multithreaded DLL。这一步和 Sapera 库本身的编译方式绑定,选错就是一堆 LNK2005 重定义。
第五步,源码里包含头文件:
#include <SapClassBasic.h> // Basic Classes 的总入口新版 VS 里不想点菜单的话,用命令行编译一次最直观,也能验证路径对不对:
cl /nologo /EHsc /MD /I"C:\DALSA\Sapera\Classes\Basic" ^ GrabTest.cpp ^ /link /LIBPATH:"C:\DALSA\Sapera\Lib" SapClassBasic.lib/MD对应 Release 的 Multithreaded DLL,/MDd对应 Debug 版;/I是头文件目录,/LIBPATH是库目录。这条命令能过,说明路径和库都没问题,剩下的就是代码逻辑。
3.2 GUI Classes 的配置差异:别把两个 lib 搞混
需要用到CAcqConfigDlg、CBufDlg这类现成对话框时,配置要换成 GUI 那一套:头文件目录改成C:\DALSA\Sapera\Classes\Gui,导入库换成SapClassGui.lib与SapClassGuiD.lib,源码里包含SapClassGui.h。
原文在这一步写的是“将 SapClassBasic.lib 选为 Exclude file from build for Win32 Release”,这里要注意:GUI 类本身依赖 Basic 类,实际工程里两个库往往都要链,只是同一套里 Release 和 Debug 只能留一个。我的做法是建两个配置,Release 配置只依赖SapClassBasic.lib+SapClassGui.lib,Debug 配置只依赖带D的两个,物理上分开,就不存在排除谁的问题。
3.3 运行库不匹配的现场:三种典型报错
运行时库这一项,报错信息最不友好。三种情况我都遇到过:
LNK2005: _malloc already defined—— 你的工程用了静态 CRT(/MT),而 Sapera 的库是动态 CRT 编的。改/MD。LNK2019: unresolved external symbol __imp__xxx—— 反过来了,你用了/MD但链的是静态库,或者 Debug 工程链了 Release 的 lib。检查D后缀。- 编译能过、一运行就崩在
SapAcquisition构造里 —— 多半是运行库混用导致堆不一致,或系统里存在多个版本的 Sapera 运行时 DLL。
注意:Debug 和 Release 的导入库不能混链,这是 Sapera 这类商业库最常见的坑之一。判断方法很简单,看工程是 Debug 还是 Release,再决定依赖项里带不带
D。
4. 采集骨架:SapAcquisition 到 SapTransfer 的初始化顺序
配置通了,接下来是代码。Sapera++ 的 Basic Classes 命名规则很统一,都是Sap加类功能名。初级开发真正要记住的就五个:SapAcquisition、SapBuffer、SapBufferWithTrash、SapTransfer、SapView。它们不是并列关系,而是一条链:从板卡到内存,再到屏幕。
4.1 五个类各管一段:先把职责分清再写代码
SapAcquisition负责采集设备和相机配置文件,是所有操作的起点;SapBuffer负责内存里的图像缓冲区,尺寸、格式、数量都在这里定;SapView把 buffer 的内容显示到窗口上;SapTransfer管理采集数据的传递过程,把 acquisition 和 buffer 串起来;SapBufferWithTrash是实时处理场景下的特殊 buffer,当处理速度跟不上采集速度时,用它建一个 trash buffer 接住来不及处理的数据,避免整条链被拖慢。
顺序不能乱:没有 acquisition 就无从谈起采集参数,没有 buffer 就没有地方放图,没有 transfer 就搬不动数据,没有 view 就看不到结果。原文给的采集步骤就是这个顺序,我一般也是照这个模板写。
4.2 最小可运行的初始化与采集代码
下面这段是去掉界面后的骨架,能出第一帧图就够了:
#include <SapClassBasic.h> int main() { // 1. 采集设备:参数为板卡索引与相机配置索引,实际以本地配置为准 SapAcquisition acq(0, 0); if (!acq.Create()) return -1; // 2. 图像缓冲区:宽、高、像素格式、buffer 数量 SapBuffer buf(640, 480, SapFormatMono8, 4); if (!buf.Create()) return -1; // 3. 显示对象:绑定 buffer 与窗口句柄 SapView view(&buf, SapHwnd); // 4. 传输对象:把采集设备和缓冲区连起来 SapTransfer transfer(&acq, &buf); if (!transfer.Create()) return -1; // 5. 采一帧并显示 transfer.Grab(); view.Show(); // 6. 逆序释放,先建的先不释放,后建的后释放 transfer.Destroy(); view.Destroy(); buf.Destroy(); acq.Destroy(); return 0; }参数说明:SapAcquisition的两个索引分别指向板卡和该板卡下的相机配置,具体值取决于配置工具里导出了几份.ccf;SapBuffer的四个参数是宽、高、像素格式和 buffer 数量,buffer 数量大于 1 时可以做连续采集的轮转;SapTransfer的构造函数吃两个指针,顺序是先 acquisition 后 buffer,传反了编译能过但运行时行为不对。
不同版本的构造函数重载略有差别,有的版本要求先无参构造再Create(width, height, format, count)。我的习惯是打开C:\DALSA\Sapera\Help下的Sapera++ Programmer's Manual,对着本地版本确认一次签名,比在网上搜代码靠谱。
4.3 buffer 的三种操作:改大小、取首地址、用子 buffer
拿到图之后,下一步基本都要动 buffer。原文提到三个方向:修改 buffer 大小、子 buffer 的应用、获取首地址及偏移量。后者是接自己图像算法时最常用的。
改大小要在Create()之前设定,已经创建好的 buffer 需要先Destroy()再重建。取首地址的常见写法:
void* pData = NULL; int pitch = 0; // 每行字节数,不是像素数 buf.GetAddress(&pData); // 整个缓冲区的起始地址 buf.GetPitch(&pitch); // 行间距,用于逐行遍历 // 第 y 行第 x 个像素的地址(Mono8 为例) unsigned char* pRow = (unsigned char*)pData + y * pitch + x;这里pitch必须用GetPitch()拿,不能拿宽度乘字节数硬算——buffer 内部可能有对齐填充,自己算会错位,图看起来会像“斜切”了一样。
子 buffer 用于只关心图像某个区域的情况,比如只处理中心 ROI。常见做法是以父 buffer 为模板创建子 buffer,传入起始坐标和宽高,然后对子 buffer 单独挂 view 或 transfer。原文指向Working with buffers - Root and Child Buffers那一节,遇到父子 buffer 生命周期的问题时,那一节是唯一靠谱的出处。
4.4 释放顺序与 SapBufferWithTrash 的使用时机
释放必须逆序:先transfer,再view,再buffer,最后acquisition。任何一步顺序反了,轻则析构函数里报访问违例,重则进程退出时卡死。这类崩溃在 Debug 版里往往看不出堆栈,只能靠打印日志定位。
SapBufferWithTrash不是必选项。什么时候用?处理一帧的耗时偶尔超过帧间隔,但你又不想丢帧的时候。它内部多一块 trash 区域,采集端写满就往后丢,处理端从 trash 里取,代价是内存占用翻倍,以及你拿到的帧时间戳不再严格连续。做离线检测、不需要严格时序的场合可以用;做高速在线检测,宁可不丢帧,也不要 trash。
5. 踩坑排查:链接报错、黑屏、丢帧与运行库冲突
这一章是我这些年真正踩过的记录,按现象、原因、解决三段式写,遇到对应症状直接对号入座。
现象一:编译通过,运行时SapAcquisition::Create()返回 false,没有任何提示。
原因通常是相机配置文件没导对,或者配置文件和当前相机不匹配。配置文件是跟具体相机型号、分辨率、像素格式绑定的,换一台相机还拿旧的用,创建阶段就会失败。
解决办法:回到配置工具重新导出一份.ccf,确认文件路径没有中文和空格,然后在代码里把Create()的返回值打出来,不要直接 return 掉。
现象二:程序能跑,view 窗口一片黑,但 transfer 返回成功。
原因多半是 view 绑定的窗口句柄无效,或者显示在 buffer 还没填数据时就被调用。也有人是把SapView建在了子线程里,窗口句柄跨线程用。
解决办法:先把SapView的构造放到主线程,确认句柄非空;再在transfer.Grab()之后加一次view.Show()。如果还黑,用buf.GetAddress()把首地址的前几个字节打出来,全 0 说明数据根本没进来,问题在采集侧不在显示侧。
现象三:连续采集时偶发丢帧,日志里看不出规律。
原因是SapBuffer数量太少,或者处理耗时超过了帧间隔导致轮转跟不上。buffer 数量设成 1 时,采集端和处理端抢同一块内存,丢帧是必然的。
解决办法:把 buffer 数量加到 4 或 8 试一次,观察丢帧是否减少;确认减少后,再决定是优化处理耗时还是引入SapBufferWithTrash。不加分析直接上 trash,往往把问题从丢帧变成延迟累积。
现象四:LNK2005 重定义,报的符号全是 CRT 里的。
原因是运行时库和 Sapera 导入库不匹配,或者 Release 工程链了SapClassBasicD.lib。这个坑我见过不止一次,尤其是从别人那里拷来的工程,配置项里全是历史遗留。
解决办法:清空依赖项,按 Debug/Release 重新配一遍,确认Multithreaded DLL与Debug Multithreaded DLL对得上,带D和不带D的库不要同时出现。
现象五:换台机器部署,程序启动就提示找不到 DLL。
原因是目标机只装了运行库,或者装的版本比开发机低。Sapera 的运行时 DLL 版本之间不保证向下兼容。
解决办法:部署机装同版本的运行库包,不要图省事拷几个 DLL 过去。版本号在安装包文件名和帮助文档首页都能查到,对一遍再装。
注意:这五条里有三条最终都指向“配置项和环境”,真正属于 API 用法的只有黑屏和丢帧两条。Sapera 这类库的排查顺序应该是先环境、再配置、最后代码,反过来查会浪费大量时间。
6. 装完怎么验证:用 GrabDemo 对照最小工程跑通
装完配完,怎么确认这套环境是真的能用,而不是“编译过了应该没问题”?我的做法是拿官方 Demo 当基准,用自己的最小工程做对照,两边结果一致才算通过。
第一步,跑GrabDemo.exe,连续采集 30 秒,记录三件事:能不能出图、帧率大概多少、有没有花屏。这是基准线。
第二步,跑自己的最小工程,同样采 30 秒,把下面几个值打印出来对照:
printf("acq create: %d\n", acq.Create()); // 采集设备是否创建成功 printf("buf create: %d\n", buf.Create()); // 缓冲区是否创建成功 printf("width=%d height=%d\n", w, h); // 实际尺寸是否与配置一致 printf("pitch=%d\n", pitch); // 行间距是否符合预期第三步,比对结果。尺寸和 Demo 里显示的一致、帧率不低于 Demo 的八成、连续跑十分钟不崩,这三条都满足,环境就算过了。
| 验证项 | 通过标准 | 不通过先查 |
|---|---|---|
| 采集设备创建 | 返回 true | 相机配置文件、板卡驱动 |
| 图像尺寸 | 与配置工具显示一致 | buffer 构造参数 |
| 行间距 pitch | 不小于宽度×字节数 | 像素格式是否选错 |
| 连续采集稳定性 | 十分钟无崩溃、无明显丢帧 | buffer 数量、释放顺序 |
| 部署机启动 | 不提示缺 DLL | 运行库版本是否一致 |
第四步,也是最容易被跳过的一步:把工程从 Debug 切到 Release 再编译一次。运行库配置、库文件后缀、Exclude file from build这几项在两种配置下是分开存的,Debug 通了 Release 挂掉的情况太常见。两边都跑通,这套环境才算真正落地。
从那以后我每次配 Sapera 工程,都强制走一遍“Demo 基准 → 最小工程对照 → Debug/Release 双跑”这套流程,哪怕只是新建一个空项目。省下来的排错时间,远比这几分钟多。希望帮到你。
本文还有配套的精品资源,点击获取