0. 背景介绍
在Windows下用C# 进行DALSA相机的相关开发时,首推Sapera LT SDK,下载链接https://www.teledynevisionsolutions.com/products/sapera-lt-sdk/?model=sapera-lt-sdk&vertical=tvs-dalsa-oem&segment=tvs。如果项目部署到Linux,则需要以P/Invoke的形式调用C++ SDK。由于SDK只带了c, c++和python的demo,网上相关的C# 文档几乎没有,所以在此分享一些心得。
1. 基本环境介绍
相机:Teledyne DALSA线阵相机
测试环境:VMWARE + Debian 11 AMD64
正式环境:RK3588 + Debian11 ARM64
2. SDK
2.1 SDK下载(支持主流架构的Linux)
https://www.teledynevisionsolutions.com/support/support-center/software-firmware-downloads/dalsa/download-gige-v-linux-sdk/
其中,aarch64, ARMhf, ARMsf, x86对应不同的架构,HTML_Help和Programmers_manual.pdf 是html形式和pdf形式的手册。


2.2 SDK安装
a. 安装编译工具
apt install make build-essential libtiff-dev
b. 安装桌面应用必备组件、Python依赖
sudo apt install install python3-tk python3-pil.imagetk libgtk-3-dev libglade2-0 libglade2-dev
c. 解压并运行安装脚本
根据部署环境选择对应的SDK包:RK3588主板使用aarch64包,AMD64使用x86包。上传以后解压到任意目录,执行 sudo ./corinstall,阅读协议并接受后将自动开始安装过程。


3. 相机操作
3.1 基本流程
sdk安装完成后,库文件位置在/usr/local/lib/libGevApi.so。应用启动后需要第一时间调用GevApiInitialize()进行初始化 -> 调用GevGetCameraList()获取相机列表 -> 调用GevOpenCamera()打开相机,该方法会返回这个相机的指针,后续针对这个相机的所有操作都使用该指针。比较特殊的是GevCloseCamera(),需要使用指针的指针。
[DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)]
public static extern int GevApiInitialize();
[DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)]
public static extern int GevGetCameraList(
nint cameraInfoArray,
int arraySize,
out uint actualCount
);
[DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)]
public static extern int GevOpenCamera(
IntPtr cameraInfo,
uint accessMode,
out IntPtr cameraHandle
);
[DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)]
public static extern int GevCloseCamera(ref IntPtr cameraHandle);



3.2 获取和设置参数
a. 使用GevGetPayloadParameters()获取PayloadSize和PixelFormat。

[DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)]
public static extern int GevGetPayloadParameters(
IntPtr cameraHandle,
out ulong payloadSize,
out uint pixelFormat
);
//常用的PixelFormat枚举
public enum GevPixelFormats : uint
{
Mono8 = 0x01080001,
Mono12 = 0x01100005,
Mono12Packed = 0x010C0006,
Mono16 = 0x01100007
}
b. GevGetFeatureValue()获取指定项目的参数值,GevSetFeatureValue()设置指定项目的参数值


[DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)]
public static extern int GevGetFeatureValue(
IntPtr handle,
string feature_name,
out int feature_type,
int value_size,
IntPtr value
);
[DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)]
public static extern int GevSetFeatureValue(
IntPtr handle,
string featureName,
int valueSize,
IntPtr valuePtr
);
3.3 流传输器操作
GevInitializeTransfer()和GevStartTransfer()初始化和开启Stream Transfer;GevStopTransfer()关闭Transfer;GevAbortTransfer()直接放弃当前帧直接关闭Transfer.


[DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)]
public static extern int GevInitializeTransfer(
IntPtr cameraHandle,
int transferMode,
ulong bufferSize,
uint numBuffers,
IntPtr bufferAddresses
);
[DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)]
public static extern int GevAbortTransfer(IntPtr cameraHandle);
[DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)]
public static extern int GevStopTransfer(IntPtr cameraHandle);
[DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)]
public static extern int GevStartTransfer(IntPtr cameraHandle, uint numImages);
3.4 拍照
拍照分两种形式,主动抓取和注册回调,高速抓拍模式下推荐使用循环,在循环内部主动抓取。主动抓取的流程GevWaitForNextFrame() -> GevReleaseFrame()。


[DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)]
public static extern int GevWaitForNextFrame(
IntPtr cameraHandle,
out IntPtr frameBuffer,
uint timeoutMs
);
[DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)]
public static extern int GevReleaseFrame(IntPtr cameraHandle, IntPtr frameBuffer);
4 DEMO
program.cs
internal class Program
{
/// <summary>
/// 内存DUMP比结构体定义大了4个字节
/// </summary>
static readonly int lineaCamInfoSize = Marshal.SizeOf(typeof(GEV_CAMERA_INFO)) + 4;
static LinearCamera cam = null;
static void Main(string[] args)
{
_ = LinearCamera.GevApiInitialize();
Console.WriteLine("AccessMode:\r\n4 - exclusive\r\n2 - control\r\n0 - monitor");
string input = Console.ReadLine();
while (true)
{
if (!uint.TryParse(input, out uint acc))
{
Console.WriteLine($"无效的输入 #{input}#,有效值为 0,2,4");
input = Console.ReadLine();
continue;
}
if (acc != 0 && acc != 2 && acc != 4)
{
Console.WriteLine($"无效的输入 #{input}#,有效值为 0,2,4");
input = Console.ReadLine();
continue;
}
Thread.Sleep(2000);
CallInitLinearCemeras(acc);
}
}
static bool CallInitLinearCemeras(uint acc)
{
bool res;
IntPtr cameraInfoArray = Marshal.AllocHGlobal(lineaCamInfoSize * 16);
res = InitLinearCameras(cameraInfoArray, acc);
Marshal.FreeHGlobal(cameraInfoArray);
return res;
}
private static bool InitLinearCameras(nint cameraInfoArray, uint acc)
{
int ret = LinearCamera.GevGetCameraList(cameraInfoArray, 16, out uint cameraCount);
if (ret != 0 || cameraCount == 0)
{
Console.WriteLine($"{ret}: {(ret == 0 ? "相机数量 0" : "获取相机错误")}");
return false;
}
Console.WriteLine($"InitLinearCameras:{cameraCount} 相机");
for (int i = 0; i < cameraCount; i++)
{
var camInfo = Marshal.PtrToStructure<GEV_CAMERA_INFO>(new IntPtr(cameraInfoArray.ToInt64() + i * lineaCamInfoSize));
string sn = Encoding.ASCII.GetString(camInfo.serial).Trim('\0');
string model = Encoding.ASCII.GetString(camInfo.model).Trim('\0');
string version = Encoding.ASCII.GetString(camInfo.version).Trim('\0');
string manufacture = Encoding.ASCII.GetString(camInfo.manufacturer).TrimEnd('\0');
Console.WriteLine($"尝试打开 #{i + 1}, IP: {camInfo.ipAddr} {LinearCamera.IpAddrToString(camInfo.ipAddr)}," +
$"mac:{camInfo.macHigh}.{camInfo.macLow},host:{camInfo.host.ipAddr} SN: {sn}, model: {model}");
if (!model.Contains("Linea"))
{
Console.WriteLine("\t不是线阵相机,跳过。");
continue;
}
var ptr = new IntPtr(cameraInfoArray.ToInt64() + i * lineaCamInfoSize);
ret = LinearCamera.GevOpenCamera(ptr, acc, out nint cameraHandle);
switch (ret)
{
case 0:
break;
default:
Console.WriteLine($"打开相机 {sn} 错误: {ret:X2}");
return false;
}
int valueSize = sizeof(int);
IntPtr valuePtr = Marshal.AllocHGlobal(valueSize);
ret = LinearCamera.GevGetPayloadParameters(cameraHandle, out ulong payloadBytes, out uint pixelFormat);
if (ret != 0)
{
Console.WriteLine($"GevGetPayloadParameters错误: {ret}");
return false;
}
ret = LinearCamera.GevGetUnpackedPixelType(pixelFormat, out uint unpackedType);
// current: mono12 unpack
Console.WriteLine($"Payload大小: {payloadBytes}Bytes, PixelFormat: {(GevPixelFormats)pixelFormat}, 对应UNPACKED类型: {unpackedType}");
ret = LinearCamera.GevGetFeatureValue(
cameraHandle,
"Width",
out _,
valueSize,
valuePtr
);
int w = Marshal.ReadInt32(valuePtr);
Console.WriteLine($"Width: {w}");
ret = LinearCamera.GevGetFeatureValue(
cameraHandle,
"Height",
out _,
valueSize,
valuePtr
);
int h = Marshal.ReadInt32(valuePtr);
Console.WriteLine($"Height: {h}");
IntPtr[] bufferAddresses = new IntPtr[2];
for (int y = 0; y < bufferAddresses.Length; y++)
{
bufferAddresses[y] = Marshal.AllocHGlobal(Convert.ToInt32(payloadBytes));
}
Console.WriteLine($"申请内存: {bufferAddresses.Length}");
ret = LinearCamera.GevInitializeTransfer(
cameraHandle,
LinearCamera.ASYNCHRONOUS,
payloadBytes,
2,
Marshal.UnsafeAddrOfPinnedArrayElement(bufferAddresses, 0)
);
Console.WriteLine($"GevInitializeTransfer {ret}");
if (ret != 0)
{
Console.WriteLine($"GevInitializeTransfer错误: {ret}");
return false;
}
ret = LinearCamera.GevStartTransfer(cameraHandle, 0xFFFFFFFF);
Console.WriteLine($"GevStartTransfer: {ret}");
if (ret != 0)
{
Console.WriteLine($"GevStartTransfer错误: {ret}");
return false;
}
cam = new(model, sn);
cam.Start(cameraHandle, payloadBytes, ref cameraInfoArray);
break;
}
return true;
}
}
LinearCamera.cs
public class LinearCamera(string model, string sn)
{
#region 项目私有定义
public string Model { get; set; } = model;
public string SN { get; set; } = sn;
private nint _handle = IntPtr.Zero;
public static string IpAddrToString(uint ip) => $"{(ip >> 24) & 0xFF}.{(ip >> 16) & 0xFF}.{(ip >> 8) & 0xFF}.{ip & 0xFF}";
public void Start(nint cameraHandle, ulong payloadBytes, ref nint cameraInfoArray)
{
_handle = cameraHandle;
int frameCount = 0;
List<ushort[]> lines = [];
while (true)
{
int status = GevWaitForNextFrame(cameraHandle, out nint frameBuffer, 100);
if (status != 0 || frameBuffer == IntPtr.Zero)
{
Console.WriteLine($"GevWaitForNextFrame 错误: {status}");
Cleanup();
Thread.Sleep(500);
break;
}
try
{
GEVBUF_HEADER frame = Marshal.PtrToStructure<GEVBUF_HEADER>(frameBuffer);
Console.WriteLine($"抓取 0x{frame.address:X2}, {frameCount}: {frame.w} x {frame.h}, {frame.recv_size}Bytes");
ushort[] pixels = new ushort[payloadBytes / 2];
unsafe
{
fixed (ushort* pPixels = pixels)
{
Buffer.MemoryCopy(frame.address.ToPointer(), pPixels, payloadBytes, payloadBytes);
}
Console.WriteLine($"pixels:{pixels.Length}");
_ = GevReleaseFrame(cameraHandle, frameBuffer);
}
frameCount++;
}
catch (Exception ex)
{
Console.WriteLine($"LinearCamera.Start.Exception: {ex.Message}");
}
Cleanup();
Thread.Sleep(500);
break;
}
}
public void Cleanup()
{
Console.WriteLine($"清理: _handle:{_handle}");
if (_handle != IntPtr.Zero)
{
Console.WriteLine($"GevAbortTransfer:{GevAbortTransfer(_handle)}\t_handle:{_handle}");
Console.WriteLine($"GevFreeTransfer:{GevFreeTransfer(_handle)}\t_handle:{_handle}");
Console.WriteLine($"GevCloseCamera:{GevCloseCamera(ref _handle)}\t_handle:{_handle}");
_handle = IntPtr.Zero;
Console.WriteLine($"释放相机 {Model} [{SN}].");
}
}
#endregion
#region API函数
[DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)]
public static extern int GevApiInitialize();
[DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)]
public static extern int GevGetCameraList(
nint cameraInfoArray,
int arraySize,
out uint actualCount
);
[DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)]
public static extern int GevOpenCamera(
IntPtr cameraInfo,
uint accessMode,
out IntPtr cameraHandle
);
[DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)]
public static extern int GevCloseCamera(ref IntPtr cameraHandle);
[DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)]
public static extern int GevGetPayloadParameters(
IntPtr cameraHandle,
out ulong payloadSize,
out uint pixelFormat
);
[DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)]
public static extern int GevGetUnpackedPixelType(
uint pixelFormat,
out uint unpackedType
);
[DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)]
public static extern int GevGetFeatureValue(
IntPtr handle,
string feature_name,
out int feature_type,
int value_size,
IntPtr value
);
[DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)]
public static extern int GevInitializeTransfer(
IntPtr cameraHandle,
int transferMode,
ulong bufferSize,
uint numBuffers,
IntPtr bufferAddresses
);
[DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)]
public static extern int GevAbortTransfer(IntPtr cameraHandle);
[DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)]
public static extern int GevStartTransfer(IntPtr cameraHandle, uint numImages);
[DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)]
public static extern int GevWaitForNextFrame(
IntPtr cameraHandle,
out IntPtr frameBuffer,
uint timeoutMs
);
[DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)]
public static extern int GevReleaseFrame(IntPtr cameraHandle, IntPtr frameBuffer);
[DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)]
public static extern int GevFreeTransfer(IntPtr cameraHandle);
#endregion
public const string LIBNAME = "/usr/local/lib/libGevApi.so";
public const int MAX_GEVSTRING_LENGTH = 65;
public const int GEV_EXCLUSIVE_MODE = 0x04;
public const int GEV_MONITOR_MODE = 0x00;
public const int GEV_CONTROL_MODE = 0x02;
public const int ASYNCHRONOUS = 0;
}
#region enum
public enum GevPixelFormats : uint
{
Mono8 = 0x01080001,
Mono8Signed = 0x01080002,
Mono10 = 0x01100003,
Mono10Packed = 0x010C0004,
Mono12 = 0x01100005,
Mono12Packed = 0x010C0006,
Mono14 = 0x01100025,
Mono16 = 0x01100007,
BayerGR8 = 0x01080008,
BayerRG8 = 0x01080009,
BayerGB8 = 0x0108000A,
BayerBG8 = 0x0108000B,
BayerGR10 = 0x0110000C,
BayerRG10 = 0x0110000D,
BayerGB10 = 0x0110000E,
BayerBG10 = 0x0110000F,
BayerGR10Packed = 0x010C0026,
BayerRG10Packed = 0x010C0027,
BayerGB10Packed = 0x010C0028,
BayerBG10Packed = 0x010C0029,
BayerGR12 = 0x01100010,
BayerRG12 = 0x01100011,
BayerGB12 = 0x01100012,
BayerBG12 = 0x01100013,
BayerGR12Packed = 0x010C002A,
BayerRG12Packed = 0x010C002B,
BayerGB12Packed = 0x010C002C,
BayerBG12Packed = 0x010C002D,
RGB8Packed = 0x02180014,
BGR8Packed = 0x02180015,
RGBA8Packed = 0x02200016,
BGRA8Packed = 0x02200017,
RGB10Packed = 0x02300018,
BGR10Packed = 0x02300019,
RGB12Packed = 0x0230001A,
BGR12Packed = 0x0230001B,
RGB14Packed = 0x0230005E,
BGR14Packed = 0x0230004A,
RGB16Packed = 0x02300033,
BGR16Packed = 0x0230004B,
RGBA16Packed = 0x02400064,
BGRA16Packed = 0x02400051,
RGB10V1Packed = 0x0220001C,
RGB10V2Packed = 0x0220001D,
YUV411packed = 0x020C001E,
YUV422packed = 0x0210001F,
YUV444packed = 0x02180020,
PFNC_YUV422_8 = 0x02100032,
RGB8Planar = 0x02180021,
RGB10Planar = 0x02300022,
RGB12Planar = 0x02300023,
RGB16Planar = 0x02300024,
PFNC_BiColorBGRG8 = 0x021000A6,
PFNC_BiColorBGRG10 = 0x022000A9,
PFNC_BiColorBGRG10p = 0x021400AA,
PFNC_BiColorBGRG12 = 0x022000AD,
PFNC_BiColorBGRG12p = 0x021800AE,
PFNC_BiColorRGBG8 = 0x021000A5,
PFNC_BiColorRGBG10 = 0x022000A7,
PFNC_BiColorRGBG10p = 0x021400A8,
PFNC_BiColorRGBG12 = 0x022000AB,
PFNC_BiColorRGBG12p = 0x021800AC
}
#endregion
#region struct
[StructLayout(LayoutKind.Sequential)]
public struct GEV_CAMERA_INFO
{
public int fIPv6;
public uint ipAddr;
public uint ipAddrLow;
public uint ipAddrHigh;
public uint macLow;
public uint macHigh;
public GEV_NETWORK_INTERFACE host;
public uint capabilities;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = LinearCamera.MAX_GEVSTRING_LENGTH)]
public byte[] manufacturer;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = LinearCamera.MAX_GEVSTRING_LENGTH)]
public byte[] model;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = LinearCamera.MAX_GEVSTRING_LENGTH)]
public byte[] serial;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = LinearCamera.MAX_GEVSTRING_LENGTH)]
public byte[] version;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = LinearCamera.MAX_GEVSTRING_LENGTH)]
public byte[] username;
}
[StructLayout(LayoutKind.Sequential)]
public struct GEV_NETWORK_INTERFACE
{
public int fIPv6;
public uint ipAddr;
public uint ipAddrLow;
public uint ipAddrHig;
public uint ifIndex;
}
[StructLayout(LayoutKind.Sequential)]
public struct GEVBUF_HEADER
{
public uint payload_type;
public uint state;
/// <summary>
/// 1-imcomplete.0-complete
/// </summary>
public int status;
public uint timestamp_hi;
public uint timestamp_lo;
public ulong timestamp;
public ulong recv_size;
public ulong id;
public uint h;
public uint w;
public uint x_offset;
public uint y_offset;
public uint x_padding;
public uint y_padding;
public uint d;
public uint format;
public IntPtr address;
public IntPtr chunk_data;
public uint chunk_size;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
public byte[] filename;
}
#endregion