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
    分类: articles