简介

GXCI是基于Rust的大恒相机API上层封装

模块

主要分为四大模块:

  1. HAL API:基于RAW Wrapper的为具体功能而作高级封装
  2. RAW Wrapper:GxIAPI.dll的直接Rust封装,提供原生API调用
  3. Utils:一些工具函数
  4. Error Handling:统一错误处理

特性

目前有四个Features:

  • solo:单相机模式
  • use-opencv:使用opencv库
  • (开发中)multi模式:多相机模式
  • (开发中)use-imageproc:使用imageproc库

默认开启的是solo和use-opencv

说白了

raw就是用大恒相机给的C语言开发用的GXIAPI.dll,结合开发文档,用libloading这个库把dll的所有函数加载进来封装了一层。然后此外每个对应的类型、结构体、枚举、常量等等都重新用rust写了一遍。特别是枚举描述部分还遇到过结构体内存没对齐的问题。

hal最主要做的其实就是给GX_INSTANCE、DEVICE_HANDLE这些在库内部用全局变量封装了一层,在调库的时候就不用每次let一个gx出来然后调gx的方法。

但其实全局变量封装目前用的还是std的Mutex(典中典LazyLock<Arc<Mutex<T>>>),暂时还没引入tokio(毕竟手头也只有一个相机,前端那边也还没有多相机的业务)

utils写的一坨狗屎,乱成一堆,开始写的时候项目水平还很差是怎样的,但也暂时懒得改了

error部分挺好的,还是喜欢原生Error Handling

快速开始

主要分为

  1. 从旧版本迁移
  2. 预配置项
  3. 安装
  4. 列举你的设备
  5. 采一张图
  6. 推流
  7. 调整相机参数

这样七个部分,搭配examples快速上手GXCI

从旧版本迁移

如果你是第一次使用GXCI那么,你可以跳过这一节。这一节主要是为了帮助0.3.6之前的GXCI用户迁移到新版本。

GXCI在0.3.5及此前的版本一直使用的是4.9.0版本的OpenCV,但GXCI从0.3.6开始使用4.10.0版本的OpenCV。所以用户也需要相应的升级一下自己的OpenCV版本

通过Choco升级OpenCV

choco upgrade chocolatey
choco upgrade opencv

更改系统环境变量

把系统变量的OPENCV_LINK_LIBS更新为opencv_world4100即可

以后如果报错STATUS_DLL_NOT_FOUND,就复制一份opencv_world4100.dll到exe的同级文件夹就行

清理旧版本Target

如果你之前使用过GXCI,那么你可能需要清理一下之前的target文件夹,因为之前的target文件夹可能会有一些旧的依赖库,导致编译错误

cargo clean

预配置项

预配置项一共有两个,即大恒相机官方驱动OpenCV

大恒相机官方驱动

GXCI库基于大恒相机官方提供的C语言dll(即GxIAPI.dll)进行开发,默认位于C:\Program Files\Daheng Imaging\GalaxySDK\APIDll\Win64\GxIAPI.dll

直接前往大恒官网下载中心下载你所使用的相机的驱动即可。

OpenCV

GXCI默认依赖的图像处理库为opencv,即OpenCV的Rust绑定库。

这部分配置可以参照如下几个文章,但都是英文

当然我们这里也提供了一份中文版的opencv-rust配置方法

安装LLVM和OpenCV 4.10.0

在Windows 10/11中,我建议使用Chocolatey来安装LLVM和OpenCV 4.10.0:

choco install llvm opencv

以下是一些会用到的官方网站:

添加Path环境变量(系统变量)

将以下路径添加到相应的系统的Path环境变量中:

  1. OpenCV的bin路径
  2. OpenCV的x64 bin路径
  3. Chocolatey的bin路径
  4. LLVM的bin路径

请根据你的安装路径自行调整,下面是一个示例:

C:\tools\opencv\build\bin
C:\tools\opencv\build\x64\vc16\bin
C:\ProgramData\chocolatey\bin
C:\Program Files\LLVM\bin

添加OpenCV环境变量(系统变量)

新建三个系统变量:

  1. OPENCV_INCLUDE_PATHS
  2. OPENCV_LINK_LIBS
  3. OPENCV_LINK_PATHS

请根据你的安装路径自行调整,下面是一个示例: |变量名|变量值| |---|---| |OPENCV_INCLUDE_PATHS|C:\tools\opencv\build\include| |OPENCV_LINK_LIBS|opencv_world410| |OPENCV_LINK_PATHS|C:\tools\opencv\build\x64\vc16\lib|

复制opencv_world410.dll到目标目录(如果需要)

有时候你需要将opencv_world4100.dll复制到目标目录,即与exe文件同级的目录。

重启电脑(如果需要)

有时候你需要重启电脑才能使环境变量生效。

Galaxy Viewer (可选)

Galaxy Viewer是大恒相机官方提供的相机调试工具,可以用来查看相机的参数、调整相机的参数等。

难绷碎碎念

OpenCV库在这里用于方便地将图像矩阵化并提供GUI显示图像,说白了就用来保存图片和显示推流的,实际上GXCI目前并没有用到太多的图像处理算法。

怎么说呢,我认为OpenCV在图像处理领域确实是一个必要的库,所以我们也re-export了这个库,依赖启用了use-opencvgxci就可以直接用use gxci::opencv来使用opencv。

但是OpenCV的缺点是配置和编译时间都有点难绷,所以这里还提供了一个use-imageproc特性,通过使用imageproc库来避免使用OpenCV。

但是在我测试的过程中imageproc的编译时间也很长(或者说就没编译成果过),特别是在nalgebra部分。有一天我编译imageproc的nalgebra花了3个小时都还没完成,绝对是卡在那了,尝试被迫终止。所以我也只能认为OpenCV仍然是一个不错的选择了,所以GXCI默认启用的也就是use-opencv这个特性了。

安装

在Cargo.toml中添加依赖:

[dependencies]
gxci = "0.3.6"
# 默认并且目前也只支持solo和use-opencv两个特性

就OK啦,开始调库吧

列举你的设备

HAL代码示例

下面是列举已链接设备的示例代码。该示例代码使用 gxi_count_devicesgxi_list_devices 函数来获取设备数量和设备信息,然后使用 print_device_info 函数打印设备信息。

use gxci::hal::base::*;
use gxci::hal::device::{gxi_count_devices,gxi_list_devices};
use gxci::utils::debug::print_device_info;

fn main()->Result<()> {
    gxci_init_default()?;

    let device_num = gxi_count_devices(1000)?;
    println!("Device number: {}", device_num);

    let base_info = gxi_list_devices()?;
    for device in &base_info {
        print_device_info(&device);
    }
    
    gxci_close()?;

    Ok(())
}

HAL注意点

  1. hal部分的base模块推荐还是直接use gxci::hal::base::*全部引入,一是里面的函数绝对都会用到,而是这里还re-export了error模块的Result类型,方便错误处理。
  2. gxci_init_default()默认的初始化地址是"C:\\Program Files\\Daheng Imaging\\GalaxySDK\\APIDll\\Win64\\GxIAPI.dll",如果你的dll文件不在默认路径,可以使用gxci_init("your_dll_path")来初始化。(暂时只支持绝对路径,请自行拼接适配)
  3. gxi_count_devices函数的参数是超时时间,通常就传1000,单位是毫秒。
  4. gxi_list_devices函数返回的是一个Vec<GX_DEVICE_BASE_INFO>GX_DEVICE_BASE_INFO的具体内容参看GXCI的Docs.rs文档的struct部分或者大恒的C语言SDK文档,文档是SDK自带的,默认位置在C:\Program Files\Daheng Imaging\GalaxySDK\Doc,自己根据安装目录找一找就行。
  5. 在程序结束时,记得调用gxci_close()来释放dll。

RAW代码示例

下面是列举已链接设备的示例代码。首先new一个GXInstance,然后调用 gx_init_lib 函数来初始化库,接着调用 gx_update_device_listgx_get_all_device_base_info 函数来获取设备数量和设备信息,然后使用 print_device_info 函数打印设备信息,最后调用 gx_close_lib 函数来关闭库。

use std::mem::size_of;
use gxci::{
    raw::{
        gx_interface::*, 
        gx_struct::*,
    },
    utils::{
        debug::print_device_info,
        builder::GXDeviceBaseInfoBuilder,
    },
};

fn main() {
        // You can change the library path as you need
        let gx = GXInstance::new("C:\\Program Files\\Daheng Imaging\\GalaxySDK\\APIDll\\Win64\\GxIAPI.dll").expect("Failed to load library");
        gx.gx_init_lib().expect("Failed to initialize library");

        let mut device_num = 0;
        gx.gx_update_device_list(&mut device_num, 1000)
            .expect("Failed to update device list");

        if device_num > 0 {

            let mut base_info: Vec<GX_DEVICE_BASE_INFO> = (0..device_num).map(|_| {
                GXDeviceBaseInfoBuilder::new().build()
            }).collect();
            
            // or you can use the following code to initialize the vector without using the builder

            // let mut base_info = vec![
            //     GX_DEVICE_BASE_INFO {
            //         szVendorName: [0; GX_INFO_LENGTH_32_BYTE],
            //         szModelName: [0; GX_INFO_LENGTH_32_BYTE],
            //         szSN: [0; GX_INFO_LENGTH_32_BYTE],
            //         szDisplayName: [0; GX_INFO_LENGTH_128_BYTE],
            //         szDeviceID: [0; GX_INFO_LENGTH_64_BYTE],
            //         szUserID: [0; GX_INFO_LENGTH_64_BYTE],
            //         accessStatus: GX_ACCESS_STATUS_CMD::Unknown,
            //         deviceClass: GX_DEVICE_CLASS::Unknown,
            //         reserved: [0; 300],
            //     };
            //     device_num as usize
            // ];

            let mut size = (device_num as usize) * size_of::<GX_DEVICE_BASE_INFO>();
            let status = gx
                .gx_get_all_device_base_info(base_info.as_mut_ptr(), &mut size)
                .expect("Failed to get all device base info");

            if status == 0 {
                // Assuming 0 is GX_STATUS_SUCCESS
                println!(
                    "Device base info retrieved successfully. Number of devices: {}",
                    device_num
                );
                for device in &base_info {
                    print_device_info(&device);
                }

            } else {
                println!("Failed to retrieve device base info, status: {}", status);
            }
        } else {
            println!("No Devices found.");
        }

        gx.gx_close_lib().expect("Failed to close library");
        println!("Library closed.")
    }

RAW注意点

  1. 谨慎使用吧,平常开发用hal还是更舒服一些(主要还是懒得写文档了)

采一张图

HAL代码示例

采图首先要用gxi_open_device打开设备,然后调用gxi_get_image获取图像,最后调用gxi_save_image_as_png保存缓存区中的图像,然后调用gxi_close_device关闭设备就走完一遍流程了

当然,除了gxi_get_image把图像保存在缓存区这个返回Resulf<()>函数,还有gxi_get_image_as_rawgxi_get_image_as_bytes两个函数可以返回拿到的图像数据,前者返回的是Result<&[u8]>,后者返回的是Result<Vec<u8>>

use gxci::hal::device::*;
use gxci::hal::base::*;
use gxci::utils::debug::print_device_info;

fn main()->Result<()> {
    gxci_init_default()?;

    let device_num = gxi_count_devices( 1000)?;
    println!("Device number: {}", device_num);

    let base_info = gxi_list_devices()?;
    for device in &base_info {
        print_device_info(&device);
    }
    
    gxi_open_device()?;

    gxi_get_image()?;
    
    gxi_save_image_as_png("test.png")?;

    gxi_close_device()?;

    gxci_close()?;
    
    Ok(())
}

HAL注意点

  1. 当然偷懒直接把device的全部都引入也是可以的23333
  2. gxi_open_devicesolo模式下默认打开第一个设备,多设备暂时没写
  3. gxi_get_image函数返回的是Result<()>,因为图像数据是保存在缓存区的,所以这个函数只是把图像数据保存在缓存区,如果要获取图像数据,可以使用gxi_get_image_as_rawgxi_get_image_as_bytes两个函数
  4. gxi_save_image_as_png函数的参数是保存的文件名,保存在当前目录下

RAW代码示例

采图首先要用gx_open_device_by_index打开设备,然后发送GX_COMMAND_ACQUISITION_START命令开始采集,接着调用gx_get_image获取图像,采图成功后调用opencv库把image_buffer转换成Mat对象,然后调用imwrite函数保存图像,最后调用gx_send_command发送GX_COMMAND_ACQUISITION_STOP命令停止采集,然后调用gx_close_device关闭设备就走完一遍流程了


use std::mem::size_of;
use std::slice;

use opencv::{
    imgcodecs,
    core,
};

use gxci::{
    raw::{
        gx_interface::*, 
        gx_enum::*,
        gx_struct::*,
        gx_handle::*,
    },
    utils::{
        debug::print_device_info,
        builder::GXDeviceBaseInfoBuilder,
        facade::*,
    },
};


fn main() -> Result<()> {
    unsafe {
        let gx = GXInstance::new("C:\\Program Files\\Daheng Imaging\\GalaxySDK\\APIDll\\Win64\\GxIAPI.dll").expect("Failed to load library");
        gx.gx_init_lib().expect("Failed to initialize library");

        // Update the device list
        let mut device_num = 0;
        gx.gx_update_device_list(&mut device_num, 1000)
            .expect("Failed to update device list");

        if device_num > 0 {

            let mut base_info: Vec<GX_DEVICE_BASE_INFO> = (0..device_num).map(|_| {
                GXDeviceBaseInfoBuilder::new().build()
            }).collect();
            let mut size = (device_num as usize) * size_of::<GX_DEVICE_BASE_INFO>();
            let status = gx
                .gx_get_all_device_base_info(base_info.as_mut_ptr(), &mut size)
                .expect("Failed to get all device base info");

            if status == 0 {
                println!(
                    "Device base info retrieved successfully. Number of devices: {}",
                    device_num
                );
                
                for device in &base_info {
                    print_device_info(&device);
                }

                let first_device_sn = std::str::from_utf8(&base_info[0].szSN).unwrap_or("");
                let mut device_handle: GX_DEV_HANDLE = std::ptr::null_mut();

                let open_status = gx
                    .gx_open_device_by_index(1, &mut device_handle)
                    .expect("Failed to open device with index");

                if open_status == 0 {
                    println!(
                        "Successfully opened device index 1 with SN: {}",
                        first_device_sn.trim_end_matches(char::from(0))
                    );

                    gx.gx_send_command(device_handle, GX_FEATURE_ID::GX_COMMAND_ACQUISITION_START)
                        .expect("Failed to send command");

                    // 这种写法在所有权机制下是错误的,因为image_buffer在返回的时候就已经被释放了
                    // let frame_data_facade = fetch_frame_data(&gx, device_handle);
                    // let mut frame_data = convert_to_frame_data(&frame_data_facade.unwrap());
                    
                    // 这种写法是正确的,因为image_buffer被返回到了当前作用域
                    #[allow(unused_variables)]
                    let (frame_data_facade, image_buffer) = fetch_frame_data(&gx, device_handle).unwrap();
                    let mut frame_data = convert_to_frame_data(&frame_data_facade);


                        let result = gx.gx_get_image(device_handle, &mut frame_data, 100);
                        match result {
                            Ok(_) => {
                                println!("Image captured successfully.");

                                if frame_data.nStatus == 0 {
                                    let data = slice::from_raw_parts(frame_data.pImgBuf as *const u8, (frame_data.nWidth * frame_data.nHeight) as usize);
                                    
                                    let mat = core::Mat::new_rows_cols_with_data(
                                        frame_data.nHeight, 
                                        frame_data.nWidth, 
                                        data
                                    ).unwrap();
                        
                                    let vec = core::Vector::<i32>::new();
                                    if imgcodecs::imwrite("right.png", &mat, &vec).unwrap() {
                                        println!("Image saved successfully.");
                                    } else {
                                        println!("Failed to save the image.");
                                    }
                                    
                                }
                            }
                            Err(e) => eprintln!("Failed to capture image: {:?}", e),
                        }

                    gx.gx_send_command(device_handle, GX_FEATURE_ID::GX_COMMAND_ACQUISITION_STOP)
                        .expect("Failed to send command");

                    // Close the device
                    gx.gx_close_device(device_handle)
                        .expect("Failed to close device");
                    println!("Device closed.")
                } else {
                    println!(
                        "Failed to open device with SN: {}",
                        first_device_sn.trim_end_matches(char::from(0))
                    );
                }
            } else {
                println!("Failed to retrieve device base info, status: {}", status);
            }
        } else {
            println!("No Devices found.");
        }

        gx.gx_close_lib().expect("Failed to close library");
        println!("Library closed.");
        Ok(())
    }
}

RAW注意点

  1. 谨慎使用吧,平常开发用hal还是更舒服一些(主要还是懒得写文档了)
  2. 这里还玩了会所有权游戏,image_buffer如果不返回到当前作用域,就会在facade的那个函数中被释放掉,导致下文直接就拿不到图像数据了(送客不接客,客死他乡了)

推流

HAL代码示例

核心在于gxi_use_stream,这个0.3.4版本新增的函数

use gxci::hal::device::*;
use gxci::hal::base::*;
use gxci::utils::debug::print_device_info;
use gxci::utils::extract::{extract_callback_img_buf,extract_frame_callback_param};
use gxci::raw::gx_struct::GX_FRAME_CALLBACK_PARAM;
use gxci::raw::gx_interface::Result;
use gxci::opencv::{core, highgui};


extern "C" fn frame_callback(p_frame_callback_data: *mut GX_FRAME_CALLBACK_PARAM) {
    
    let frame_callback_data = extract_frame_callback_param(p_frame_callback_data);
    let data = extract_callback_img_buf(frame_callback_data);

    let mat = core::Mat::new_rows_cols_with_data(
        frame_callback_data.nHeight, 
        frame_callback_data.nWidth, 
        data
    ).unwrap();
    
    highgui::imshow("Camera Frame", &mat).unwrap();
    if highgui::wait_key(10).unwrap() > 0 {
        highgui::destroy_window("Camera Frame").unwrap();
    }
}

fn main()->Result<()> {
    gxci_init_default()?;

    let device_num = gxi_count_devices( 1000)?;
    println!("Device number: {}", device_num);

    let base_info = gxi_list_devices()?;
    for device in &base_info {
        print_device_info(&device);
    }

    gxi_open_device()?;

    gxi_use_stream(frame_callback)?;

    gxi_close_device()?;

    gxci_close()?;

    Ok(())
}

HAL注意点

  1. 最闪亮的函数当属gxi_use_stream,这个函数的参数是一个类型为pub type GXCaptureCallBack = extern "C" fn(pFrameData: *mut GX_FRAME_CALLBACK_PARAM);的回调函数,允许你自定义回调函数来处理推流采集到的图像数据。此外,在utils模块中还提供了extract_callback_img_bufextract_frame_callback_param两个函数来帮助你提取回调函数中的图像数据。
  2. 这里的frame_callback就仅仅只是把图像用opencv显示出来罢了,你可以根据自己的需求来处理图像数据。例如通过全局变量来暴露图像数据,然后在主线程中处理图像数据等等

RAW代码示例


use std::mem::size_of;
use std::slice;
use std::thread::sleep;
use std::time::Duration;

use opencv::{
    highgui,
    core,
};

use gxci::{
    raw::{
        gx_interface::*, 
        gx_enum::*,
        gx_struct::*,
        gx_handle::*,
    },
    utils::{
        debug::print_device_info,
        builder::GXDeviceBaseInfoBuilder,
    },
};

extern "C" fn frame_callback(p_frame_callback_data: *mut GX_FRAME_CALLBACK_PARAM) {
    // 避免刷屏
    // println!("Frame callback triggered.");
    // println!("Frame status: {:?}", unsafe { (*p_frame_callback_data).status });
    // println!("Frame All: {:?}", unsafe { *p_frame_callback_data });

    unsafe {
        let frame_callback_data = &*p_frame_callback_data;
        if frame_callback_data.status == 0 {
            let data = slice::from_raw_parts(frame_callback_data.pImgBuf as *const u8, (frame_callback_data.nWidth * frame_callback_data.nHeight) as usize);
            let mat = core::Mat::new_rows_cols_with_data(
                frame_callback_data.nHeight, 
                frame_callback_data.nWidth, 
                // core::CV_8UC1, 
                data
            ).unwrap();
            highgui::imshow("Camera Frame", &mat).unwrap();
            if highgui::wait_key(10).unwrap() > 0 {
                highgui::destroy_window("Camera Frame").unwrap();
            }
        }
    }
}

fn main()->Result<()> {
        // You can change the library path as you need
        let gx = GXInstance::new("C:\\Program Files\\Daheng Imaging\\GalaxySDK\\APIDll\\Win64\\GxIAPI.dll").expect("Failed to load library");
        gx.gx_init_lib().expect("Failed to initialize library");

        // Update the device list
        let mut device_num = 0;
        gx.gx_update_device_list(&mut device_num, 1000)
            .expect("Failed to update device list");

        if device_num > 0 {

            let mut base_info: Vec<GX_DEVICE_BASE_INFO> = (0..device_num).map(|_| {
                GXDeviceBaseInfoBuilder::new().build()
            }).collect();
            let mut size = (device_num as usize) * size_of::<GX_DEVICE_BASE_INFO>();
            let status = gx
                .gx_get_all_device_base_info(base_info.as_mut_ptr(), &mut size)
                .expect("Failed to get all device base info");

            if status == 0 {
                // Assuming 0 is GX_STATUS_SUCCESS
                println!(
                    "Device base info retrieved successfully. Number of devices: {}",
                    device_num
                );
                
                for device in &base_info {
                    print_device_info(&device);
                }

                // Attempt to open the first device using its SN
                let first_device_sn = std::str::from_utf8(&base_info[0].szSN).unwrap_or("");
                let mut device_handle: GX_DEV_HANDLE = std::ptr::null_mut();

                let open_status = gx
                    .gx_open_device_by_index(1, &mut device_handle)
                    .expect("Failed to open device with index");

                if open_status == 0 {
                    println!(
                        "Successfully opened device index 1 with SN: {}",
                        first_device_sn.trim_end_matches(char::from(0))
                    );

                    gx.gx_set_float(device_handle, GX_FEATURE_ID::GX_FLOAT_GAIN, 20.0)?;

                    let reg_result = gx.gx_register_capture_callback(device_handle, frame_callback);
                    match reg_result {
                        Ok(_) => println!("Capture callback registered successfully."),
                        Err(e) => eprintln!("Failed to register capture callback: {:?}", e),
                    }

                    gx.gx_send_command(device_handle, GX_FEATURE_ID::GX_COMMAND_ACQUISITION_START)
                        .expect("Failed to send command");
                        
                        highgui::named_window("Camera", highgui::WINDOW_AUTOSIZE).unwrap();
                    loop {
                        sleep(Duration::from_secs(60));
                        break;
                    }

                    gx.gx_send_command(device_handle, GX_FEATURE_ID::GX_COMMAND_ACQUISITION_STOP)
                        .expect("Failed to send command");

                    let unregeister_result = gx.gx_unregister_capture_callback(device_handle);
                    match unregeister_result {
                        Ok(_) => println!("Capture callback unregistered successfully."),
                        Err(e) => eprintln!("Failed to unregister capture callback: {:?}", e),
                    }

                    // Close the device
                    gx.gx_close_device(device_handle)
                        .expect("Failed to close device");
                    println!("Device closed.")
                } else {
                    println!(
                        "Failed to open device with SN: {}",
                        first_device_sn.trim_end_matches(char::from(0))
                    );
                }
            } else {
                println!("Failed to retrieve device base info, status: {}", status);
            }
        } else {
            println!("No Devices found.");
        }

        gx.gx_close_lib().expect("Failed to close library");
        println!("Library closed.");
        Ok(())
    }

RAW注意点

  1. 谨慎使用吧,平常开发用hal还是更舒服一些(主要还是懒得写文档了)

调整相机参数

HAL代码示例

这里我们就承接着上面推流部分的代码进行了,这里我们调整了相机的一些参数,例如设置了相机的分辨率、自动增益等等,分别是通过gxi_set_widthgxi_set_heightgxi_set_gain_auto_continuousgxi_set_gain函数。

use gxci::hal::device::*;
use gxci::hal::base::*;
use gxci::hal::control::analog::*;
use gxci::hal::control::image_format::*;
use gxci::utils::debug::print_device_info;
use gxci::utils::extract::{extract_callback_img_buf,extract_frame_callback_param};
use gxci::raw::gx_struct::GX_FRAME_CALLBACK_PARAM;
use gxci::raw::gx_interface::Result;
use gxci::opencv::{core, highgui};


extern "C" fn frame_callback(p_frame_callback_data: *mut GX_FRAME_CALLBACK_PARAM) {
    
    let frame_callback_data = extract_frame_callback_param(p_frame_callback_data);
    let data = extract_callback_img_buf(frame_callback_data);

    let mat = core::Mat::new_rows_cols_with_data(
        frame_callback_data.nHeight, 
        frame_callback_data.nWidth, 
        data
    ).unwrap();
    
    highgui::imshow("Camera Frame", &mat).unwrap();
    if highgui::wait_key(10).unwrap() > 0 {
        highgui::destroy_window("Camera Frame").unwrap();
    }
}

fn main()->Result<()> {
    gxci_init_default()?;

    let device_num = gxi_count_devices( 1000)?;
    println!("Device number: {}", device_num);

    let base_info = gxi_list_devices()?;
    for device in &base_info {
        print_device_info(&device);
    }
    

    gxi_open_device()?;

    gxi_set_width(4024)?;
    
    gxi_set_height(3036)?;

    gxi_set_gain_auto_continuous()?;

    // gxi_set_gain(1.0)?;

    gxi_use_stream(frame_callback)?;

    gxi_close_device()?;

    gxci_close()?;

    Ok(())
}

HAL注意点

  1. 注意参数设置范围,例如分辨率的设置范围,增益的设置范围等等,超出相机限制范围会导致函数返回错误
  2. 实际上control这个模块就是按照Galaxy Viewer的设置界面来对应编写了,涵盖了大部分的相机参数设置需求。但是底层都是通过gxi_get_feature_valuegxi_set_feature_value函数来设置的,所以如果有特殊的参数设置需求,可以参考具体文档来直接调用这两个函数

RAW代码示例

哦豁,这个还没写

RAW注意点

  1. 有时间再写

更改日志

0.1

  • raw模块从gxi_hako库迁移过来,然后gxi_hako库进入deprecated状态
  • 初步实现了hal模块,用于相机的基本操作

0.2

0.2.0

  • 安全化了raw模块,现在所有对raw模块的调用都不需要unsafe
  • 初步实现了全局错误处理,但是是在raw模块的gx_interface
  • md文档中的图片上云,减少体积

0.2.3

  • 重新加入了solofeature
  • gxci_init_default()可以通过默认路径加载GxIAPI.dll,gxci_check_device_handle()可以检查设备句柄是否有效

0.2.4

  • gxi_get_device_handle()用于获取设备句柄
  • 初始化配置了config模块,但是没有实现任何功能

0.3

0.3.0

  • hal添加了check模块,用于检查常见错误
  • hal添加了config模块,用于配置相机参数,但是某些FeatureID缺失,导致部分功能未实现
  • hal添加了control模块,用于控制相机,基于Galaxy Viewer的侧边栏

0.3.2

  • gxi_use_stream()允许使用自定义流回调函数处理图像数据。您可以在hal_use_stream示例中查看用法

0.3.3

  • re-export了opencvimageproc,您可以通过gxci::opencvgxci::imageproc访问这两个模块

0.3.4

  • gxi_get_image_as_frame_data()gxi_get_image_as_raw()gxi_get_image_as_bytes()提供了使用图像数据的接口,并提供了示例

0.3.5

  • 独立的error.rs模块和优化的错误处理部分

0.3.6

  • 升级到OpenCV 4.10.0,依赖opencv和升级到最新的0.93.5
  • 基于mdbook搭建了中文与英文的文档站,提供了快速上手的教程,网址为https://hakochest.github.io/gxci/

这啥,好像是之前写的,不过还是留着吧

旧版是一个叫做gxi_hako的crate库,里面raw部分和utils部分的不完全实现,现在已经暂时弃用了;

新版也就是这一款gxci,里面包含了raw、HAL、utils三个部分的实现;

截至目前,2024年7月11日23点45分,已经完成了features=["solo"]部分的HAL库编写,多相机的feature还未实现,等再次闲下来再来更新吧(๑˃ᴗ˂)ﻭ

2024年9月8日21点15分,2.0更新!主要是错误处理和安全化,现在所有的库函数都是安全的了,同时建立了非常规范和健壮的错误处理。此外也更新了所有例子,消除了所有的Warning。

2024年9月19日16点09分,3.0更新!主要是增加了config模块,现在所有的HAL和raw-binding的config模块都已经实现了,你可以调节宽高、增益、白平衡以及一切已实现的相机参数!但是由于inc中部分FeatureID的缺失,所以config模块还有一些函数没有实现。此外,增加了control模块,这是基于Galaxy Viewer的侧边栏常用部分的封装。

路线图

0.4计划更新多相机支持,尝试解决imageproc的依赖问题

附录

GxIAPI.dll的方法实现情况

  • 302 0 0001C020 GXCloseDevice
  • 101 1 0001BBC0 GXCloseLib
  • 700 2 0001E9E0 GXExportConfigFile
  • 707 3 0001EA50 GXExportConfigFileW ?在开发文档里面没介绍这个函数
  • 602 4 0001E920 GXFlushEvent
  • 505 5 0001E6E0 GXFlushQueue
  • 201 6 0001BDE0 GXGetAllDeviceBaseInfo
  • 414 7 0001D5F0 GXGetBool
  • 419 8 0001E080 GXGetBuffer
  • 418 9 0001DF50 GXGetBufferLength
  • 205 A 0001BE80 GXGetDeviceIPInfo
  • 423 B 0001C0B0 GXGetDevicePersistentIpAddress
  • 411 C 0001D3C0 GXGetEnum
  • 410 D 0001CF50 GXGetEnumDescription
  • 409 E 0001CE20 GXGetEnumEntryNums
  • 506 F 0001E970 GXGetEventNumInQueue
  • 422 10 0001C1E0 GXGetFeatureName
  • 408 11 0001CCF0 GXGetFloat
  • 406 12 0001C960 GXGetFloatRange
  • 504 13 0001E670 GXGetImage
  • 404 14 0001C730 GXGetInt
  • 403 15 0001C590 GXGetIntRange
  • 204 16 0001BC40 GXGetLastError
  • 709 17 0001F370 GXGetOptimalPacketSize (Windows Only)
  • 416 18 0001DAA0 GXGetString
  • 415 19 0001D820 GXGetStringLength
  • 425 1A 0001D970 GXGetStringMaxLength
  • 705 1B 0001EEF0 GXGigEForceIp
  • 704 1C 0001ECC0 GXGigEIpConfiguration
  • 706 1D 0001F170 GXGigEResetDevice
  • 701 1E 0001EAC0 GXImportConfigFile
  • 708 1F 0001EB40 GXImportConfigFileW ?在开发文档里面没介绍这个函数
  • 100 20 0001BB70 GXInitLib
  • 400 21 0001C260 GXIsImplemented
  • 401 22 0001C370 GXIsReadable
  • 402 23 0001C480 GXIsWritable
  • 301 24 0001BFB0 GXOpenDevice
  • 300 25 0001BF10 GXOpenDeviceByIndex
  • 702 26 0001EBC0 GXReadRemoteDevicePort
  • 710 27 0001F3E0 GXReadRemoteDevicePortStacked
  • 500 28 0001E5B0 GXRegisterCaptureCallback
  • 600 29 0001E730 GXRegisterDeviceOfflineCallback
  • 603 2A 0001E820 GXRegisterFeatureCallback
  • 421 2B 0001E480 GXSendCommand
  • 507 2C 0001F100 GXSetAcqusitionBufferNumber
  • 413 2D 0001D720 GXSetBool
  • 420 2E 0001E350 GXSetBuffer
  • 424 2F 0001C160 GXSetDevicePersistentIpAddress
  • 412 30 0001D4F0 GXSetEnum
  • 407 31 0001CBE0 GXSetFloat
  • 405 32 0001C860 GXSetInt
  • 417 33 0001DDC0 GXSetString
  • 501 34 0001E620 GXUnregisterCaptureCallback
  • 601 35 0001E7B0 GXUnregisterDeviceOfflineCallback
  • 604 36 0001E8B0 GXUnregisterFeatureCallback
  • 206 37 0001BD70 GXUpdateAllDeviceList
  • 200 38 0001BD00 GXUpdateDeviceList
  • 703 39 0001EC40 GXWriteRemoteDevicePort
  • 711 3A 0001F450 GXWriteRemoteDevicePortStacked (Windows Only)

HAL模块函数实现情况

  • base
    • gxi_check()
    • gxci_init()
    • gxci_init_default()
    • gxci_close()
  • device
    • gxi_count_devices()
    • gxi_list_devices()
    • gxi_open_device() // solo feature
    • gxi_close_device() // solo feature
    • gxi_check_device_handle() // solo feature
    • gxi_send_command() // solo feature
    • gxi_get_image() // solo feature
    • gxi_open_stream() // solo feature
    • gxi_open_stream_interval() // solo feature
    • gxi_close_stream() // solo feature
  • check
    • check_status()
    • check_status_with_ok_fn()
    • check_gx_status()
    • check_gx_status_with_ok_fn()
  • config
    • Here are the HAL functions
    • gxi_get_feature_value()
    • gxi_set_feature_value()
    • Following are the raw-wapper functions
    • gxi_get_feature_name()
    • gxi_get_int_range()
    • gxi_get_int()
    • gxi_set_int()
    • gxi_get_float_range()
    • gxi_get_float()
    • gxi_set_float()
    • gxi_get_enum_entry_nums()
    • gxi_get_enum_description()
    • gxi_get_enum()
    • gxi_set_enum()
    • gxi_get_bool()
    • gxi_set_bool()
    • gxi_get_string_length()
    • gxi_get_string_max_length()
    • gxi_get_string()
    • gxi_set_string()
    • gxi_get_buffer_length()
    • gxi_get_buffer()
    • gxi_set_buffer()
  • control (这里只列出了函数的数量,这部分的列表太长了,所以请查看ControlList markdown)
    • device 17
    • image_format 27+
    • acquisition 46+
    • digital_io 0 (But MISSING this module's FEATURE_ID)
    • analog 40+
    • transport_layer 1
    • user_set 10
    • chunk_data 8
  • event
    • todo!()
  • network
    • todo!()

控制函数列表

这部分的列表太长了,所以我把它分成了这个文档。

某些枚举选择函数将在未来实现,但应该不是在0.3。

并且很多FeatureID都缺失了,所以很多函数都没有实现。

  • control
    • device
      • gxi_get_device_vendor_name()
      • gxi_get_device_model_name()
      • gxi_get_device_version()
      • gxi_get_device_firmware_version()
      • gxi_get_device_serial_number()
      • gxi_get_factory_setting_version()
      • gxi_get_device_user_id()
      • gxi_set_device_user_id()
      • gxi_get_device_link_selector()
      • gxi_set_device_link_selector()
      • gxi_get_device_link_throughput_limit_mode()
      • gxi_set_device_link_throughput_limit_mode()
      • gxi_set_device_link_throughput_limit_mode_off()
      • gxi_set_device_link_throughput_limit_mode_on()
      • gxi_get_device_link_throughput_limit()
      • gxi_set_device_link_throughput_limit()
      • gxi_get_device_link_current_throughput()
      • todo!()
    • image_format
      • gxi_get_sensor_width()
      • gxi_get_sensor_height()
      • gxi_get_max_width()
      • gxi_get_max_height()
      • gxi_get_width()
      • gxi_set_width()
      • gxi_get_height()
      • gxi_set_height()
      • gxi_get_offset_x()
      • gxi_set_offset_x()
      • gxi_get_offset_y()
      • gxi_set_offset_y()
      • gxi_get_region_selector()
      • gxi_set_region_selector()
      • gxi_set_region_selector_region0()
      • gxi_get_pixel_format()
      • gxi_set_pixel_format()
      • gxi_set_pixel_format_bayer_rgb()
      • gxi_set_pixel_format_bayer_rg10()
      • gxi_get_pixel_size()
      • gxi_get_pixel_color_filter()
      • gxi_get_test_pattern_generator_selector()
      • gxi_set_test_pattern_generator_selector()
      • gxi_set_test_pattern_generator_selector_region0()
      • gxi_get_test_pattern()
      • gxi_set_test_pattern()
      • gxi_set_test_pattern_off()
      • gxi_set_test_pattern_gray_frame_ramp_moving()
      • gxi_set_test_pattern_slant_line_moving()
      • gxi_set_test_pattern_slant_line()
      • todo!()
    • acquisition
      • gxi_get_acquisition_mode()
      • gxi_set_acquisition_mode()
      • gxi_set_acquisition_mode_continuous()
      • gxi_acquisition_start()
      • gxi_acquisition_stop()
      • gxi_get_trigger_selector()
      • gxi_set_trigger_selector()
      • gxi_set_trigger_selector_frame_start()
      • gxi_get_trigger_mode()
      • gxi_set_trigger_mode()
      • gxi_set_trigger_mode_off()
      • gxi_get_trigger_source()
      • gxi_set_trigger_source()
      • gxi_set_trigger_source_software()
      • gxi_set_trigger_source_line0()
      • gxi_set_trigger_source_line2()
      • gxi_set_trigger_source_line3()
      • gxi_get_trigger_activation()
      • gxi_set_trigger_activation()
      • gxi_set_trigger_activation_falling_edge()
      • gxi_set_trigger_activation_rising_edge()
      • gxi_get_trigger_delay()
      • gxi_set_trigger_delay()
      • gxi_get_trigger_filter_rasing_edge()
      • gxi_set_trigger_filter_rasing_edge()
      • gxi_get_trigger_filter_falling_edge()
      • gxi_set_trigger_filter_falling_edge()
      • gxi_get_exposure_mode()
      • gxi_set_exposure_mode()
      • gxi_set_exposure_mode_timed()
      • gxi_get_exposure_time()
      • gxi_set_exposure_time()
      • gxi_get_exposure_auto()
      • gxi_set_exposure_auto()
      • gxi_set_exposure_auto_off()
      • gxi_set_exposure_auto_continuous()
      • gxi_set_exposure_auto_once()
      • gxi_get_auto_exposure_time_min()
      • gxi_set_auto_exposure_time_min()
      • gxi_get_auto_exposure_time_max()
      • gxi_set_auto_exposure_time_min()
      • gxi_get_aaroi_width()
      • gxi_set_aaroi_width()
      • gxi_get_aaroi_height()
      • gxi_set_aaroi_height()
      • gxi_get_aaroi_offset_x()
      • gxi_set_aaroi_offset_x()
      • gxi_get_aaroi_offset_y()
      • gxi_set_aaroi_offset_y()
      • gxi_get_expected_gray_value()
      • gxi_set_expected_gray_value()
      • todo!()
    • digital_io (But MISSING this module's FEATURE_ID)
      • gxi_get_line_selector()
      • gxi_set_line_selector()
      • gxi_set_line_selector_line0()
      • gxi_set_line_selector_line1()
      • gxi_set_line_selector_line2()
      • gxi_set_line_selector_line3()
      • gxi_get_line_mode()
      • gxi_set_line_mode()
      • gxi_set_line_mode_input()
      • gxi_get_line_inverter()
      • gxi_set_line_inverter()
      • gxi_get_line_source()
      • gxi_get_line_status()
      • gxi_get_line_status_all()
      • gxi_get_user_output_selector()
      • gxi_set_user_output_selector()
      • gxi_set_user_output_selector_user_output0()
      • gxi_set_user_output_selector_user_output1()
      • gxi_set_user_output_selector_user_output2()
      • gxi_get_user_output_value()
      • gxi_set_user_output_value()
      • todo!()
    • analog
      • gxi_get_gain_selector()
      • gxi_set_gain_selector()
      • gxi_set_gain_selector_analog_all()
      • gxi_get_gain()
      • gxi_set_gain()
      • gxi_get_gain_auto()
      • gxi_set_gain_auto()
      • gxi_set_gain_auto_off()
      • gxi_set_gain_auto_continuous()
      • gxi_set_gain_auto_once()
      • gxi_get_auto_gain_min()
      • gxi_set_auto_gain_min()
      • gxi_get_auto_gain_max()
      • gxi_set_auto_gain_max()
      • gxi_get_balance_ratio_selector()
      • gxi_set_balance_ratio_selector()
      • gxi_set_balance_ratio_selector_red()
      • gxi_set_balance_ratio_selector_green()
      • gxi_set_balance_ratio_selector_blue()
      • gxi_get_balance_ratio()
      • gxi_get_balance_white_auto()
      • gxi_set_balance_white_auto()
      • gxi_set_balance_white_auto_off()
      • gxi_set_balance_white_auto_continuous()
      • gxi_set_balance_white_auto_once()
      • gxi_get_awb_lamp_house()
      • gxi_set_awb_lamp_house()
      • gxi_set_awb_lamp_house_adaptive()
      • todo!("more variants")
      • gxi_get_awbroi_width()
      • gxi_set_awbroi_width()
      • gxi_get_awbroi_height()
      • gxi_set_awbroi_height()
      • gxi_get_awbroi_offset_x()
      • gxi_set_awbroi_offset_x()
      • gxi_get_awbroi_offset_y()
      • gxi_set_awbroi_offset_y()
      • todo!()
    • transport_layer
      • gxi_get_payload_size()
      • todo!()
    • user_set
      • gxi_get_user_set_selector()
      • gxi_set_user_set_selector()
      • gxi_set_user_set_selector_default()
      • gxi_set_user_set_selector_user_set0()
      • gxi_user_set_load()
      • gxi_user_set_save()
      • gxi_get_user_set_default()
      • gxi_set_user_set_default()
      • gxi_set_user_set_default_default()
      • gxi_set_user_set_default_user_set0()
      • todo!()
    • chunk_data
      • gxi_get_chunk_mode_active()
      • gxi_set_chunk_mode_active()
      • gxi_get_chunk_selector()
      • gxi_set_chunk_selector()
      • gxi_set_chunk_selector_frame_id()
      • gxi_set_chunk_selector_timestamp()
      • gxi_get_chunk_enable()
      • gxi_set_chunk_enable()
      • gxi_get_chunk_timestamp()
      • todo!()

问题反馈

有任何问题,直接提 issue 即可

如果你想贡献,可以 fork 后提交 pull request

如果你想联系我,可以发邮件到zoneherobrine@gmail.com

或者恰个 QQ:2212540603(好友请求中附带相关信息,比如说是gxci问题~)

Ending

Reference