diff --git a/ExternalLibraries/MvCameraControl.Net.XML b/ExternalLibraries/MvCameraControl.Net.XML new file mode 100644 index 0000000..e79ec14 --- /dev/null +++ b/ExternalLibraries/MvCameraControl.Net.XML @@ -0,0 +1,18755 @@ + + + + MvCameraControl.Net + + + + + 排序方式 + + + + + 按序列号排序 + + + + + 按用户自定义名字排序 + + + + + 按当前IP地址排序(升序) + + + + + 按当前IP地址排序(降序) + + + + + 动作命令信息 + + + + + 设备密钥 + + + + + 组键 + + + + + 组掩码 + + + + + 只有设置成1时Action Time才有效,非1时无效 + + + + + 预定的时间,和主频有关 + + + + + 广播包地址 + + + + + 等待ACK的超时时间,如果为0表示不需要ACK + + + + + 只有设置成1时指定的网卡IP才有效,非1时无效 + + + + + 指定的网卡IP + + + + + 动作命令返回信息 + + + + + 设备IP + + + + + 状态码 + + + + + 设备枚举类,支持枚举GigE Vision、USB3 Vision相机,及采集卡上的相机(GigE Vision、CameraLink、CoaXPress、XoFlink) + + + + + 枚举设备 + + 设备接口类型 + 设备列表 + 成功,返回MV_OK;失败,返回错误码 + + 设备接口类型为MV_GIGE_DEVICE时,枚举所有GigE设备,包含虚拟GigE设备和采集卡上的GigE设备。 + 设备接口类型为MV_USB_DEVICE时,枚举所有USB设备,包含虚拟USB设备。 + + 设备列表的内存是在SDK内部分配的,调用该接口时会进行设备列表内存的释放和申请,避免多线程枚举操作。 + + 枚举到设备后,通过创建设备实例。 + + + + + 枚举设备,支持枚举指定厂商的设备 + + 设备接口类型 + 厂商名称 + 设备列表 + 成功,返回MV_OK;失败,返回错误码 + + 设备接口类型为MV_GIGE_DEVICE时,枚举所有GigE设备,包含虚拟GigE设备和采集卡上的GigE设备。 + 设备接口类型为MV_USB_DEVICE时,枚举所有USB设备,包含虚拟USB设备。 + + 设备列表的内存是在SDK内部分配的,调用该接口时会进行设备列表内存的释放和申请,避免多线程枚举操作。 + + 枚举到设备后,通过创建设备实例。 + + + + + 枚举设备, 可指定排序方式枚举、根据厂商名字过滤 + + 设备接口类型 + 排序方式 + 厂商名称 + 设备列表 + 成功,返回MV_OK;失败,返回错误码 + + 设备接口类型为MV_GIGE_DEVICE时,仅枚举网络上的网口相机,不包含虚拟GigE相机和采集卡上的相机 + 设备接口类型为MV_USB_DEVICE时,枚举普通USB设备,不包含虚拟USB设备。 + + 设备列表的内存是在SDK内部分配的,调用该接口时会进行设备列表内存的释放和申请,避免多线程枚举操作。 + + 枚举到设备后,通过创建设备实例。 + + + + + 判断设备是否可达 + + 设备信息 + 访问权限 + 可达,返回true;不可达,返回false + + + + 设置GigE设备枚举超时时间,范围 1-UINT_MAX(包括1,不包括UINT_MAX) + + 超时时间 + 成功,返回MV_OK;失败,返回错误码 + + + + 设置枚举命令的回复包类型 + + 回复包类型(默认广播),0-单播,1-广播 + 成功,返回MV_OK;失败,返回错误码 + + + + 获取主机串口列表 + + 串口列表 + 成功,返回MV_OK;失败,返回错误码 + + + + 设置在指定的串口上枚举设备 + + 串口列表 + 成功,返回MV_OK;失败,返回错误码 + + + + 发出动作命令 + + 动作命令信息 + 动作命令返回信息列表 + 成功,返回MV_OK;失败,返回错误码 + + 仅GigEVision相机支持 + + + + + 获取GigE设备组播状态 + + 设备信息 + 组播状态(true:组播状态;false:非组播) + 成功,返回MV_OK;失败,返回错误码 + + 仅GigEVision相机支持 + + + + + 将用户的设备信息格式转换为SDK的内部设备信息格式 + + + + + + + + 设备工厂类,用于创建设备实例 + + + + + 创建设备对象 + + 设备信息 + 成功-返回设备实例,失败-抛出异常 + + + + 通过设备IP地址创建设备,适用于GigE设备,不包含虚拟设备与采集卡设备 + + 设备IP地址 + 网口IP地址 + 成功-返回设备实例,失败-抛出异常 + + + + 通过GenTL设备信息创建设备句柄 + + 设备信息 + 成功-返回设备实例,失败-抛出异常 + + + + 设备接口类型 + + + + GigE Vision 设备 + + + USB3 Vision 设备 + + + Camera Link 设备(串口) + + + 虚拟 GigE Vision 设备 + + + 虚拟 USB3 Vision 设备 + + + 网口采集卡下GigE Vision设备 + + + Camera Link 设备 + + + CoaXPress设备 + + + XoFLink设备 + + + + 设备基本信息 + + + + + 设备接口类型 + + + + + 制造商信息 + + + + + 设备型号 + + + + + 设备版本 + + + + + 设备序列号 + + + + + 用户自定义名称 + + + + + 设备类型信息,7 - 0 bit: 预留,15 - 8 bit:产品子类别,23 - 16 bit:产品类型,31 - 24bit:产品线(如: 0x01 标准产品;0x02 3D产品;0x03 智能ID产品) + + + + + GigE相机信息 + + + + + GigE Vision协议主要版本 + + + + + GigE Vision协议次要版本 + + + + + 高MAC地址 + + + + + 低MAC地址 + + + + + IP配置选项 + + + + + 当前IP配置 + + + + + 当前IP地址 + + + + + 当前子网掩码 + + + + + 当前网关 + + + + + 网口IP地址 + + + + + 是否虚拟相机 + + + + + 是否采集卡上的相机 + + + + + USB相机信息 + + + + + 控制输入端点 + + + + + 控制输出端点 + + + + + 流端点 + + + + + 事件端点 + + + + + 供应商ID号 + + + + + 产品ID号 + + + + + 设备索引号 + + + + + 设备GUID号 + + + + + 家族名字 + + + + + 支持的USB协议 + + + + + 设备地址 + + + + + 是否虚拟相机 + + + + + Camera Link串口设备信息 + + + + + 端口号 + + + + + 名称 + + + + + CoaXPress设备信息 + + + + + 相机ID + + + + + 采集卡ID + + + + + Camera Link设备信息 + + + + + 相机ID + + + + + 采集卡ID + + + + + XoFLink设备信息 + + + + + 相机ID + + + + + 采集卡ID + + + + + 设备访问权限,只支持GigE设备 + + + + + 独占权限,其他APP只允许读CCP寄存器 + + + + + 可以从5模式下抢占权限,然后以独占权限打开 + + + + + 控制权限,其他APP允许读所有寄存器 + + + + + 可以从5模式下抢占权限,然后以控制权限打开 + + + + + 以可被抢占的控制权限打开 + + + + + 可以从5模式下抢占权限,然后以可被抢占的控制权限打开 + + + + + 读模式打开设备,适用于控制权限下 + + + + + IP配置类型 + + + + + 静态IP + + + + + DHCP自动获取IP + + + + + LLA(Link-local address),链路本地地址 + + + + + GigE设备传输模式 + + + + + 驱动模式 + + + + + Socket模式 + + + + + GigE设备网络传输的相关信息 + + + + + 已接收数据大小 [统计StartGrabbing和StopGrabbing之间的数据量] + + + + + 丢失的包数量 + + + + + 丢帧数量 + + + + + 已接收的帧数 + + + + + 请求重发包数 + + + + + 重发包数 + + + + + USB传输信息 + + + + + 已接收数据大小 [Open和Close之间] + + + + + 已收到的帧数 + + + + + 错误帧数 + + + + + GigE传输类型 + + + + + 单播 + + + + + 组播 + + + + + 局域网内广播 + + + + + 子网内广播 + + + + + 从相机获取 + + + + + 用户自定义应用端接收图像数据Port号 + + + + + 设置了单播,但本实例不接收图像数据 + + + + + 组播模式,但本实例不接收图像数据 + + + + + 设备异常类型 + + + + + 设备断开连接 + + + + + 设备异常消息 + + + + + 消息类型 + + + + + 提供设备通用的属性和接口 + + + + + 打开设备,默认以独占权限打开 + + 成功,返回MV_OK;失败,返回错误码 + + + + 以指定访问权限打开设备,只支持GigE设备 + + 访问权限 + 切换访问权限时的密钥 + 成功,返回MV_OK;失败,返回错误码 + + 目前设备暂不支持MV_ACCESS_ExclusiveWithSwitch、MV_ACCESS_ControlWithSwitch、MV_ACCESS_ControlSwitchEnable、MV_ACCESS_ControlSwitchEnableWithKey这四种抢占模式。 + + + + + 关闭设备 + + 成功,返回MV_OK;失败,返回错误码 + + + + 设备本地升级 + + 升级文件路径 + 成功,返回MV_OK;失败,返回错误码 + + 通过该接口可以将升级固件文件发送给设备进行升级。该接口需要等待升级固件文件成功传给设备端之后再返回,响应时间可能较长。 + + + + + 获取升级进度 + + 升级进度 + 成功,返回MV_OK;失败,返回错误码 + + + + 开启设备指定事件 + + 事件名称 + 成功,返回MV_OK;失败,返回错误码 + + + + 关闭设备指定事件 + + 事件名称 + 成功,返回MV_OK;失败,返回错误码 + + + + 设备异常事件 + + + + + 判断设备是否处于连接状态 + + + + + 获取设备对应的图像采集对象 + + + + + 获取设备对应的事件采集对象 + + + + + 获取设备信息 + + + + + 获取设备对应的参数配置对象 + + + + + 获取格式转换对象 + + + + + 获取用于图像处理的对象 + + + + + 获取用于图像保存的对象 + + + + + 获取用于解码图像的的对象 + + + + + 获取录像对象 + + + + + 获取图像和图形渲染对象 + + + + + GigE设备专用接口 + + + + + 获取最佳包大小 + + 最佳包大小 + 成功,返回MV_OK;失败,返回错误码 + + + + 强制设备IP + + IP地址 + 子网掩码 + 默认网关 + 成功,返回MV_OK;失败,返回错误码 + + 强制设置设备网络参数(包括IP、子网掩码、默认网关),强制设置之后将需要重新创建设备句柄,仅GigE设备支持。 + 如果设备为DHCP的状态,调用该接口强制设置设备网络参数之后设备将会重启。 + + + + + 配置IP方式 + + IP配置类型 + 成功,返回MV_OK;失败,返回错误码 + + + + 设置传输模式,默认为Driver模式 + + 网络传输模式 + 成功,返回MV_OK;失败,返回错误码 + + + + 获取网络传输信息 + + 网络传输信息 + 成功,返回MV_OK;失败,返回错误码 + + + + 设置GVSP取流超时时间 + + 超时时间(MS),默认300ms,范围:>10ms + 成功,返回MV_OK;失败,返回错误码 + + + + 获取GVSP取流超时时间 + + 超时时间(MS) + 成功,返回MV_OK;失败,返回错误码 + + + + 设置GVCP命令超时时间 + + 超时时间(MS),默认500ms,范围:0-10000ms + 成功,返回MV_OK;失败,返回错误码 + + + + 获取GVCP命令超时时间 + + 超时时间(MS) + 成功,返回MV_OK;失败,返回错误码 + + + + 设置重传GVCP命令次数 + + 重传次数,范围:0-100 + 成功,返回MV_OK;失败,返回错误码 + + + + 获取重传GVCP命令次数 + + 重传次数 + 成功,返回MV_OK;失败,返回错误码 + + + + 设置是否打开重发包,及重发包参数 + + 是否支持重发包 + 最大重发比 + 重发超时时间 + 成功,返回MV_OK;失败,返回错误码 + + + + 设置重传命令最大尝试次数 + + 重传命令最大尝试次数,默认值20 + 成功,返回MV_OK;失败,返回错误码 + + 该接口必须在调用开启重传包功能之后调用,否则失败且返回MV_E_CALLORDER。 + + + + + 获取重传命令最大尝试次数 + + 传命令最大尝试次数 + 成功,返回MV_OK;失败,返回错误码 + + 该接口必须在调用开启重传包功能之后调用,否则失败且返回MV_E_CALLORDER。 + + + + + 设置同一重传包多次请求之间的时间间隔 + + 同一重传包多次请求之间的时间间隔,默认10ms + 成功,返回MV_OK;失败,返回错误码 + + + + 获取同一重传包多次请求之间的时间间隔 + + 同一重传包多次请求之间的时间间隔(以毫秒为单位) + 成功,返回MV_OK;失败,返回错误码 + + + + 设置传输模式,可以为单播模式、组播模式等 + + 传输模式 + 组播地址,组播模式下有意义 + 组播端口,组播模式下有意义 + 成功,返回MV_OK;失败,返回错误码 + + + + USB设备专用接口 + + + + + 设置U3V的传输包大小 + + 传输的包大小,单位:Byte,默认为1M,范围:Windows[0x400, 0x400000], Linux[0x400, 0x200000] + 成功,返回MV_OK;失败,返回错误码 + + 增加传输包大小可以适当降低取流时的CPU占用率。但不同的PC和不同USB扩展卡存在不同的兼容性,如果该参数设置过大可能会出现取不到图像的风险。 + + + + + 获取U3V的传输包大小 + + 传输的包大小, 单位:Byte + 成功,返回MV_OK;失败,返回错误码 + + + + 设置U3V的传输通道个数 + + 传输通道个数,范围:1-10 + 成功,返回MV_OK;失败,返回错误码 + + 可根据PC的性能、设备出图帧率、图像大小和内存使用率等因素对该参数进行调节。但不同的PC和不同的USB扩展卡存在不同的兼容性。 + + + + + 获取U3V的传输通道个数 + + 传输通道个数 + 成功,返回MV_OK;失败,返回错误码 + + 该接口用于获取当前的U3V异步取流节点个数,2000W设备的MONO8默认为3个,YUV为默认2个,RGB为默认1个,其它情况默认8个节点。 + + + + + 设置U3V的事件缓存节点个数 + + 事件缓存节点个数,范围:1-64 + 成功,返回MV_OK;失败,返回错误码 + + 该接口用于设置当前的U3V事件缓存节点个数,默认情况下为5个。 + + + + + 设置U3V相机同步读写超时时间,范围为1000~UINT,默认1000 ms + + 同步读写超时时间 + 成功,返回MV_OK;失败,返回错误码 + + + + 获取U3V相机同步读写超时时间 + + 同步读写超时时间 + 成功,返回MV_OK;失败,返回错误码 + + + + 获取主机从USB设备接收的数据统计信息,如已接收字节数、帧数 + + USB传输信息 + 成功,返回MV_OK;失败,返回错误码 + + + + Camera Link波特率 + + + + + 9600 + + + + + 19200 + + + + + 38400 + + + + + 57600 + + + + + 115200 + + + + + 230400 + + + + + 460800 + + + + + 921600 + + + + + 最大值 + + + + + Camera Link串口设备专用接口 + + + + + 设置设备波特率 + + 波特率 + 成功,返回MV_OK;失败,返回错误码 + + + + 获取设备波特率 + + 波特率 + 成功,返回MV_OK;失败,返回错误码 + + + + 获取设备与主机间连接支持的波特率 + + 所支持波特率的或运算结果,单个波特率参考 + 成功,返回MV_OK;失败,返回错误码 + + + + 设置串口操作等待时长 + + 串口操作的等待时长,单位为ms + 成功,返回MV_OK;失败,返回错误码 + + + + 错误码定义 + + + + 成功,无错误 + + + 错误或无效的句柄 + + + 不支持的功能 + + + 缓存已满 + + + 函数调用顺序错误 + + + 错误的参数 + + + 资源申请失败 + + + 无数据 + + + 前置条件有误,或运行环境已发生变化 + + + 版本不匹配 + + + 传入的内存空间不足 + + + 异常图像,可能是丢包导致图像不完整 + + + 动态导入DLL失败 + + + 没有可输出的缓存 + + + 加密错误 + + + 未知的错误 + + + 通用错误 + + + 参数非法 + + + 值超出范围 + + + 属性 + + + 运行环境有问题 + + + 逻辑错误 + + + 节点访问条件有误 + + + 超时 + + + 转换异常 + + + GenICam未知错误 + + + 命令不被设备支持 + + + 访问的目标地址不存在 + + + 目标地址不可写 + + + 设备无访问权限 + + + 设备忙,或网络断开 + + + 网络包数据错误 + + + 网络相关错误 + + + 设备IP冲突 + + + 读usb出错 + + + 写usb出错 + + + 设备异常 + + + GenICam相关错误 + + + 带宽不足 + + + 驱动不匹配或者未装驱动 + + + USB未知的错误 + + + 升级固件不匹配 + + + 升级固件语言不匹配 + + + 升级冲突(设备已经在升级了再次请求升级即返回此错误) + + + 升级时设备内部出现错误 + + + 升级时未知错误 + + + 处理正确 + + + 不确定类型错误 + + + 能力集中存在无效参数 + + + 内存地址为空 + + + 内存对齐不满足要求 + + + 内存空间大小不够 + + + 内存空间大小不满足对齐要求 + + + 内存地址不满足对齐要求 + + + 图像格式不正确或者不支持 + + + 图像宽高不正确或者超出范围 + + + 图像宽高与step参数不匹配 + + + 图像数据存储地址为空 + + + 设置或者获取参数类型不正确 + + + 设置或者获取参数的输入、输出结构体大小不正确 + + + 处理类型不正确 + + + 处理时输入、输出参数大小不正确 + + + 子处理类型不正确 + + + 子处理时输入、输出参数大小不正确 + + + index参数不正确 + + + value参数不正确或者超出范围 + + + param_num参数不正确 + + + 函数参数指针为空 + + + 超过限定的最大内存 + + + 回调函数出错 + + + 加密错误 + + + 算法库使用期限错误 + + + 参数范围不正确 + + + 数据大小不正确 + + + 数据step不正确 + + + cpu不支持优化代码中的指令集 + + + 警告 + + + 算法库超时 + + + 算法版本号出错 + + + 模型版本号出错 + + + GPU内存分配错误 + + + 文件不存在 + + + 字符串为空 + + + 图像解码器错误 + + + 打开文件错误 + + + 文件读取错误 + + + 文件写错误 + + + 文件读取大小错误 + + + 文件类型错误 + + + 模型类型错误 + + + 分配内存错误 + + + 线程绑核失败 + + + 噪声特性图像格式错误 + + + 噪声特性类型错误 + + + 噪声特性个数错误 + + + 噪声特性增益个数错误 + + + 噪声曲线增益值输入错误 + + + 噪声曲线柱数错误 + + + 噪声估计初始化增益设置错误 + + + 噪声估计未初始化 + + + 颜色空间模式错误 + + + 图像ROI个数错误 + + + 图像ROI原点错误 + + + 图像ROI大小错误 + + + 输入的相机增益不存在(增益个数已达上限) + + + 输入的相机增益不在范围内 + + + 输入的噪声特性内存大小错误 + + + + 设备event信息 + + + + + Event名称 + + + + + EventID + + + + + 流通道序号 + + + + + 帧号 (暂无固件支持) + + + + + 时间戳 + + + + + Event数据长度 (暂无固件支持) + + + + + Event数据,内部会进行一次拷贝,将非托管内存拷贝到托管内存 (暂无固件支持) + + + + + 设备事件 + + + + + 事件信息 + + + + + 提供设备事件订阅相关接口 + + + + + 订阅事件 + + 事件名称 + 成功,返回MV_OK;失败,返回错误码 + + + + 取消事件订阅 + + 事件名称 + 成功,返回MV_OK;失败,返回错误码 + + + + 订阅所有事件 + + 成功,返回MV_OK;失败,返回错误码 + + + + 取消订阅所有事件 + + 成功,返回MV_OK;失败,返回错误码 + + + + 设备事件 + + + + + 通过GenTL枚举到的接口信息 + + + + + GenTL接口ID + + + + + 传输层类型 + + + + + 显示名称 + + + + + GenTL的cti文件索引 + + + + + 通过GenTL枚举到的设备信息 + + + + + GenTL接口ID + + + + + 设备ID + + + + + 供应商名字 + + + + + 型号名字 + + + + + 传输层类型 + + + + + 设备显示名称 + + + + + 用户自定义名字 + + + + + 序列号 + + + + + 设备版本号 + + + + + GenTL的cti文件索引 + + + + + 提供GenTL相关接口 + + + + + 通过GenTL枚举Interfaces + + GenTL的cti文件路径 + Interfaces列表 + 成功,返回MV_OK;失败,返回错误码 + + + + 通过GenTL Interface枚举设备 + + Interface信息 + 设备列表 + 成功,返回MV_OK;失败,返回错误码 + + 枚举到设备后,通过创建设备实例。 + + + + + 卸载cti库 + + 枚举卡时加载的cti文件路径 + 成功,返回MV_OK;失败,返回错误码 + 卸载前需要保证通过该cti枚举出的相机已全部关闭,否则报错前置条件错误。 + + + + 提供获取图像缓存、图像属性的接口 + + + + + 转换为Bitmap + + Bitmap对象,失败返回null + + + + 图像数据指针(非托管内存) + + + + + 图像数据,内部会进行一次拷贝,将非托管内存拷贝到托管内存 + + + + + 图像宽度 + + + + + 图像高度 + + + + + 像素格式 + + + + + 图像大小 + + + + + 图像解码 + + + + + 无损解码 + + 输入图像及帧信息 + 输出图像及帧信息。图像使用完之后需调用Dispose方法及时释放内存,防止内存快速上涨。 + 成功,返回MV_OK;失败,返回错误码 + + 将从相机中取到的无损压缩码流解码成裸数据,同时支持解析当前相机实时图像的水印信息(如果输入的无损码流不是当前相机或者不是实时取流的,则水印解析可能异常)。 + 若解码失败,请检查以下情况:(1)需要CPU支持 SSE AVX指令集(2)若当前帧异常(丢包等),可能导致解码异常(3)相机出图异常,即使不丢包也会异常。 + + + + + 图像渲染模式 + + + + + 默认模式,Windows:GDI,Linux:OpenGL + + + + + Direct3D,只支持Windows + + + + + OPENGL,只支持Windows + + + + + 颜色 + + + + + 构造函数 + + 红色,根据像素颜色的相对深度,范围为[0.0 , 1.0],代表着[0, 255]的颜色深度 + 绿色,根据像素颜色的相对深度,范围为[0.0 , 1.0],代表着[0, 255]的颜色深度 + 蓝色,根据像素颜色的相对深度,范围为[0.0 , 1.0],代表着[0, 255]的颜色深度 + 透明度,根据像素颜色的相对透明度,范围为[0.0 , 1.0] (此参数功能暂不支持) + + + + 红色,根据像素颜色的相对深度,范围为[0.0 , 1.0],代表着[0, 255]的颜色深度 + + + + + 绿色,根据像素颜色的相对深度,范围为[0.0 , 1.0],代表着[0, 255]的颜色深度 + + + + + 蓝色,根据像素颜色的相对深度,范围为[0.0 , 1.0],代表着[0, 255]的颜色深度 + + + + + 透明度,根据像素颜色的相对透明度,范围为[0.0 , 1.0] (此参数功能暂不支持) + + + + + 矩形 + + + + + 构造函数 + + 矩形上边缘距离图像上边缘的距离,根据图像的相对位置,范围为[0.0 , 1.0] + 矩形下边缘距离图像下边缘的距离,根据图像的相对位置,范围为[0.0 , 1.0] + 矩形左边缘距离图像左边缘的距离,根据图像的相对位置,范围为[0.0 , 1.0] + 矩形右边缘距离图像右边缘的距离,根据图像的相对位置,范围为[0.0 , 1.0] + + + + 矩形上边缘距离图像上边缘的距离,根据图像的相对位置,范围为[0.0 , 1.0] + + + + + 矩形下边缘距离图像下边缘的距离,根据图像的相对位置,范围为[0.0 , 1.0] + + + + + 矩形左边缘距离图像左边缘的距离,根据图像的相对位置,范围为[0.0 , 1.0] + + + + + 矩形右边缘距离图像右边缘的距离,根据图像的相对位置,范围为[0.0 , 1.0] + + + + + 点 + + + + + 构造函数 + + 该点距离图像左边缘距离,根据图像的相对位置,范围为[0.0 , 1.0] + 该点距离图像上边缘距离,根据图像的相对位置,范围为[0.0 , 1.0] + + + + 该点距离图像左边缘距离,根据图像的相对位置,范围为[0.0 , 1.0] + + + + + 该点距离图像上边缘距离,根据图像的相对位置,范围为[0.0 , 1.0] + + + + + 圆形 + + + + + 构造函数 + + 圆心 + 宽向半径,根据图像的相对位置[0, 1.0],半径与圆心的位置有关,需保证画出的圆在显示框范围之内,否则报错 + 高向半径,根据图像的相对位置[0, 1.0],半径与圆心的位置有关,需保证画出的圆在显示框范围之内,否则报错 + + + + 圆心 + + + + + 宽向半径,根据图像的相对位置[0, 1.0],半径与圆心的位置有关,需保证画出的圆在显示框范围之内,否则报错 + + + + + 高向半径,根据图像的相对位置[0, 1.0],半径与圆心的位置有关,需保证画出的圆在显示框范围之内,否则报错 + + + + + 线条 + + + + + 线条的起始点坐标 + + + + + 线条的终点坐标 + + + + + 提供图像渲染、图形绘制接口 + + + + + 显示一帧图像 + + 窗口句柄 + 图像信息 + 渲染模式 + 成功,返回MV_OK;失败,返回错误码 + + 渲染方式可选择GDI或D3D,默认选择为GDI模式。渲染引擎仅在客户端不连接相机的情况下可以进行设置。GDI模式对电脑的显卡性能没有要求,适用于所有电脑。 D3D模式适用于安装显卡驱动且显卡内存大于1GB的电脑,该模式下客户端预览的图像效果会优于GDI模式下的图像效果。 + 渲染模式为RenderMode.OPENGL时支持4G以上超大图渲染 + + + + + 显示一帧图像 + + 窗口句柄 + 图像数据指针 + 图像数据长度 + 图像宽 + 图像高 + 像素格式 + 渲染模式 + 成功,返回MV_OK;失败,返回错误码 + + 渲染方式可选择GDI或D3D,默认选择为GDI模式。渲染引擎仅在客户端不连接相机的情况下可以进行设置。GDI模式对电脑的显卡性能没有要求,适用于所有电脑。 D3D模式适用于安装显卡驱动且显卡内存大于1GB的电脑,该模式下客户端预览的图像效果会优于GDI模式下的图像效果。 + + + + + 在图像上绘制矩形 + + 矩形框 + 线条颜色 + 线条宽度,只能是1或2 + 成功,返回MV_OK;失败,返回错误码 + + + + 在图像上绘制圆形 + + 圆形信息 + 线条颜色 + 线条宽度,只能是1或2 + 成功,返回MV_OK;失败,返回错误码 + + + + 在图像上绘制线条 + + 线条信息 + 线条颜色 + 线条宽度,只能是1或2 + 成功,返回MV_OK;失败,返回错误码 + + + + 图像格式 + + + + + BMP图像格式 + + + + + JPEG图像格式 + + + + + PNG图像格式 + + + + + TIFF图像格式 + + + + + 图像格式信息 + + + + + 图像格式 + + + + + JPEG编码质量(50-99],其他格式无效 + + + + + 提供保存图像数据到文件的接口,支持BMP、JPG、PNG、TIFF格式图像 + + + + + 保存图像到文件,支持BMP、JPG、PNG、TIFF格式图像 + + 文件路径 + 图像数据 + 图像格式信息 + 图像插值方法 + 成功,返回MV_OK;失败,返回错误码 + + + + 保存图像到缓存,支持BMP、JPG格式图像 + + 图像缓存 + 转换后的图像数据长度 + 图像数据 + 图像格式信息 + 图像插值方法 + 成功,返回MV_OK;失败,返回错误码 + + + + 提供采集卡属性和接口 + + + + + 打开采集卡 + + 成功,返回MV_OK;失败,返回错误码 + + + + 关闭采集卡 + + 成功,返回MV_OK;失败,返回错误码 + + + + 设备本地升级 + + 升级文件路径 + 成功,返回MV_OK;失败,返回错误码 + + 通过该接口可以将升级固件文件发送给设备进行升级。该接口需要等待升级固件文件成功传给设备端之后再返回,响应时间可能较长。 + + + + + 获取升级进度 + + 升级进度 + 成功,返回MV_OK;失败,返回错误码 + + + + 枚举采集卡上的相机 + + 相机列表 + 成功,返回MV_OK;失败,返回错误码 + + + + 开启设备指定事件 + + 事件名称 + 成功,返回MV_OK;失败,返回错误码 + + + + 关闭设备指定事件 + + 事件名称 + 成功,返回MV_OK;失败,返回错误码 + + + + 获取采集卡对应的参数配置对象 + + + + + 获取采集卡对应的事件采集对象 + + + + + 提供枚举采集卡接口 + + + + + 枚举采集卡 + + 采集卡接口类型 + 采集卡列表 + 成功,返回MV_OK;失败,返回错误码 + + 枚举到采集卡后,通过创建采集卡实例。 + + + + + 采集卡工厂类,创建采集卡实例 + + + + + 根据采集卡信息创建采集卡实例 + + 采集卡信息 + 成功-返回采集卡实例,失败-抛出异常 + + + + 根据采集卡ID创建采集卡实例 + + 采集卡ID + 成功-返回采集卡实例,失败-抛出异常 + + + + 采集卡接口类型定义 + + + + + GigE Vision采集卡 + + + + + Camera Link采集卡 + + + + + CoaXPress采集卡 + + + + + XoFLink采集卡 + + + + + 采集卡信息 + + + + + 采集卡接口类型, + + + + + 采集卡的PCIE插槽信息 + + + + + 采集卡ID + + + + + 显示名称 + + + + + 序列号 + + + + + 型号 + + + + + 厂商 + + + + + 版本号 + + + + + 自定义名称 + + + + + 图像旋转角度 + + + + + 90度 + + + + + 180度 + + + + + 270度 + + + + + 图像翻转类型 + + + + + 垂直翻转 + + + + + 水平翻转 + + + + + 图像重构方式 + + + + + 源图像按行拆分成多张图像 + + + + + 图像拼接方式 + + + + + 垂直方向拼接 + + + + + 提供图像处理相关接口,比如旋转、翻转、对比度、饱和度等 + + + + + 图像旋转 + + 输入图像 + 输出图像。图像使用完之后需调用Dispose方法及时释放内存,防止内存快速上涨。 + 旋转角度 + 成功,返回MV_OK;失败,返回错误码 + + 该接口只支持MONO8/RGB24/BGR24格式数据的90/180/270度旋转。 + + + + + 图像翻转 + + 输入图像 + 输出图像。图像使用完之后需调用Dispose方法及时释放内存,防止内存快速上涨。 + 翻转类型 + 成功,返回MV_OK;失败,返回错误码 + + 该接口只支持MONO8/RGB24/BGR24格式数据的垂直和水平翻转。 + + + + + 图像对比度调节 + + 输入图像 + 输出图像。图像使用完之后需调用Dispose方法及时释放内存,防止内存快速上涨。 + 对比度值,[1, 10000] + 成功,返回MV_OK;失败,返回错误码 + + + + 重构图像(用于分时曝光功能) + + 输入图像 + 曝光个数(1-8] + 图像重构方式 + 输出图像列表。图像使用完之后需调用Dispose方法及时释放内存,防止内存快速上涨。 + 成功,返回MV_OK;失败,返回错误码 + + + + 重构图像(用于分时曝光功能,图像拆分后再拼接) + + 输入图像 + 曝光个数(1-8] + 图像重构的方式 + 图像拼接的方式 + 输出图像。图像使用完之后需调用Dispose方法及时释放内存,防止内存快速上涨。 + 成功,返回MV_OK;失败,返回错误码 + + + + 内存块 + + + + + 内存池 + + + + + 关闭内存池,清空内部缓存 + + + + + 归还内存块到内存池 + + 内存块 + 是否手动释放 + + + + 更新内存块列表,释放过期内存。方法内部不加锁 + + + + + + 更新内存块的空闲时间并删除过期内存块。 方法内部不加锁 + + + + + + + 打印统计信息 + + + + + 初始化属性值,在构造函数中使用 + + + + + 创建相机句柄 + + + 错误码 + + + + 判断设备是否处于连接状态 + + + + + 获取设备对应的图像采集对象 + + + + + 获取设备信息 + + + + + 获取设备对应的参数配置对象 + + + + + 设置设备波特率 + + 波特率 + 成功,返回MV_OK;失败,返回错误码 + + + + 获取设备波特率 + + 波特率 + 成功,返回MV_OK;失败,返回错误码 + + + + 获取设备与主机间连接支持的波特率 + + 所支持波特率的或运算结果,单个波特率参考 + 成功,返回MV_OK;失败,返回错误码 + + + + 设置串口操作等待时长 + + 串口操作的等待时长,单位为ms + 成功,返回MV_OK;失败,返回错误码 + + + + Chunk数据 + + + + + Chunk数据指针(非托管内存) + + + + + Chunk数据,内部会进行一次拷贝,将非托管内存拷贝到托管内存 + + + + + ChunkID + + + + + Chunk数据长度 + + + + + 图像帧里面的chunk信息 + + + + + 通过ChunkID获取对应的ChunkData + + + + + + + 创建设备对象 + + + + + 创建设备对象 + + 设备信息 + 设备实例 + + + + 通过设备IP地址创建设备,适用于GigE设备 + + 设备IP地址 + 网口IP地址 + 设备实例 + + + + 通过GenTL设备信息创建设备句柄 + + 设备信息 + 成功,返回MV_OK;失败,返回错误码 + + + + 采集卡类型 + + + + + 未知采集卡 + + + + + 虚拟采集卡 + + + + + 自研采集卡 + + + + + gige设备信息实现 + + + + + 占用设备的主机ip + + + + + 组播ip + + + + + 组播port + + + + + 设备接口类型 + + + + + 制造商信息 + + + + + 制造商特殊信息 + + + + + 设备型号 + + + + + 设备版本 + + + + + 设备序列号 + + + + + 用户自定义名称 + + + + + 设备类型信息,7 - 0 bit: 预留,15 - 8 bit:产品子类别,23 - 16 bit:产品类型,31 - 24bit:产品线(如: 0x01 标准产品;0x02 3D产品;0x03 智能ID产品) + + + + + GigE Vision协议主要版本 + + + + + GigE Vision协议次要版本 + + + + + 高MAC地址 + + + + + 低MAC地址 + + + + + IP配置选项 + + + + + 当前IP配置 + + + + + 当前IP地址 + + + + + 当前子网掩码 + + + + + 当前网关 + + + + + 网口IP地址 + + + + + 是否虚拟相机 + + + + + 是否采集卡上的相机 + + + + + USB设备信息类 + + + + + 设备接口类型 + + + + + 制造商信息 + + + + + 设备型号 + + + + + 设备版本 + + + + + 设备序列号 + + + + + 用户自定义名称 + + + + + 设备类型信息,7 - 0 bit: 预留,15 - 8 bit:产品子类别,23 - 16 bit:产品类型,31 - 24bit:产品线(如: 0x01 标准产品;0x02 3D产品;0x03 智能ID产品) + + + + + 控制输入端点 + + + + + 控制输出端点 + + + + + 流端点 + + + + + 事件端点 + + + + + 供应商ID号 + + + + + 产品ID号 + + + + + 设备索引号 + + + + + 设备GUID号 + + + + + 家族名字 + + + + + 供应商名称 + + + + + 支持的USB协议 + + + + + 设备地址 + + + + + 是否虚拟相机 + + + + + Camera Link串口设备信息 + + + + + 设备接口类型 + + + + + 制造商信息 + + + + + 设备型号 + + + + + 设备版本 + + + + + 设备序列号 + + + + + 用户自定义名称 + + + + + 设备类型信息,7 - 0 bit: 预留,15 - 8 bit:产品子类别,23 - 16 bit:产品类型,31 - 24bit:产品线(如: 0x01 标准产品;0x02 3D产品;0x03 智能ID产品) + + + + + 端口号 + + + + + 名称 + + + + + CoaXPress设备信息 + + + + + 设备接口类型 + + + + + 制造商信息 + + + + + 设备型号 + + + + + 设备版本 + + + + + 设备序列号 + + + + + 用户自定义名称 + + + + + 设备类型信息,7 - 0 bit: 预留,15 - 8 bit:产品子类别,23 - 16 bit:产品类型,31 - 24bit:产品线(如: 0x01 标准产品;0x02 3D产品;0x03 智能ID产品) + + + + + 相机ID + + + + + 采集卡ID + + + + + Camera Link设备信息 + + + + + 设备接口类型 + + + + + 制造商信息 + + + + + 设备型号 + + + + + 设备版本 + + + + + 设备序列号 + + + + + 用户自定义名称 + + + + + 设备类型信息,7 - 0 bit: 预留,15 - 8 bit:产品子类别,23 - 16 bit:产品类型,31 - 24bit:产品线(如: 0x01 标准产品;0x02 3D产品;0x03 智能ID产品) + + + + + 相机ID + + + + + 采集卡ID + + + + + XoFLink设备信息 + + + + + 设备接口类型 + + + + + 制造商信息 + + + + + 设备型号 + + + + + 设备版本 + + + + + 设备序列号 + + + + + 用户自定义名称 + + + + + 设备类型信息,7 - 0 bit: 预留,15 - 8 bit:产品子类别,23 - 16 bit:产品类型,31 - 24bit:产品线(如: 0x01 标准产品;0x02 3D产品;0x03 智能ID产品) + + + + + 相机ID + + + + + 采集卡ID + + + + + Event名称 + + + + + EventID + + + + + 流通道序号 + + + + + 帧号 + + + + + 时间戳 + + + + + Event数据长度 + + + + + Event数据 + + + + + GenTL接口ID + + + + + 传输层类型 + + + + + 显示名称 + + + + + GenTL的cti文件索引 + + + + + GenTL接口ID + + + + + 设备ID + + + + + 供应商名字 + + + + + 型号名字 + + + + + 传输层类型 + + + + + 设备显示名称 + + + + + 用户自定义名字 + + + + + 序列号 + + + + + 设备版本号 + + + + + GenTL的cti文件索引 + + + + + 获取最佳包大小 + + + + + + + 强制配置ip + + + + + + + + + 设置ip方式 + + + + + + + 设置传输模式,默认为Driver模式 + + 网络传输模式 + 成功,返回MV_OK;失败,返回错误码 + + + + 获取网络传输信息 + + 网络传输信息 + 成功,返回MV_OK;失败,返回错误码 + + + + 设置GVSP取流超时时间 + + 超时时间(MS),默认300ms,范围:>10ms + 成功,返回MV_OK;失败,返回错误码 + + + + 获取GVSP取流超时时间 + + 超时时间(MS) + 成功,返回MV_OK;失败,返回错误码 + + + + 设置GVCP命令超时时间 + + 超时时间(MS),默认500ms,范围:0-10000ms + 成功,返回MV_OK;失败,返回错误码 + + + + 获取GVCP命令超时时间 + + 超时时间(MS) + 成功,返回MV_OK;失败,返回错误码 + + + + 设置重传GVCP命令次数 + + 重传次数,范围:0-100 + 成功,返回MV_OK;失败,返回错误码 + + + + 获取重传GVCP命令次数 + + 重传次数 + 成功,返回MV_OK;失败,返回错误码 + + + + 设置是否打开重发包,及重发包参数 + + 是否支持重发包 + 最大重发比 + 重发超时时间 + 成功,返回MV_OK;失败,返回错误码 + + + + 设置重传命令最大尝试次数 + + 重传命令最大尝试次数,默认值20 + 成功,返回MV_OK;失败,返回错误码 + + 该接口必须在调用开启重传包功能之后调用,否则失败且返回MV_E_CALLORDER。 + + + + + 获取重传命令最大尝试次数 + + 传命令最大尝试次数 + 成功,返回MV_OK;失败,返回错误码 + + 该接口必须在调用开启重传包功能之后调用,否则失败且返回MV_E_CALLORDER。 + + + + + 设置同一重传包多次请求之间的时间间隔 + + 同一重传包多次请求之间的时间间隔,默认10ms + 成功,返回MV_OK;失败,返回错误码 + + + + 获取同一重传包多次请求之间的时间间隔 + + + 成功,返回MV_OK;失败,返回错误码 + + + + 设置传输模式,可以为单播模式、组播模式等 + + + 组播地址,组播模式下有意义 + 组播端口,组播模式下有意义 + 成功,返回MV_OK;失败,返回错误码 + + + + 解码功能实现类 + + + + + 判断像素是否为Mono格式 + + + + + + + 判断图像格式是否为彩色格式 + + + + + + + 获取图像大小 + + 图像宽度 + 图像高度 + 像素格式 + + + + + 内存拷贝 + + 目标缓存 + 源缓存 + 拷贝大小 + + + + 将FrameOut转成MvCCDll的帧结构体 + + + + + + + + 将Byte数组转为String,使用UTF-8编码,并去掉结尾的'\0' + + + + + + + String字符串拷贝到byte[] + + + + + + + 判断字符数组是否为utf-8 + + 字符数组 + + + + + 将枚举出来的设备信息转化为输出格式 + + + + + + + + 打开采集卡 + + 成功,返回MV_OK;失败,返回错误码 + + + + 关闭采集卡 + + 成功,返回MV_OK;失败,返回错误码 + + + + 设备本地升级 + + 升级文件路径 + 成功,返回MV_OK;失败,返回错误码 + + 通过该接口可以将升级固件文件发送给设备进行升级。该接口需要等待升级固件文件成功传给设备端之后再返回,响应时间可能较长。 + + + + + 获取升级进度 + + 升级进度 + 成功,返回MV_OK;失败,返回错误码 + + + + 枚举采集卡上的相机 + + 相机列表 + 成功,返回MV_OK;失败,返回错误码 + + + + 获取采集卡对应的参数配置对象 + + + + + 采集卡接口类型, + + + + + 采集卡的PCIE插槽信息 + + + + + 采集卡ID + + + + + 显示名称 + + + + + 序列号 + + + + + 型号 + + + + + 厂商 + + + + + 版本号 + + + + + 自定义名称 + + + + + MyCamera + + + + ch GigE Vision采集卡 |en GigE Vision interface + + + ch Camera Link采集卡 |en Camera Link interface + + + ch CoaXPress采集卡 |en CoaXPress interface + + + ch XoFLink采集卡 |en XoFLink interface + + + Unknown Device Type, Reserved + + + GigE Device + + + 1394-a/b Device + + + USB3.0 Device + + + CameraLink Device + + + Virtual GigE Device + + + Virtual USB Device + + + GenTL GigE Device + + + GenTL CML Device + + + GenTL CXP Device + + + GenTL XOF Device + + + + ch:信息结构体的最大缓存 | en: Max buffer size of information structs + + + + + 最大的相机数量 + + + + + ch:最大Interface数量 | en:Max num of interfaces + + + + + ch:最大GenTL设备数量 | en:Max num of GenTL devices + + + + + XML节点描述最大长度 + + + + + XML节点最大长度 + + + + + XML节点最大数量 + + + + + XML节点显示名最大数量 + + + + + 枚举类型最大的EnumEntry个数 + + + + + string类型节点值的最大长度 + + + + + 最大父节点数 + + + + + 最大节点描述长度 + + + + + 参数导出允许配置的最大节点个数 + + + + + 参数导入时节点导入失败的最大错误个数 + + + + + 设备断开连接 + + + + + SDK与驱动版本不匹配 + + + + + 相机Event事件名称最大长度 + + + + 最大枚举条目对应的符号长度 + + + 分时曝光时最多将源图像拆分的个数 + + + 最大支持的串口数量 + + + + ch:最大支持的采集卡数量 | en:The maximum number of Frame Grabber interface supported + + + + + ch 静态 |en Static + + + + + ch DHCP |en DHCP + + + + + ch LLA |en LLA + + + + + 9600 + + + + + 19200 + + + + + 38400 + + + + + 57600 + + + + + 115200 + + + + + 230400 + + + + + 460800 + + + + + 921600 + + + + + ch 最大值 |en Auto Max + + + + + ch 网络流量和丢包信息 |en Network traffic and packet loss information + + + + + ch host接收到来自U3V设备的字节总数 |en The total number of bytes host received from U3V device + + + + + ch独占权限,其他APP只允许读CCP寄存器 |en Exclusive authority, other APP is only allowed to read the CCP register + + + + + ch 可以从5模式下抢占权限,然后以独占权限打开 |en You can seize the authority from the 5 mode, and then open with exclusive authority + + + + + ch 控制权限,其他APP允许读所有寄存器 |en Control authority, allows other APP reading all registers + + + + + ch 可以从5的模式下抢占权限,然后以控制权限打开 |en You can seize the authority from the 5 mode, and then open with control authority + + + + + ch 以可被抢占的控制权限打开 |en Open with seized control authority + + + + + ch 可以从5的模式下抢占权限,然后以可被抢占的控制权限打开 |en You can seize the authority from the 5 mode, and then open with seized control authority + + + + + ch 读模式打开设备,适用于控制权限下 |en Open with read mode and is available under control authority + + + + 成功,无错误 + + + 错误或无效的句柄 + + + 不支持的功能 + + + 缓存已满 + + + 函数调用顺序错误 + + + 错误的参数 + + + 资源申请失败 + + + 无数据 + + + 前置条件有误,或运行环境已发生变化 + + + 版本不匹配 + + + 传入的内存空间不足 + + + 异常图像,可能是丢包导致图像不完整 + + + 动态导入DLL失败 + + + 没有可输出的缓存 + + + 加密错误 + + + 打开文件出现错误 + + + 未知的错误 + + + 通用错误 + + + 参数非法 + + + 值超出范围 + + + 属性 + + + 运行环境有问题 + + + 逻辑错误 + + + 节点访问条件有误 + + + 超时 + + + 转换异常 + + + GenICam未知错误 + + + 命令不被设备支持 + + + 访问的目标地址不存在 + + + 目标地址不可写 + + + 设备无访问权限 + + + 设备忙,或网络断开 + + + 网络包数据错误 + + + 网络相关错误 + + + 设备IP冲突 + + + 读usb出错 + + + 写usb出错 + + + 设备异常 + + + GenICam相关错误 + + + 带宽不足 + + + 驱动不匹配或者未装驱动 + + + USB未知的错误 + + + 升级固件不匹配 + + + 升级固件语言不匹配 + + + 升级冲突(设备已经在升级了再次请求升级即返回此错误) + + + 升级时设备内部出现错误 + + + 升级时未知错误 + + + 处理正确 + + + 不确定类型错误 + + + 能力集中存在无效参数 + + + 内存地址为空 + + + 内存对齐不满足要求 + + + 内存空间大小不够 + + + 内存空间大小不满足对齐要求 + + + 内存地址不满足对齐要求 + + + 图像格式不正确或者不支持 + + + 图像宽高不正确或者超出范围 + + + 图像宽高与step参数不匹配 + + + 图像数据存储地址为空 + + + 设置或者获取参数类型不正确 + + + 设置或者获取参数的输入、输出结构体大小不正确 + + + 处理类型不正确 + + + 处理时输入、输出参数大小不正确 + + + 子处理类型不正确 + + + 子处理时输入、输出参数大小不正确 + + + index参数不正确 + + + value参数不正确或者超出范围 + + + param_num参数不正确 + + + 函数参数指针为空 + + + 超过限定的最大内存 + + + 回调函数出错 + + + 加密错误 + + + 算法库使用期限错误 + + + 参数范围不正确 + + + 数据大小不正确 + + + 数据step不正确 + + + cpu不支持优化代码中的指令集 + + + 警告 + + + 算法库超时 + + + 算法版本号出错 + + + 模型版本号出错 + + + GPU内存分配错误 + + + 文件不存在 + + + 字符串为空 + + + 图像解码器错误 + + + 打开文件错误 + + + 文件读取错误 + + + 文件写错误 + + + 文件读取大小错误 + + + 文件类型错误 + + + 模型类型错误 + + + 分配内存错误 + + + 线程绑核失败 + + + 噪声特性图像格式错误 + + + 噪声特性类型错误 + + + 噪声特性个数错误 + + + 噪声特性增益个数错误 + + + 噪声曲线增益值输入错误 + + + 噪声曲线柱数错误 + + + 噪声估计初始化增益设置错误 + + + 噪声估计未初始化 + + + 颜色空间模式错误 + + + 图像ROI个数错误 + + + 图像ROI原点错误 + + + 图像ROI大小错误 + + + 输入的相机增益不存在(增益个数已达上限) + + + 输入的相机增益不在范围内 + + + 输入的噪声特性内存大小错误 + + + + Byte array to struct + + Byte array + Struct type + Struct object + + + + Struct to Byte array + + Struct object + Byte + Bytes + + + + 判断字符数组是否为utf-8 + + 字符数组 + + + + + Write Error Message + + Message + ErrorNum + + + + 获取枚举类型节点(支持的枚举个数扩展到256) V4.4.1新增 + + + + + Grab callback + + Image data + Frame info + User defined variable + + + + Grab callback + + Image data + Frame info + User defined variable + + + + Xml Update callback(Interfaces not recommended) + + Node type + Current node feature structure + Nodes list + User defined variable + + + + Exception callback + + Msg type + User defined variable + + + + Event callback (Interfaces not recommended) + + User defined ID + User defined variable + + + + Event callback + + Event Info + User defined variable + + + + Stream Exception callback + + Msg type + User defined variable + + + + ch:采集卡信息列表 | en: Interface Information List + + + + + ch:在线设备数量 | en:Online Interface Number + + + + + ch:支持最多64个设备 | en:Support up to 64 Interfaces + + + + + ch:采集卡信息 | en: Interface information + + + + + ch: 采集卡类型; 低16位有效: bits(0~2)代表功能, bits(3~7)代表相机, bits(8-15)代表总线| en: Interface type + + + + + ch: 采集卡的PCIE插槽信息 | en: PCIe slot information of interface + + + + + ch: 采集卡ID | en: Interface ID + + + + + ch 显示名称 | en: Display name + + + + + ch 序列号 |en: Serial number + + + + + ch 型号 | en: model name + + + + + ch: 厂商 |en: manufacturer name + + + + + ch: 版本号| en: device version + + + + + ch: 自定义名称 |en: user defined name + + + + + ch 保留字段 | en Reserved + + + + + 排序方式 + + + + + 按序列号排序 + + + + + 按用户自定义名字排序 + + + + + 按当前IP地址排序(升序) + + + + + 按当前IP地址排序(降序) + + + + + ch: GigE设备信息 | en: GigE device information + + + + + IP 配置选项 + + + + + IP configuration:bit31-static bit30-dhcp bit29-lla + + + + + curtent ip + + + + + curtent subnet mask + + + + + current gateway + + + + + 制造商名 + + + + + 型号名 + + + + + 设备版本信息 + + + + + 制造商特殊信息 + + + + + 序列号 + + + + + 用户自定义名 + + + + + 网口IP地址 + + + + + 预留 + + + + + ch: GigE设备信息 | en: GigE device information + + + + + IP 配置选项 + + + + + IP configuration:bit31-static bit30-dhcp bit29-lla + + + + + curtent ip + + + + + curtent subnet mask + + + + + current gateway + + + + + 制造商名 + + + + + 型号名 + + + + + 设备版本信息 + + + + + 制造商特殊信息 + + + + + 序列号 + + + + + 用户自定义名 + + + + + 网口IP地址 + + + + + 预留 + + + + + ch:USB3 设备信息 | en:USB3 device information + + + + + 控制输入端点 + + + + + 控制输出端点 + + + + + 流端点 + + + + + 事件端点 + + + + + 供应商ID号 + + + + + 产品ID号 + + + + + 设备索引号 + + + + + 设备GUID号 + + + + + 供应商名字 + + + + + 型号名字 + + + + + 家族名字 + + + + + 设备版本号 + + + + + 制造商名字 + + + + + 序列号 + + + + + 用户自定义名字 + + + + + 支持的USB协议 + + + + + 设备地址 + + + + + 保留字节 + + + + + ch:USB3 设备信息 | en:USB3 device information + + + + + 控制输入端点 + + + + + 控制输出端点 + + + + + 流端点 + + + + + 事件端点 + + + + + 供应商ID号 + + + + + 产品ID号 + + + + + 设备索引号 + + + + + 设备GUID号 + + + + + 供应商名字 + + + + + 型号名字 + + + + + 家族名字 + + + + + 设备版本号 + + + + + 制造商名字 + + + + + 序列号 + + + + + 用户自定义名字 + + + + + 支持的USB协议 + + + + + 设备地址 + + + + + 保留字节 + + + + + ch:CamLink设备信息 | en:CamLink device information + + + + + 端口号ID + + + + + 模型名 + + + + + 家族名 + + + + + 设备版本信息 + + + + + 制造商名字 + + + + + 序列号 + + + + + 保留字节 + + + + + ch:采集卡Camera Link相机信息 | en:Camera Link device information on frame grabber + + + + + ch 采集卡ID |en Interface ID of Frame Grabber + + + + + ch 供应商名字 |en Vendor name + + + + + ch 型号名字 |en Model name + + + + + ch 厂商信息 |en Manufacturer information + + + + + ch 相机版本 |en Device version + + + + + ch 序列号 |en Serial number + + + + + ch 用户自定义名字 |en User defined name + + + + + ch 相机ID |en Device ID + + + + + ch 保留字段 |en Reserved + + + + + ch:CoaXPress相机信息 | en:CoaXPress device information + + + + + ch 采集卡ID |en Interface ID of Frame Grabber + + + + + ch 供应商名字 |en Vendor name + + + + + ch 型号名字 |en Model name + + + + + ch 厂商信息 |en Manufacturer information + + + + + ch 相机版本 |en Device version + + + + + ch 序列号 |en Serial number + + + + + ch 用户自定义名字 |en User defined name + + + + + ch 相机ID |en Device ID + + + + + ch 保留字段 |en Reserved + + + + + ch:XoFLink相机信息 | en:XoFLink device information + + + + + ch 采集卡ID |en Interface ID of Frame Grabber + + + + + ch 供应商名字 |en Vendor name + + + + + ch 型号名字 |en Model name + + + + + ch 厂商信息 |en Manufacturer information + + + + + ch 相机版本 |en Device version + + + + + ch 序列号 |en Serial number + + + + + ch 用户自定义名字 |en User defined name + + + + + ch 相机ID |en Device ID + + + + + ch 保留字段 |en Reserved + + + + + ch:设备信息 | en:Device information + + + + + 主版本号 + + + + + 次版本号 + + + + + MAC高地址 + + + + + MAC低地址 + + + + + 设备传输层协议类型,e.g. MV_GIGE_DEVICE + + + + + ch 设备类型信息 | en Device Type Info + + + + + 保留字节 + + + + + 设备类型 + + + + + 构造函数 + + 输入任意数,因为不接受无参构造函数 + + + + ch:特定类型的设备信息 | en:Special devcie information + + + + + GigE + + + + + Camera Link + + + + + Usb + + + + + CML + + + + + CXP + + + + + XOF + + + + + 相机列表 + + + + + 在线设备数量 + + + + + 支持最多256个设备 + + + + + ch:通过GenTL枚举到的Interface信息 | en:Interface Information with GenTL + + + + + GenTL接口ID + + + + + 传输层类型 + + + + + 设备显示名称 + + + + + GenTL的cti文件索引 + + + + + 保留字节 + + + + + ch:通过GenTL枚举到的设备信息列表 | en:Interface Information List with GenTL + + + + + ch:在线设备数量 | en:Online Interface Number + + + + + ch:支持最多256个设备 | en:Support up to 256 Interfaces + + + + + ch:通过GenTL枚举到的设备信息 | en:Device Information discovered by with GenTL + + + + + 采集卡ID + + + + + 设备ID + + + + + 供应商名字 + + + + + 模型名 + + + + + 传输类型 + + + + + 显示名 + + + + + 用户自定义名 + + + + + 序列号 + + + + + 设备版本信息 + + + + + cti文件序号 + + + + + 保留字节 + + + + + ch:通过GenTL枚举到的设备信息 | en:Device Information discovered by with GenTL + + + + + 采集卡ID + + + + + 设备ID + + + + + 供应商名字 + + + + + 模型名 + + + + + 传输类型 + + + + + 显示名 + + + + + 用户自定义名 + + + + + 序列号 + + + + + 设备版本信息 + + + + + cti文件序号 + + + + + 保留字节 + + + + + ch:GenTL设备列表 | en:GenTL devices list + + + + + 在线设备数量 + + + + + 支持最多256个设备 + + + + + Net Trans Info + + + + + 已接收数据大小 [统计StartGrabbing和StopGrabbing之间的数据量] + + + + + 丢帧数量 + + + + + 接收帧数 + + + + + 请求重发包数 + + + + + 重发包数 + + + + + Frame Out Info + + + + + 图像宽 + + + + + 图像高 + + + + + 像素格式 + + + + + 帧号 + + + + + 时间戳高32位 + + + + + 时间戳低32位 + + + + + 保留,8字节对齐 + + + + + 主机生成的时间戳 + + + + + 帧数据大小 + + + + + 丢包数量 + + + + + 保留字节 + + + + + Chunk数据信息 + + + + + Chunk数据 + + + + + ChunkID + + + + + Chunk大小 + + + + + 保留字节 + + + + + Frame Out Info Ex + + + + + 图像宽 + + + + + 图像高 + + + + + 像素格式 + + + + + 帧号 + + + + + 时间戳高32位 + + + + + 时间戳低32位 + + + + + 保留,8字节对齐 + + + + + 主机生成的时间戳 + + + + + Frame大小 + + + + + 秒数 + + + + + 周期数 + + + + + 周期偏移量 + + + + + 增益 + + + + + 曝光时间 + + + + + 平均亮度 + + + + + Red + + + + + Green + + + + + Blue + + + + + 帧计数器 + + + + + 触发计数 + + + + + 输入 + + + + + 输出 + + + + + 水平偏移量 + + + + + 垂直偏移量 + + + + + Chunk宽度 + + + + + Chunk高度 + + + + + 丢包数 + + + + + 为解析的Chunk数量 + + + + + 为解析的Chunk列表 + + + + + 图像宽扩展 + + + + + 图像高扩展 + + + + + 帧长度扩展 + + + + + 保留字节 + + + + + 为解析的Chunk列表 + + + + + 为解析的Chunk内容 + + + + + 对齐结构体,无实际用途 + + + + + 输出帧信息 + + + + + 帧数据地址 + + + + + 帧信息 + + + + + 保留字节 + + + + + 取流策略 + + + + + 从旧到新一帧一帧的获取图像(默认为该策略) + + + + + 获取列表中最新的一帧图像(同时清除列表中的其余图像) + + + + + 获取列表中最新的图像,个数由OutputQueueSize决定,范围为1-ImageNodeNum,设置成1等同于LatestImagesOnly,设置成ImageNodeNum等同于OneByOne + + + + + 等待下一帧图像 + + + + + 显示帧信息 + + + + + 显示窗口的句柄 + + + + + 显示的帧数据 + + + + + 显示的帧数据大小 + + + + + 图像宽 + + + + + 图像高 + + + + + 像素格式 + + + + + 保留字节 + + + + + 显示帧信息 + + + + + 图像宽 + + + + + 图像高 + + + + + 像素格式 + + + + + 显示的帧数据 + + + + + 显示的帧数据大小 + + + + + 图像渲染方式 0-默认模式(Windows GDI/Linux OPENGL), 1-D3D模式(Windows有效) + + + + + 保留字节 + + + + + 图像信息 + + + + + 图像宽 + + + + + 图像高 + + + + + 像素格式 + + + + + 图像缓存 + + + + + 图像缓存大小 + + + + + 图像长度 + + + + + 保留字节 + + + + + ch:保存3D数据格式 | en:Save 3D file + + + + + 未定义数据格式 + + + + + PLY数据格式 + + + + + CSV数据格式 + + + + + OBJ数据格式 + + + + + 保存的点阵参数 + + + + + [IN] 每一行点的数量 + + + + + [IN] 行数 + + + + + [IN] 输入数据的像素格式 + + + + + [IN] 输入数据缓存 + + + + + [IN] 输入数据大小 + + + + + [OUT] 输出像素数据缓存 + + + + + [IN] 提供的输出缓冲区大小(nLinePntNum * nLineNum * (16*3 + 4) + 2048) + + + + + [OUT] 输出像素数据缓存长度 + + + + + 保存的点阵文件类型 + + + + + 保留字节 + + + + + 保存的图像格式 + + + + + 未定义类型 + + + + + Bmp图像格式 + + + + + Jpeg图像格式 + + + + + Png图像格式 + + + + + Tif图像格式 + + + + + 保存的图像参数 + + + + + [IN] 输入数据缓存 + + + + + [IN] 输入数据大小 + + + + + [IN] 输入数据的像素格式 + + + + + [IN] 图像宽 + + + + + [IN] 图像高 + + + + + [OUT] 输出图片缓存 + + + + + [OUT] 输出图片大小 + + + + + [IN] 提供的输出缓冲区大小 + + + + + [IN] 输出图片格式 + + + + + 保存的图像参数 + + + + + [IN] 输入数据缓存 + + + + + [IN] 输入数据大小 + + + + + [IN] 输入数据的像素格式 + + + + + [IN] 图像宽 + + + + + [IN] 图像高 + + + + + [OUT] 输出图片缓存 + + + + + [OUT] 输出图片大小 + + + + + [IN] 提供的输出缓冲区大小 + + + + + [IN] 输出图片格式 + + + + + [IN] 编码质量, (50-99] + + + + + [IN] Bayer的插值方法 0-快速 1-均衡 2-最优(如果传入其它值则默认为最优) + + + + + 保留字节 + + + + + 保存的图像信息扩展 + + + + + [IN] 输入数据缓存 + + + + + [IN] 输入数据大小 + + + + + [IN] 输入数据的像素格式 + + + + + [IN] 图像宽 + + + + + [IN] 图像高 + + + + + [OUT] 输出图片缓存 + + + + + [OUT] 输出图片大小 + + + + + [IN] 提供的输出缓冲区大小 + + + + + [IN] 输出图片格式 + + + + + [IN] 编码质量, (50-99] + + + + + [IN] Bayer的插值方法 0-快速 1-均衡 2-最优(如果传入其它值则默认为最优) + + + + + 保留字节 + + + + + 保存图像到文件的参数 + + + + + [IN] 输入数据的像素格式 + + + + + [IN] 输入数据缓存 + + + + + [IN] 输入数据大小 + + + + + [IN] 图像宽 + + + + + [IN] 图像高 + + + + + [IN] 输入图片格式 + + + + + [IN] 编码质量, (0-100] + + + + + [IN] 输入文件路径 + + + + + [IN] Bayer的插值方法 0-快速 1-均衡 2-最优(如果传入其它值则默认为最优) + + + + + 保留字节 + + + + + 保存图像到文件信息扩展 + + + + + [IN] 图像宽 + + + + + [IN] 图像高 + + + + + [IN] 输入数据的像素格式 + + + + + [IN] 输入数据缓存 + + + + + [IN] 输入数据大小 + + + + + [IN] 输入图片格式 + + + + + [IN] 输入文件路径 + + + + + [IN] 编码质量, (0-100] + + + + + [IN] Bayer的插值方法 0-快速 1-均衡 2-最优(如果传入其它值则默认为最优) + + + + + 保留字节 + + + + + 保存图片所需参数 + + + + + [IN] 输入图片格式 + + + + + [IN] 编码质量, (0-100] + + + + + [IN] Bayer的插值方法 0-快速 1-均衡 2-最优(如果传入其它值则默认为最优) + + + + + 保留字节 + + + + + 旋转角度 + + + + + 旋转90度 + + + + + 旋转180度 + + + + + 旋转270度 + + + + + 旋转图像参数 + + + + + [IN] 像素格式(仅支持Mono8/RGB24/BGR24) + + + + + [IN][OUT] 图像宽 + + + + + [IN][OUT] 图像高 + + + + + [IN] 输入数据缓存 + + + + + [IN] 输入数据大小 + + + + + [OUT] 输出图片缓存 + + + + + [OUT] 输出图片大小 + + + + + [IN] 提供的输出缓冲区大小 + + + + + [IN] 旋转角度 + + + + + 保留字节 + + + + + 图像翻转类型 + + + + + 垂直方向翻转 + + + + + 水平方向翻转 + + + + + 翻转图像参数 + + + + + [IN] 像素格式(仅支持Mono8/RGB24/BGR24) + + + + + [IN] 图像宽 + + + + + [IN] 图像高 + + + + + [IN] 输入数据缓存 + + + + + [IN] 输入数据大小 + + + + + [OUT] 输出图片缓存 + + + + + [OUT] 输出图片大小 + + + + + [IN] 提供的输出缓冲区大小 + + + + + [IN] 翻转类型 + + + + + 保留字节 + + + + + 像素转换参数 + + + + + [IN] 图像宽 + + + + + [IN] 图像高 + + + + + [IN] 源像素格式 + + + + + [IN] 输入数据缓存 + + + + + [IN] 输入数据大小 + + + + + [IN] 目标像素格式 + + + + + [OUT] 输出数据缓存 + + + + + [OUT] 输出数据大小 + + + + + [IN] 提供的输出缓冲区大小 + + + + + 保留字节 + + + + + 图像像素转换信息扩展 + + + + + [IN] 图像宽 + + + + + [IN] 图像高 + + + + + [IN] 源像素格式 + + + + + [IN] 输入数据缓存 + + + + + [IN] 输入数据大小 + + + + + [IN] 目标像素格式 + + + + + [OUT] 输出数据缓存 + + + + + [OUT] 输出数据大小 + + + + + [IN] 提供的输出缓冲区大小 + + + + + 保留字节 + + + + + Gamma类型 + + + + + 不启用 + + + + + GAMMA值 + + + + + GAMMA曲线,8位需要的长度:256*sizeof(unsigned char) + 10位需要的长度:1024*sizeof(unsigned short) + 12位需要的长度:4096*sizeof(unsigned short) + 16位需要的长度:65536*sizeof(unsigned short) + + + + + 线性RGB转非线性RGB + + + + + 非线性RGB转线性RGB + + + + + Gamma参数 + + + + + [IN] Gamma类型 + + + + + [IN] Gamma值 + + + + + [IN] Gamma曲线缓存 + + + + + [IN] Gamma曲线长度 + + + + + 保留字节 + + + + + CCM参数 + + + + + [IN] 是否启用CCM + + + + + [IN] CCM矩阵(-8192~8192) + + + + + 保留字节 + + + + + CCM参数 + + + + + [IN] 是否启用CCM + + + + + [IN] 量化3x3矩阵 + + + + + [IN] 量化系数(2的整数幂) + + + + + 保留字节 + + + + + CLUT参数 + + + + + [IN] 是否启用CLUT + + + + + [IN] 量化系数(2的整数幂) + + + + + [IN] CLUT大小,建议值17 + + + + + [OUT] 量化CLUT + + + + + [IN] 量化CLUT缓存大小(nCLUTSize*nCLUTSize*nCLUTSize*sizeof(int)*3) + + + + + 保留字节 + + + + + 对比度调节参数 + + + + + [IN] 图像宽度(最小8) + + + + + [IN] 图像高度(最小8) + + + + + [IN] 输入图像缓存 + + + + + [IN] 输入图像缓存长度 + + + + + [IN] 输入的像素格式 + + + + + [OUT] 输出像素数据缓存 + + + + + [IN] 提供的输出缓冲区大小 + + + + + [OUT] 输出像素数据缓存长度 + + + + + [IN] 对比度值,范围:[1, 10000] + + + + + 保留字节 + + + + + 锐化参数 + + + + + [IN] 图像宽度(最小8) + + + + + [IN] 图像高度(最小8) + + + + + [IN] 输入图像缓存 + + + + + [IN] 输入图像缓存长度 + + + + + [IN] 输入的像素格式 + + + + + [OUT] 输出像素数据缓存 + + + + + [IN] 提供的输出缓冲区大小 + + + + + [OUT] 输出像素数据缓存长度 + + + + + [IN] 锐度调节强度,范围:[0, 500] + + + + + [IN] 锐度调节半径(半径越大,耗时越长),范围:[1, 21] + + + + + [IN] 锐度调节阈值,范围:[0, 255] + + + + + 保留字节 + + + + + 色彩校正参数(包括CCM和CLUT) + + + + + [IN] 图像宽度 + + + + + [IN] 图像高度 + + + + + [IN] 输入图像缓存 + + + + + [IN] 输入图像缓存长度 + + + + + [IN] 输入的像素格式 + + + + + [OUT] 输出像素数据缓存 + + + + + [IN] 提供的输出缓冲区大小 + + + + + [OUT] 输出像素数据缓存长度 + + + + + [IN] 输入有效图像位数,8 or 10 or 12 or 16 + + + + + [IN] 输入Gamma信息 + + + + + [IN] 输入CCM信息 + + + + + [IN] 输入CLUT信息 + + + + + 保留字节 + + + + + 矩形ROI参数 + + + + + [IN] 矩形左上角X轴坐标 + + + + + [IN] 矩形左上角Y轴坐标 + + + + + [IN] 矩形宽度 + + + + + [IN] 矩形高度 + + + + + 噪声估计参数 + + + + + [IN] 图像宽度 + + + + + [IN] 图像高度 + + + + + [IN] 输入的像素格式 + + + + + [IN] 输入图像缓存 + + + + + [IN] 输入图像缓存长度 + + + + + [IN] 图像ROI + + + + + [IN] ROI个数 + + + + + [IN] 噪声阈值[0-4095] + + + + + [OUT] 输出噪声特性 + + + + + [IN] 提供的输出缓冲区大小 + + + + + [OUT] 输出噪声特性长度 + + + + + 保留字节 + + + + + 空域降噪参数 + + + + + [IN] 图像宽度 + + + + + [IN] 图像高度 + + + + + [IN] 输入的像素格式 + + + + + [IN] 输入图像缓存 + + + + + [IN] 输入图像缓存长度 + + + + + [OUT] 输出降噪后的数据 + + + + + [IN] 提供的输出缓冲区大小 + + + + + [OUT] 输出降噪后的数据长度 + + + + + [IN] 输入噪声特性 + + + + + [IN] 输入噪声特性长度 + + + + + [IN] 降噪强度(0-100) + + + + + [IN] 锐化强度(0-32) + + + + + [IN] 噪声校正系数(0-1280) + + + + + [IN] 亮度校正系数(1-2000) + + + + + [IN] 色调校正系数(1-2000) + + + + + [IN] 亮度降噪强度(0-100) + + + + + [IN] 色调降噪强度(0-100) + + + + + [IN] 锐化强度(1-1000) + + + + + 保留字节 + + + + + LSC标定参数 + + + + + [IN] 图像宽度(16~65536) + + + + + [IN] 图像高度(16~65536) + + + + + [IN] 输入的像素格式 + + + + + [IN] 输入图像缓存 + + + + + [IN] 输入图像缓存长度 + + + + + [OUT] 输出标定表缓存 + + + + + [IN] 提供的标定表缓冲大小(nWidth*nHeight*sizeof(unsigned short)) + + + + + [OUT] 输出标定表缓存长度 + + + + + [IN] 宽度分块数 + + + + + [IN] 高度分块数 + + + + + [IN] 边缘填充系数,范围1~5 + + + + + [IN] 标定方式,0-中心为基准 + 1-最亮区域为基准 + 2-目标亮度 + + + + + [IN] 目标亮度(8bits,[0,255]) + (10bits,[0,1023]) + (12bits,[0,4095]) + (16bits,[0,65535]) + + + + + 保留字节 + + + + + LSC校正参数 + + + + + [IN] 图像宽度(16~65536) + + + + + [IN] 图像高度(16~65536) + + + + + [IN] 输入的像素格式 + + + + + [IN] 输入图像缓存 + + + + + [IN] 输入图像缓存长度 + + + + + [OUT] 输出像素数据缓存 + + + + + [IN] 提供的输出缓冲区大小 + + + + + [OUT] 输出像素数据缓存长度 + + + + + [IN] 输入校正表缓存 + + + + + [IN] 输入校正表缓存长度 + + + + + 保留字节 + + + + + 噪声特性类型 + + + + + 无效 + + + + + 噪声曲线 + + + + + 噪声水平 + + + + + 默认值 + + + + + 噪声基本信息 + + + + + 版本 + + + + + 噪声特性类型 + + + + + 图像格式 + + + + + 平均噪声水平 + + + + + 曲线点数 + + + + + 噪声曲线 + + + + + 亮度曲线 + + + + + 保留字节 + + + + + 噪声估计参数 + + + + + [IN] 图像宽(大于等于8) + + + + + [IN] 图像高(大于等于8) + + + + + [IN] 像素格式 + + + + + [IN] 输入数据缓存 + + + + + [IN] 输入数据大小 + + + + + [IN] 噪声阈值(0-4095) + + + + + [IN] 用于存储噪声曲线和亮度曲线(需要外部分配,缓存大小:4096 * sizeof(int) * 2) + + + + + [OUT] 降噪特性信息 + + + + + [IN] 线程数量,0表示算法库根据硬件自适应;1表示单线程(默认);大于1表示线程数目 + + + + + 保留字节 + + + + + 降噪参数 + + + + + [IN] 图像宽(大于等于8) + + + + + [IN] 图像高(大于等于8) + + + + + [IN] 像素格式 + + + + + [IN] 输入数据缓存 + + + + + [IN] 输入数据大小 + + + + + [OUT] 输出降噪后的数据 + + + + + [IN] 提供的输出缓冲区大小 + + + + + [OUT] 输出降噪后的数据长度 + + + + + [IN] 降噪特性信息(来源于噪声估计) + + + + + [IN] 降噪强度(0-100) + + + + + [IN] 锐化强度(0-32) + + + + + [IN] 噪声校正系数(0-1280) + + + + + [IN] 线程数量,0表示算法库根据硬件自适应;1表示单线程(默认);大于1表示线程数目 + + + + + 保留字节 + + + + + 帧特殊信息 + + + + + [OUT] 秒数 + + + + + [OUT] 周期数 + + + + + [OUT] 周期偏移量 + + + + + [OUT] 增益 + + + + + [OUT] 曝光时间 + + + + + [OUT] 平均亮度 + + + + + [OUT] 红色 + + + + + [OUT] 绿色 + + + + + [OUT] 蓝色 + + + + + [OUT] 总帧数 + + + + + [OUT] 触发计数 + + + + + [OUT] 输入 + + + + + [OUT] 输出 + + + + + [OUT] 水平偏移量 + + + + + [OUT] 垂直偏移量 + + + + + [OUT] 水印宽 + + + + + [OUT] 水印高 + + + + + 保留字节 + + + + + HB解码参数 + + + + + [IN] 输入数据缓存 + + + + + [IN] 输入数据大小 + + + + + [OUT] 图像宽 + + + + + [OUT] 图像高 + + + + + [OUT] 输出数据缓存 + + + + + [IN] 提供的输出缓冲区大小 + + + + + [OUT] 输出数据大小 + + + + + [OUT] 输出的像素格式 + + + + + [OUT] 水印信息 + + + + + 保留字节 + + + + + 录像格式定义 + + + + + 未定义格式 + + + + + AVI格式 + + + + + 录像参数 + + + + + [IN] 输入数据的像素格式 + + + + + [IN] 图像宽(指定目标参数时需为8的倍数) + + + + + [IN] 图像高(指定目标参数时需为8的倍数) + + + + + [IN] 帧率fps(大于1/16) + + + + + [IN] 码率kbps(128kbps-16Mbps) + + + + + [IN] 录像格式 + + + + + [IN] 录像文件存放路径 + + + + + 保留字节 + + + + + 输入帧信息 + + + + + [IN] 图像数据指针 + + + + + [IN] 图像大小 + + + + + 保留字节 + + + + + 采集模式 + + + + + 单帧模式 + + + + + 多帧模式 + + + + + 持续采集模式 + + + + + 增益模式 + + + + + 关闭 + + + + + 一次 + + + + + 连续 + + + + + 曝光模式 + + + + + Timed + + + + + TriggerWidth + + + + + 自动曝光模式 + + + + + 关闭 + + + + + 一次 + + + + + 连续 + + + + + 相机触发模式 + + + + + 关闭 + + + + + 打开 + + + + + Gamma选择器 + + + + + USER + + + + + SRGB + + + + + 自动白平衡 + + + + + 关闭自动白平衡 + + + + + 一次自动白平衡 + + + + + 连续自动白平衡 + + + + + 触发源 + + + + + LINE0 + + + + + LINE1 + + + + + LINE2 + + + + + LINE3 + + + + + COUNTER0 + + + + + SOFTWARE + + + + + FrequencyConverter + + + + + ALL MATHCH INFO + + + + + 需要输出的信息类型,e.g. MV_MATCH_TYPE_NET_DETECT + + + + + 输出的信息缓存,由调用者分配 + + + + + 信息缓存的大小 + + + + + + + + + + 已接收数据大小 [统计StartGrabbing和StopGrabbing之间的数据量] + + + + + 丢失的包数量 + + + + + 丢帧数量 + + + + + 帧数 + + + + + 请求重发包数 + + + + + 重发包数 + + + + + USB + + + + + 已接收数据大小 [统计OpenDevicce和CloseDevice之间的数据量] + + + + + 已收到的帧数 + + + + + 错误帧数 + + + + + 保留字节 + + + + + 图像的基本信息 + + + + + 宽度值 + + + + + 宽度最小值 + + + + + 宽度最大值 + + + + + Width Inc + + + + + 高度值 + + + + + 高度最小值 + + + + + 高度最大值 + + + + + Height Inc + + + + + 帧率 + + + + + 最小帧率 + + + + + 最大帧率 + + + + + 当前的像素格式 + + + + + 支持的像素格式种类 + + + + + 像素列表 + + + + + 保留字节 + + + + + 节点是否可见的权限等级 + + + + + Always visible + + + + + Visible for experts or Gurus + + + + + Visible for Gurus + + + + + Not Visible + + + + + Object is not yet initialized + + + + + 事件信息 + + + + + 事件名 + + + + + Event号 + + + + + 流通到序号 + + + + + 帧号高位 + + + + + 帧号低位 + + + + + 时间戳高位 + + + + + 时间戳低位 + + + + + Event数据 + + + + + Event数据长度 + + + + + 保留字节 + + + + + 节点错误类型 + + + + + 节点不存在 + + + + + 访问条件错误,通常是节点不可读写 + + + + + 写入越界,超出该节点支持的范围 + + + + + 校验失败,通常是写入的值与文件中的值不匹配 + + + + + 其它错误,可查阅日志 + + + + + 节点错误信息 + + + + + 节点名称 + + + + + 错误类型 + + + + + 保留字节 + + + + + 错误信息列表 + + + + + 错误个数 + + + + + 错误信息 + + + + + 保留字节 + + + + + 节点名称 + + + + + 节点名称 + + + + + 保留字节 + + + + + 节点列表 + + + + + 错误信息 + + + + + 节点个数 + + + + + 保留字节 + + + + + 文件存取 + + + + + 用户文件名 + + + + + 设备文件名 + + + + + 保留字节 + + + + + 文件存取 + + + + + 用户文件数据缓存空间 + + + + + 用户数据缓存大小 + + + + + 文件实际缓存大小 + + + + + 设备文件名 + + + + + 保留字节 + + + + + 文件存取进度 + + + + + 已完成的长度 + + + + + 总长度 + + + + + 保留字节 + + + + + GigE传输类型 + + + + + 表示单播(默认) + + + + + 表示组播 + + + + + 表示局域网内广播,暂不支持 + + + + + 表示子网内广播,暂不支持 + + + + + 表示从相机获取,暂不支持 + + + + + 表示用户自定义应用端接收图像数据Port号 + + + + + 表示设置了单播,但本实例不接收图像数据 + + + + + 表示组播模式,但本实例不接收图像数据 + + + + + 传输模式,可以为单播模式、组播模式等 + + + + + 传输模式 + + + + + 目标IP,组播模式下有意义 + + + + + 目标Port,组播模式下有意义 + + + + + 保留字节 + + + + + 动作命令信息 + + + + + 设备密钥 + + + + + 组键 + + + + + 组掩码 + + + + + 只有设置成1时Action Time才有效,非1时无效 + + + + + 预定的时间,和主频有关 + + + + + 广播包地址 + + + + + 等待ACK的超时时间,如果为0表示不需要ACK + + + + + 只有设置成1时指定的网卡IP才有效,非1时无效 + + + + + 指定的网卡IP + + + + + 保留字节 + + + + + 动作命令结果 + + + + + IP address of the device + + + + + status code returned by the device + + + + + 保留字节 + + + + + 动作命令结果列表 + + + + + 返回值个数 + + + + + 返回的结果 + + + + + 每个节点对应的接口类型 + + + + + IValue接口类型 + + + + + IBase接口类型 + + + + + IInteger接口类型 + + + + + IBoolean接口类型 + + + + + ICommand接口类型 + + + + + IFloat接口类型 + + + + + IString接口类型 + + + + + IRegister接口类型 + + + + + ICategory接口类型 + + + + + IEnumeration接口类型 + + + + + IEnumEntry接口类型 + + + + + IPort接口类型 + + + + + XML节点特点 + + + + + 节点类型 + + + + + 是否可见 + + + + + 节点描述 + + + + + 显示名称 + + + + + 节点名 + + + + + 提示 + + + + + 保留字节 + + + + + XML节点列表 + + + + + 节点个数 + + + + + 节点列表 + + + + + 整型节点值 + + + + + 当前值 + + + + + 最大值 + + + + + 最小值 + + + + + Inc + + + + + 保留字节 + + + + + 整型节点值 + + + + + 当前值 + + + + + 最大值 + + + + + 最小值 + + + + + Inc + + + + + 保留字节 + + + + + 浮点型节点值 + + + + + 当前值 + + + + + 最大值 + + + + + 最小值 + + + + + 保留字节 + + + + + 枚举型节点值 + + + + + 当前值 + + + + + 有效数据个数 + + + + + 保留字节 + + + + + 保留字节 + + + + + 枚举型节点值 + + + + + 当前值 + + + + + 有效数据个数 + + + + + 保留字节 + + + + + 保留字节 + + + + + 字符串型节点值 + + + + + 当前值 + + + + + 节点值的最大长度 + + + + + 保留字节 + + + + + 节点的读写性 + + + + + 未实现 + + + + + 不可获取 + + + + + 只写 + + + + + 只读 + + + + + 可读可写 + + + + + 未定义 + + + + + 内部用于AccessMode循环检测 + + + + + 整型节点 + + + + + 节点名 + + + + + 显示名称 + + + + + 节点描述 + + + + + 提示 + + + + + 是否可见 + + + + + 访问模式 + + + + + 是否锁定。0-否;1-是 + + + + + 当前值 + + + + + 最小值 + + + + + 最大值 + + + + + 增量 + + + + + 保留字节 + + + + + 布尔型节点 + + + + + 节点名 + + + + + 显示名称 + + + + + 节点描述 + + + + + 提示 + + + + + 是否可见 + + + + + 访问模式 + + + + + 是否锁定。0-否;1-是 + + + + + 当前值 + + + + + 保留字节 + + + + + 命令型节点 + + + + + 节点名 + + + + + 显示名称 + + + + + 节点描述 + + + + + 提示 + + + + + 是否可见 + + + + + 访问模式 + + + + + 是否锁定。0-否;1-是 + + + + + 保留字节 + + + + + 浮点型节点 + + + + + 节点名 + + + + + 显示名称 + + + + + 节点描述 + + + + + 提示 + + + + + 是否可见 + + + + + 访问模式 + + + + + 是否锁定。0-否;1-是 + + + + + 当前值 + + + + + 最小值 + + + + + 最大值 + + + + + 增量 + + + + + 保留字节 + + + + + 字符串类型节点 + + + + + 节点名 + + + + + 显示名称 + + + + + 节点描述 + + + + + 提示 + + + + + 是否可见 + + + + + 访问模式 + + + + + 是否锁定。0-否;1-是 + + + + + 当前值 + + + + + 保留字节 + + + + + 寄存器型节点 + + + + + 节点名 + + + + + 显示名称 + + + + + 节点描述 + + + + + 提示 + + + + + 是否可见 + + + + + 访问模式 + + + + + 是否锁定。0-否;1-是 + + + + + 当前值 + + + + + 保留字节 + + + + + 类别属性 + + + + + 节点描述 + + + + + 显示名称 + + + + + 节点名 + + + + + 提示 + + + + + 是否可见 + + + + + 保留字节 + + + + + EnumEntry属性节点 + + + + + 节点名 + + + + + 显示名称 + + + + + 节点描述 + + + + + 提示 + + + + + + + + + + 父节点数 + + + + + 父节点列表 + + + + + 是否可见 + + + + + 当前值 + + + + + 访问模式 + + + + + 是否锁定。0-否;1-是 + + + + + 保留字节 + + + + + 节点描述 + + + + + 节点描述 + + + + + Enumeration属性节点 + + + + + 是否可见 + + + + + 节点描述 + + + + + 显示名称 + + + + + 节点名 + + + + + 提示 + + + + + Symbolic数 + + + + + 当前Symbolic索引 + + + + + Symbolic索引 + + + + + 访问模式 + + + + + 是否锁定。0-否;1-是 + + + + + 当前值 + + + + + 保留字节 + + + + + Port属性节点 + + + + + 是否可见 + + + + + 节点描述 + + + + + 显示名称 + + + + + 节点名 + + + + + 提示 + + + + + 访问模式 + + + + + 是否锁定。0-否;1-是 + + + + + 保留字节 + + + + 辅助线颜色 + + + [0.0 , 1.0] + + + [0.0 , 1.0] + + + [0.0 , 1.0] + + + [0.0 , 1.0] + + + 预留字节 + + + 自定义点坐标 + + + [0.0 , 1.0] + + + [0.0 , 1.0] + + + 预留字节 + + + 矩形框区域信息 + + + [0.0 , 1.0] + + + [0.0 , 1.0] + + + [0.0 , 1.0] + + + [0.0 , 1.0] + + + 辅助线颜色 + + + 辅助线宽度 + + + 预留字节 + + + 圆形框区域信息 + + + 圆心信息 + + + 宽向半径,根据图像的相对位置[0, 1.0] + + + 高向半径,根据图像的相对位置[0, 1.0] + + + 辅助线颜色信息 + + + 辅助线宽度 + + + 预留字节 + + + 线条辅助线信息 + + + 线条辅助线的起始点坐标 + + + 线条辅助线的终点坐标 + + + 辅助线颜色信息 + + + 辅助线宽度 + + + 预留字节 + + + 枚举类型指定条目信息 + + + 指定值 + + + 指定值对应的符号 + + + 预留字节 + + + U3V流异常类型 + + + 异常的图像,该帧被丢弃 + + + 缓存列表溢出,清除最旧的一帧 + + + 缓存列表为空,该帧被丢弃 + + + 断流恢复 + + + 断流,恢复失败,取流被中止 + + + 设备异常,取流被中止 + + + 重构后的图像列表 + + + 源图像宽 + + + 源图像高 + + + 像素格式 + + + 输出数据缓存 + + + 输出数据长度 + + + 提供的输出缓冲区大小 + + + 预留字节 + + + + 分时曝光的图像处理方式 + + + + + 源图像按行拆分成多张图像 + + + + + 源图像两行拆分成多张图像 + + + + 重构图像参数信息 + + + 源图像宽 + + + 源图像高 + + + 像素格式 + + + 输入数据缓存 + + + 输入数据长度 + + + 曝光个数(1-8] + + + 图像重构方式 + + + + 输出数据缓存信息 + + + + 预留字节 + + + 串口信息 + + + + 串口号 + + + + + 保留字节 + + + + 串口列表 + + + + 串口数量 + + + + + 串口信息 + + + + + 保留字节 + + + + + 像素格式定义 + + + + + 未定义像素格式 + + + + + Mono1p + + + + + Mono2p + + + + + Mono4p + + + + + Mono8 + + + + + Mono8_Signed + + + + + Mono10 + + + + + Mono10_Packed + + + + + Mono12 + + + + + Mono12_Packed + + + + + Mono14 + + + + + Mono16 + + + + + BayerGR8 + + + + + BayerRG8 + + + + + BayerGB8 + + + + + BayerBG8 + + + + + BayerRBGG8 + + + + + BayerGR10 + + + + + BayerRG10 + + + + + BayerGB10 + + + + + BayerBG10 + + + + + BayerGR12 + + + + + BayerRG12 + + + + + BayerGB12 + + + + + BayerBG12 + + + + + BayerGR10_Packed + + + + + BayerRG10_Packed + + + + + BayerGB10_Packed + + + + + BayerBG10_Packed + + + + + BayerGR12_Packed + + + + BayerRG12_Packed + + + BayerGB12_Packed + + + BayerBG12_Packed + + + BayerGR16 + + + BayerRG16 + + + BayerGB16 + + + BayerBG16 + + + RGB8_Packed + + + BGR8_Packed + + + RGBA8_Packed + + + BGRA8_Packed + + + RGB10_Packed + + + BGR10_Packed + + + RGB12_Packed + + + BGR12_Packed + + + RGB16_Packed + + + BGR16_Packed/// + + + RGBA16_Packed + + + BGRA16_Packed + + + RGB10V1_Packe + + + RGB10V2_Packed + + + RGB12V1_Packed + + + RGB565_Packed + + + BGR565_Packed + + + YUV411_Packed + + + YUV422_Packed + + + YUV422_YUYV_Packed + + + YUV444_Packed + + + YCBCR8_CBYCR + + + YCBCR422_8 + + + YCBCR422_8_CBYCRY + + + YCBCR411_8_CBYYCRYY + + + YCBCR601_8_CBYCR + + + YCBCR601_422_8 + + + YCBCR601_422_8_CBYCRY + + + YCBCR601_411_8_CBYYCRYY + + + YCBCR709_8_CBYCR + + + YCBCR709_422_8 + + + YCBCR709_422_8_CBYCRY + + + YCBCR709_411_8_CBYYCRYY + + + YUV420SP_NV12 + + + YUV420SP_NV21 + + + RGB8_Planar + + + RGB10_Planar + + + RGB12_Planar + + + RGB16_Planar + + + Jpeg + + + Coord3D_ABC32f + + + Coord3D_ABC32f_Planar + + + Coord3D_AC32f + + + COORD3D_DEPTH_PLUS_MASK + + + Coord3D_ABC32 + + + Coord3D_AB32f + + + Coord3D_AB32 + + + Coord3D_AC32f_64 + + + Coord3D_AC32f_Planar + + + Coord3D_AC32 + + + Coord3D_A32f + + + Coord3D_A32 + + + Coord3D_C32f + + + Coord3D_C32 + + + Coord3D_ABC16 + + + Coord3D_C16 + + + Float32 + + + HB_Mono8 + + + HB_Mono10 + + + HB_Mono10_Packed + + + HB_Mono12 + + + HB_Mono12_Packed + + + HB_Mono16 + + + HB_BayerGR8 + + + HB_BayerRG8 + + + HB_BayerGB8 + + + HB_BayerBG8 + + + HB_BayerRBGG8 + + + HB_BayerGR10 + + + HB_BayerRG10 + + + HB_BayerGB10 + + + HB_BayerBG10 + + + HB_BayerGR12 + + + HB_BayerRG12 + + + HB_BayerGB12 + + + HB_BayerBG12 + + + HB_BayerGR10_Packed + + + HB_BayerRG10_Packed + + + HB_BayerGB10_Packed + + + HB_BayerBG10_Packed + + + HB_BayerGR12_Packed + + + HB_BayerRG12_Packed + + + HB_BayerGB12_Packed + + + HB_BayerBG12_Packed + + + HB_YUV422_Packed + + + HB_YUV422_YUYV_Packed + + + HB_RGB8_Packed + + + HB_BGR8_Packed + + + HB_RGBA8_Packed + + + HB_BGRA8_Packed + + + HB_RGB16_Packed + + + HB_BGR16_Packed + + + HB_RGBA16_Packed + + + HB_BGRA16_Packed + + + + 图像类,实现基本的图像属性和接口,实现图像克隆、ToBitmap。作为其他图像类的基类 + + + + + 根据图像宽、高和像素格式创建对象,内部申请内存 + + 图像宽度 + 图像高度 + 像素格式 + 相机句柄,用于ToBitmap时像素格式转换 + + + + 根据图像宽、高、像素格式、图像大小创建对象,内部申请内存 + + 图像宽度 + 图像高度 + 像素格式 + 图像大小 + 相机句柄,用于ToBitmap时像素格式转换 + + + + 根据图像宽、高、像素格式、图像大小创建对象,外部传入内存 + + + + + + + + + + + 用于克隆 + + + + + + 构造空对象,内部变量由由子类初始化 + + + + + 相机句柄,用于内部做格式转换等图像处理 + + + + + 非托管内存指针 + + + + + 图像数据,内部会进行一次拷贝,将非托管内存拷贝到托管内存 + + + + + 从C库获取图像地址(GetImageBuffer或者回调),包装成Image,图像数据存放在非托管内存中 + + + + + 使用传入的非托管内存创建新的对象 + + + + + + + 设备句柄 + + + + 非托管内存指针 + + + + + 图像数据,内部会进行一次拷贝,将非托管内存拷贝到托管内存 + + + + + 基于内存池的Image对象,用于图像处理相关接口 + + + + + 根据图像宽、高和像素格式创建对象,内部申请内存 + + 图像宽度 + 图像高度 + 像素格式 + 相机句柄,用于ToBitmap时像素格式转换 + 内存池 + + + + 根据图像宽、高、像素格式、图像大小创建对象,内部申请内存 + + 图像宽度 + 图像高度 + 像素格式 + 相机句柄,用于ToBitmap时像素格式转换 + 图像大小 + 内存池 + + + + 根据图像宽、高、像素格式、图像大小创建对象,外部传入内存池内存 + + + + + + + + + + + + 整型参数实现类 + + + + + 整形参数 + + + + + 当前值 + + + + + 最大值 + + + + + 最小值 + + + + + Increment + + + + + 枚举项参数 + + + + + 枚举值 + + + + + 枚举符号 + + + + + 枚举类型实现类 + + + + + 枚举类型参数 + + + + + 当前枚举项 + + + + + 支持的枚举类型个数 + + + + + 支持的枚举项列表 + + + + + float类型实现类 + + + + + 浮点型参数 + + + + + 当前值 + + + + + 最大值 + + + + + 最小值 + + + + + String类型参数实现类 + + + + + 字符串类型参数 + + + + + 当前值 + + + + + 最大长度 + + + + + 参数导入时出错的节点信息 + + + + + 参数导入导出时的节点错误信息 + + + + + 节点名称 + + + + + 错误类型 + + + + + 节点名称 + + + + + 错误信息 + + + + + 参数实现类 + + + + + 提供设备参数配置相关接口 + + + + + 清除GenICam节点缓存 + + 成功,返回MV_OK;失败,返回错误码 + + 在加载工业相机节点时需要读取GenICam配置文件,该接口可以起到清除GenICam缓存的功能。 + + + + + 获取Integer属性值 + + 属性键值,如获取宽度信息则为"Width" + 属性值 + 成功,返回MV_OK;失败,返回错误码 + + + + 设置Integer型属性值 + + 属性键值,如设置宽度信息则为"Width" + 属性值 + 成功,返回MV_OK;失败,返回错误码 + + + + 获取Enumeration属性值 + + 属性键值,如获取像素格式信息则为"PixelFormat" + 属性值 + 成功,返回MV_OK;失败,返回错误码 + + + + 设置Enumeration属性值 + + 属性键值,如设置像素格式信息则为"PixelFormat" + Enum型节点的值 + 成功,返回MV_OK;失败,返回错误码 + + + + 设置Enumeration属性 + + 属性键值,如设置像素格式信息则为"PixelFormat" + EnumEntry的名称 + 成功,返回MV_OK;失败,返回错误码 + + + + 获取Float属性值 + + 属性键值 + 属性值 + 成功,返回MV_OK;失败,返回错误码 + + + + 设置Float型属性值 + + 属性键值 + 属性值 + 成功,返回MV_OK;失败,返回错误码 + + + + 获取Boolean属性值 + + 属性键值 + 属性值 + 成功,返回MV_OK;失败,返回错误码 + + + + 设置Boolean型属性值 + + 属性键值 + 属性值 + 成功,返回MV_OK;失败,返回错误码 + + + + 获取String属性值 + + 属性键值 + 属性值 + 成功,返回MV_OK;失败,返回错误码 + + + + 设置String型属性值 + + 属性键值 + 属性值 + 成功,返回MV_OK;失败,返回错误码 + + + + 设置Command型属性值 + + 属性键值 + 成功,返回MV_OK;失败,返回错误码 + + + + 导入设备属性文件 + + 文件路径 + 成功,返回MV_OK;失败,返回错误码 + + + + 导入设备属性文件并保存错误信息列表 + + 文件路径 + 导入失败的节点信息 + 成功,返回MV_OK;失败,返回错误码 + 部分节点导入失败时也会返回MV_OK,通过nodeErrors返回导入失败的节点及错误原因 + + + + 保存设备属性到文件 + + 文件路径 + 成功,返回MV_OK;失败,返回错误码 + + + + 通过设备寄存器地址读取寄存器 + + 待读取的内存地址,该地址可以从设备的Camera.xml文件中获取,形如xxx_RegAddr的xml节点值 + 待读取的内存长度 + 存放读到的内存值(GEV设备内存值是按照大端模式存储的,其它协议设备按照小端存储) + 成功,返回MV_OK;失败,返回错误码 + + 访问设备,读取某段寄存器的数据。 + + + + + 通过设备寄存器地址写寄存器 + + 待写入的内存地址,该地址可以从设备的Camera.xml文件中获取,形如xxx_RegAddr的xml节点值 + 待写入的内存长度 + 待写入的内存值(注意GEV设备内存值要按照大端模式存储,其它协议设备按照小端存储) + 成功,返回MV_OK;失败,返回错误码 + + 访问设备,把一段数据写入某段寄存器。 + + + + + 获取设备属性树XML + + 设备xml + 成功,返回MV_OK;失败,返回错误码 + + + + 获得当前节点的访问模式 + + 节点名称 + 节点的访问模式 + 成功,返回MV_OK;失败,返回错误码 + + + + 获得当前节点的类型 + + 节点名称 + 节点类型 + 成功,返回MV_OK;失败,返回错误码 + + + + 从设备读取文件,保存为本地文件 + + 设备文件名 + 本地文件路径 + 成功,返回MV_OK;失败,返回错误码 + + + + 从设备读取文件,保存在内存 + + 设备文件名 + 缓存 + 成功,返回MV_OK;失败,返回错误码 + + + + 将文件写入设备 + + 设备文件名 + 本地文件路径 + 成功,返回MV_OK;失败,返回错误码 + + + + 将内存中的文件写入设备 + + 设备文件名 + 缓存 + 成功,返回MV_OK;失败,返回错误码 + + + + 获取文件存取的进度 + + 已完成的长度 + 总长度 + 成功,返回MV_OK;失败,返回错误码 + + + + 设备句柄 + + + + + FileAccessRead到Byte数组时的默认文件大小 + + + + + 构造函数 + + + + + + 提供像素格式转换相关接口 + + + + + 设置图像插值算法类型 + + 图像插值算法 + 成功,返回MV_OK;失败,返回错误码 + + 设置内部图像转换接口的Bayer插值算法类型参数,使用的插值算法是该接口所设定的 + + + + + 插值算法平滑使能设置 + + 平滑使能(默认关闭) + 成功,返回MV_OK;失败,返回错误码 + + 设置内部图像转换接口的Bayer插值平滑使能参数,使用的插值算法是该接口所设定的。 + + + + + 设置Bayer格式的Gamma值 + + Gamma值:0.1 ~ 4.0 + 成功,返回MV_OK;失败,返回错误码 + + 设置该值后,将Bayer8/10/12/16格式转成RGB24/48, RGBA32/64,BGR24/48,BGRA32/64时起效。 + + + + + 设置Mono8/Bayer8/10/12/16格式的Gamma值 + + 像素格式 + Gamma值:0.1 ~ 4.0 + 成功,返回MV_OK;失败,返回错误码 + + 设置Mono8的gamma值后,再调用将Mono8转成Mono8时gamma值起效。 + 设置Bayer8/10/12/16的gamma值后,将Bayer8/10/12/16格式转成RGB24/48, RGBA32/64,BGR24/48,BGRA32/64时起效。 + 该接口兼容接口,新增支持Mono8像素格式 + + + + + 设置Bayer格式的Gamma信息 + + Gamma参数 + 成功,返回MV_OK;失败,返回错误码 + + 设置该信息后,在将Bayer8/10/12/16格式转成RGB24/48, RGBA32/64,BGR24/48,BGRA32/64时起效。 + + + + + 设置Bayer格式的CCM使能和矩阵,量化系数默认1024 + + CCM参数 + 成功,返回MV_OK;失败,返回错误码 + + 开启CCM并设置CCM矩阵后,在将Bayer8/10/12/16格式转成RGB24/48, RGBA32/64,BGR24/48,BGRA32/64时起效。 + + + + + 像素格式转换 + + 输入图像 + 输出图像。图像使用完之后需调用Dispose方法及时释放内存,防止内存快速上涨。 + 目标像素格式 + 成功,返回MV_OK;失败,返回错误码 + + + + 像素格式转换 + + 输入图像 + 输出图像缓存 + 输出图像长度 + 目标像素格式 + 成功,返回MV_OK;失败,返回错误码 + + + + 获取像素格式转换所需的缓存大小 + + 目标像素格式 + 图像宽 + 图像高 + 缓存大小 + + + + 设备句柄, 用于内部做格式转换 + + + + + 构造函数,内部创建内存池,只在ToBitmap中使用 + + 设备句柄 + + + + 构造函数 + + 设备句柄 + + + + + 设置图像插值算法类型 + + 图像插值算法 + 成功,返回MV_OK;失败,返回错误码 + + + + 插值算法平滑使能设置 + + 平滑使能(默认关闭) + 成功,返回MV_OK;失败,返回错误码 + + + + 设置Bayer格式的Gamma值 + + Gamma值:0.1 ~ 4.0 + 成功,返回MV_OK;失败,返回错误码 + + + + 设置Mono8/Bayer8/10/12/16格式的Gamma值 + + 像素格式 + Gamma值:0.1 ~ 4.0 + 成功,返回MV_OK;失败,返回错误码 + + + + 设置Bayer格式的Gamma信息 + + Gamma参数 + 成功,返回MV_OK;失败,返回错误码 + + + + 设置Bayer格式的CCM使能和矩阵,量化系数默认1024 + + CCM参数 + 成功,返回MV_OK;失败,返回错误码 + + + + 将图像转换为指定格式,输出IImage图像 + + 输入图像 + 输出图像 + 目的像素格式 + 成功,返回MV_OK;失败,返回错误码 + + + + 将图像转换为指定格式,输出Byte数组 + + 输入图像 + 输出图像缓存 + 输出图像长度 + 目的像素格式 + 成功,返回MV_OK;失败,返回错误码 + + outBuffer设置为null时, outDataLen会给出结果图像需要的缓存长度 + + + + + 获取像素格式转换所需的缓存大小 + + 目标像素格式 + 图像宽 + 图像高 + 缓存大小 + + + + SDk全局信息及操作接口 + + + + + 获取SDK版本信息 + + SDK版本号,格式x.y.z.a + + + + 初始化SDK + + 成功,返回MV_OK;失败,返回错误码 + + + + 反初始化SDK,释放资源 + + 成功,返回MV_OK;失败,返回错误码 + + + + 图像数据和帧信息 + + + + + 图像数据 + + + + + 帧号 + + + + + 设备时间戳 + + + + + 主机时间戳 + + + + + 帧长度 + + + + + 设备水印时标 + + + + + 周期数 + + + + + 周期偏移量 + + + + + 增益 + + + + + 曝光时间 + + + + + 平均亮度 + + + + + 白平衡红色通道 + + + + + 白平衡绿色通道 + + + + + 白平衡蓝色通道 + + + + + 总帧数 + + + + + 触发计数 + + + + + 输入 + + + + + 输出 + + + + + ROI区域,水平偏移量 + + + + + ROI区域,垂直偏移量 + + + + + 本帧丢包数 + + + + + Chunk数据 + + + + + 深拷贝 + + + + + + 相机句柄 + + + + + 图像类 + + + + + 提供取流相关接口 + + + + + 设置SDK内部图像缓存节点个数,大于等于1 + + 缓存节点个数 + 成功,返回MV_OK;失败,返回错误码 + + 调用该接口可以设置SDK内部图像缓存节点个数,在 IStreamGrabber.StartGrabbing() 前调用。 + 不同相机因为取流方式不同,不调用SetImageNodeNum方法的情况下,不同相机默认缓存节点的个数不同:比如 双U内部分配默认3个节点。 + SDK实际分配的节点个数 = SDK内部预分配的个数 + 用户分配的节点(SetImageNodeNum); + + + + + 获取当前图像缓存区的有效图像个数 + + 有效图像个数 + 成功,返回MV_OK;失败,返回错误码 + + + + 开始取流 + + 成功,返回MV_OK;失败,返回错误码 + + + + 开始取流,支持设置取流策略 + + 策略枚举值 + 成功,返回MV_OK;失败,返回错误码 + + + + 设置输出缓存个数(只有在 StreamGrabStrategy.LatestImages 策略下才有效,范围:1-ImageNodeNum) + + 输出缓存个数 + 成功,返回MV_OK;失败,返回错误码 + + + + 停止取流 + + 成功,返回MV_OK;失败,返回错误码 + + + + 获取一帧图像 + + 等待超时时间 + 图像数据和图像信息 + 成功,返回MV_OK;失败,返回错误码 + + + + 释放图像缓存(此接口用于释放不再使用的图像缓存,与 IStreamGrabber.FreeImageBuffer() 配套使用) + + 图像数据和图像数据 + 成功,返回MV_OK;失败,返回错误码 + + + + 清除取流数据缓存 + + 成功,返回MV_OK;失败,返回错误码 + + 该接口允许用户在不停止取流的时候,就能清除缓存中不需要的图像。 + 该接口在连续模式切触发模式后,可以清除历史数据。 + + + + + 设备流异常事件,只有USB设备支持 + + + 在StartGrabbing前调用 + + + + + 采集一帧图像的事件,获取图像数据 + + + 在StartGrabbing前调用 + + + + + 内部回调图像回调函数 + + + + + + + + 内部流异常回调函数 + + + + + + + 将MvCCDll中的帧结构体转为FrameOut类 + + + + + + + + USB流异常回调 + + + + + 设置U3V的传输包大小 + + 传输的包大小,单位:Byte,默认为1M,范围:Windows[0x400, 0x400000], Linux[0x400, 0x200000] + 成功,返回MV_OK;失败,返回错误码 + + 增加传输包大小可以适当降低取流时的CPU占用率。但不同的PC和不同USB扩展卡存在不同的兼容性,如果该参数设置过大可能会出现取不到图像的风险。 + + + + + 获取U3V的传输包大小 + + 传输的包大小, 单位:Byte + 成功,返回MV_OK;失败,返回错误码 + + + + 设置U3V的传输通道个数 + + 传输通道个数,范围:1-10 + 成功,返回MV_OK;失败,返回错误码 + + 可根据PC的性能、设备出图帧率、图像大小和内存使用率等因素对该参数进行调节。但不同的PC和不同的USB扩展卡存在不同的兼容性。 + + + + + 获取U3V的传输通道个数 + + 传输通道个数 + 成功,返回MV_OK;失败,返回错误码 + + 该接口用于获取当前的U3V异步取流节点个数,2000W设备的MONO8默认为3个,YUV为默认2个,RGB为默认1个,其它情况默认8个节点。 + + + + + 设置U3V的事件缓存节点个数 + + 事件缓存节点个数,范围:1-64 + 成功,返回MV_OK;失败,返回错误码 + + 该接口用于设置当前的U3V事件缓存节点个数,默认情况下为5个。 + + + + + 设置U3V相机同步读写超时时间,范围为1000~UINT,默认1000 ms + + 同步读写超时时间 + 成功,返回MV_OK;失败,返回错误码 + + + + 获取U3V相机同步读写超时时间 + + 同步读写超时时间 + 成功,返回MV_OK;失败,返回错误码 + + + + 获取主机从USB设备接收的数据统计信息,如已接收字节数、帧数 + + USB传输信息 + 成功,返回MV_OK;失败,返回错误码 + + + + 录像功能实现类 + + + + + 录像,将图片录制成AVI格式视频 + + + + + 开始录像 + + 录像文件存放路径 + 录像参数 + 成功,返回MV_OK;失败,返回错误码 + + + + 输入录像数据 + + 图像数据 + 成功,返回MV_OK;失败,返回错误码 + + + + 停止录像 + + 成功,返回MV_OK;失败,返回错误码 + + + + 异常信息 + + + + + 错误码 + + + + + Xml节点访问模式 + + + + + 不可实现 + + + + + 不可用 + + + + + 只写 + + + + + 只读 + + + + + 读写 + + + + + 未定义 + + + + + 内部用于AccessMode循环检测 + + + + + 每个节点对应的接口类型 + + + + + Value + + + + + Base + + + + + Integer + + + + + Boolean + + + + + Command + + + + + Float + + + + + String + + + + + Register + + + + + Category + + + + + Enumeration + + + + + EnumEntry + + + + + Port + + + + + 节点错误信息的类型 + + + + + 节点不存在 + + + + + 访问条件错误,通常是节点不可读写 + + + + + 写入越界,超出该节点支持的范围 + + + + + 校验失败,通常是写入的值与文件中的值不匹配 + + + + + 其它错误,可查阅日志 + + + + + 图像插值算法类型 + + + + + 快速 + + + + + 均衡 + + + + + 最优 + + + + + 最优+ + + + + + Gamma类型 + + + + + 不启用 + + + + + Gamma值 + + + + + Gamma曲线 + 8位,长度:256*sizeof(unsigned char) + 10位,长度:1024*sizeof(unsigned short) + 12位,长度:4096*sizeof(unsigned short) + 16位,长度:65536*sizeof(unsigned short) + + + + + linear RGB to sRGB + + + + + sRGB to linear RGB(仅色彩插值时支持,色彩校正时无效) + + + + + Gamma参数 + + + + + Gamma类型 + + + + + Gamma值[0.1, 4.0] + + + + + Gamma曲线缓存 + + + + + Gamma曲线长度 + + + + + CCM参数 + + + + + 是否启用CCM + + + + + CCM矩阵[-65536~65536],必须是Int32[9] + + + + + 量化系数(2的整数幂,最大65536) + + + + + 流异常类型 + + + + + 异常的图像,该帧被丢弃 + + + + + 缓存列表溢出,清除最旧的一帧 + + + + + 缓存列表为空,该帧被丢弃 + + + + + 断流恢复 + + + + + 断流,恢复失败,取流被中止 + + + + + 设备异常,取流被中止 + + + + + 流异常事件 + + + + + 流异常类型 + + + + + 取流策略 + + + + + 从旧到新一帧一帧的获取图像(默认为该策略) + + + + + 获取列表中最新的一帧图像,同时清除列表中的其余图像 + + + + + 从输出缓存列表中获取最新的OutputQueueSize帧图像,其中OutputQueueSize范围为1-ImageNodeNum,可用 SetOutputQueueSize()接口设置。ImageNodeNum默认为1,可调用SetImageNodeNum()接口设置。 OutputQueueSize设置成1等同于LatestImagesOnly策略,OutputQueueSize设置成ImageNodeNum等同于OneByOne策略。 + + + + + 在调用取流接口时忽略输出缓存列表中所有图像,并等待设备即将生成的一帧图像。该策略只支持GigE设备,不支持U3V设备 + + + + + 采集一帧图像的事件,用于获取图像数据和帧信息 + + + + + 图像帧信息 + + + + + 视频格式 + + + + + AVI格式 + + + + + 录像参数 + + + + + 输入数据的像素格式 + + + + + 图像宽(指定目标参数时需为2的倍数) + + + + + 图像高(指定目标参数时需为2的倍数) + + + + + 帧率fps(大于1/16) + + + + + 码率kbps(128-16*1024) + + + + + 录像格式 + + + + + MyCamera + + + + ch GigE Vision采集卡 |en GigE Vision interface + + + ch Camera Link采集卡 |en Camera Link interface + + + ch CoaXPress采集卡 |en CoaXPress interface + + + ch XoFLink采集卡 |en XoFLink interface + + + Unknown Device Type, Reserved + + + GigE Device + + + 1394-a/b Device + + + USB3.0 Device + + + CameraLink Device + + + Virtual GigE Device + + + Virtual USB Device + + + GenTL GigE Device + + + GenTL CML Device + + + GenTL CXP Device + + + GenTL XOF Device + + + + ch:信息结构体的最大缓存 | en: Max buffer size of information structs + + + + + 最大的相机数量 + + + + + ch:最大Interface数量 | en:Max num of interfaces + + + + + ch:最大GenTL设备数量 | en:Max num of GenTL devices + + + + + XML节点描述最大长度 + + + + + XML节点最大长度 + + + + + XML节点最大数量 + + + + + XML节点显示名最大数量 + + + + + string类型节点值的最大长度 + + + + + 最大父节点数 + + + + + 最大节点描述长度 + + + + + 设备断开连接 + + + + + SDK与驱动版本不匹配 + + + + + 相机Event事件名称最大长度 + + + + 最大枚举条目对应的符号长度 + + + 分时曝光时最多将源图像拆分的个数 + + + + ch:最大支持的采集卡数量 | en:The maximum number of Frame Grabber interface supported + + + + + ch 静态 |en Static + + + + + ch DHCP |en DHCP + + + + + ch LLA |en LLA + + + + + 9600 + + + + + 19200 + + + + + 38400 + + + + + 57600 + + + + + 115200 + + + + + 230400 + + + + + 460800 + + + + + 921600 + + + + + ch 最大值 |en Auto Max + + + + + ch 网络流量和丢包信息 |en Network traffic and packet loss information + + + + + ch host接收到来自U3V设备的字节总数 |en The total number of bytes host received from U3V device + + + + + ch独占权限,其他APP只允许读CCP寄存器 |en Exclusive authority, other APP is only allowed to read the CCP register + + + + + ch 可以从5模式下抢占权限,然后以独占权限打开 |en You can seize the authority from the 5 mode, and then open with exclusive authority + + + + + ch 控制权限,其他APP允许读所有寄存器 |en Control authority, allows other APP reading all registers + + + + + ch 可以从5的模式下抢占权限,然后以控制权限打开 |en You can seize the authority from the 5 mode, and then open with control authority + + + + + ch 以可被抢占的控制权限打开 |en Open with seized control authority + + + + + ch 可以从5的模式下抢占权限,然后以可被抢占的控制权限打开 |en You can seize the authority from the 5 mode, and then open with seized control authority + + + + + ch 读模式打开设备,适用于控制权限下 |en Open with read mode and is available under control authority + + + + 成功,无错误 + + + 错误或无效的句柄 + + + 不支持的功能 + + + 缓存已满 + + + 函数调用顺序错误 + + + 错误的参数 + + + 资源申请失败 + + + 无数据 + + + 前置条件有误,或运行环境已发生变化 + + + 版本不匹配 + + + 传入的内存空间不足 + + + 异常图像,可能是丢包导致图像不完整 + + + 动态导入DLL失败 + + + 没有可输出的缓存 + + + 加密错误 + + + 打开文件出现错误 + + + 未知的错误 + + + 通用错误 + + + 参数非法 + + + 值超出范围 + + + 属性 + + + 运行环境有问题 + + + 逻辑错误 + + + 节点访问条件有误 + + + 超时 + + + 转换异常 + + + GenICam未知错误 + + + 命令不被设备支持 + + + 访问的目标地址不存在 + + + 目标地址不可写 + + + 设备无访问权限 + + + 设备忙,或网络断开 + + + 网络包数据错误 + + + 网络相关错误 + + + 设备IP冲突 + + + 读usb出错 + + + 写usb出错 + + + 设备异常 + + + GenICam相关错误 + + + 带宽不足 + + + 驱动不匹配或者未装驱动 + + + USB未知的错误 + + + 升级固件不匹配 + + + 升级固件语言不匹配 + + + 升级冲突(设备已经在升级了再次请求升级即返回此错误) + + + 升级时设备内部出现错误 + + + 升级时未知错误 + + + 处理正确 + + + 不确定类型错误 + + + 能力集中存在无效参数 + + + 内存地址为空 + + + 内存对齐不满足要求 + + + 内存空间大小不够 + + + 内存空间大小不满足对齐要求 + + + 内存地址不满足对齐要求 + + + 图像格式不正确或者不支持 + + + 图像宽高不正确或者超出范围 + + + 图像宽高与step参数不匹配 + + + 图像数据存储地址为空 + + + 设置或者获取参数类型不正确 + + + 设置或者获取参数的输入、输出结构体大小不正确 + + + 处理类型不正确 + + + 处理时输入、输出参数大小不正确 + + + 子处理类型不正确 + + + 子处理时输入、输出参数大小不正确 + + + index参数不正确 + + + value参数不正确或者超出范围 + + + param_num参数不正确 + + + 函数参数指针为空 + + + 超过限定的最大内存 + + + 回调函数出错 + + + 加密错误 + + + 算法库使用期限错误 + + + 参数范围不正确 + + + 数据大小不正确 + + + 数据step不正确 + + + cpu不支持优化代码中的指令集 + + + 警告 + + + 算法库超时 + + + 算法版本号出错 + + + 模型版本号出错 + + + GPU内存分配错误 + + + 文件不存在 + + + 字符串为空 + + + 图像解码器错误 + + + 打开文件错误 + + + 文件读取错误 + + + 文件写错误 + + + 文件读取大小错误 + + + 文件类型错误 + + + 模型类型错误 + + + 分配内存错误 + + + 线程绑核失败 + + + 噪声特性图像格式错误 + + + 噪声特性类型错误 + + + 噪声特性个数错误 + + + 噪声特性增益个数错误 + + + 噪声曲线增益值输入错误 + + + 噪声曲线柱数错误 + + + 噪声估计初始化增益设置错误 + + + 噪声估计未初始化 + + + 颜色空间模式错误 + + + 图像ROI个数错误 + + + 图像ROI原点错误 + + + 图像ROI大小错误 + + + 输入的相机增益不存在(增益个数已达上限) + + + 输入的相机增益不在范围内 + + + 输入的噪声特性内存大小错误 + + + + Constructor + + + + + Destructor + + + + + 设备句柄 + + + + + Initialize + + Success, return MV_OK. Failure, return error code + + + + Finalize + + Success, return MV_OK. Failure, return error code + + + + 枚举采集卡设备信息 + + 采集卡类型 + 设备信息 + Success, return MV_OK. Failure, return error code + + + + 创建采集卡设备句柄 + + 采集卡设备信息 + Success, return MV_OK. Failure, return error code + + + + 通过采集卡ID创建设备句柄 + + 采集卡ID + Success, return MV_OK. Failure, return error code + + + + 打开采集卡设备 + + 采集卡信息配置文件(目前不支持传配置文件) + + + + + 关闭采集卡 + + Success, return MV_OK. Failure, return error code + + + + 销毁采集卡句柄 + + Success, return MV_OK. Failure, return error code + + + + Get Camera Handle + + + + + + Get SDK Version + + Always return 4 Bytes of version number |Main |Sub |Rev |Test| + 8bits 8bits 8bits 8bits + + + + + Get supported Transport Layer + + Supported Transport Layer number + + + + Enumerate Device + + Enumerate TLs + Device List + Success, return MV_OK. Failure, return error code + + + + Enumerate device according to manufacture name + + Enumerate TLs + Device List + Manufacture Name + Success, return MV_OK. Failure, return error code + + + + Enumerate device according to the specified ordering + + Transmission layer of enumeration(All layer protocol type can input) + Device list + Manufacture Name + Sorting Method + Success, return MV_OK. Failure, return error code + + + + Is the device accessible + + Device Information + Access Right + Access, return true. Not access, return false + + + + Set SDK log path (Interfaces not recommended) + If the logging service MvLogServer is enabled, the interface is invalid and The logging service is enabled by default + + + + + + + Create Device + + Device Information + Success, return MV_OK. Failure, return error code + + + + Create Device without log + + Device Information + Success, return MV_OK. Failure, return error code + + + + Destroy Device + + Success, return MV_OK. Failure, return error code + + + + Open Device + + Success, return MV_OK. Failure, return error code + + + + Open Device + + Access Right + Switch key of access right + Success, return MV_OK. Failure, return error code + + + + Close Device + + Success, return MV_OK. Failure, return error code + + + + Is the device connected + + Connected, return true. Not Connected or DIsconnected, return false + + + + Register the image callback function + + Callback function pointer + User defined variable + Success, return MV_OK. Failure, return error code + + + + Register the RGB image callback function + + Callback function pointer + User defined variable + Success, return MV_OK. Failure, return error code + + + + Register the BGR image callback function + + Callback function pointer + User defined variable + Success, return MV_OK. Failure, return error code + + + + Start Grabbing + + Success, return MV_OK. Failure, return error code + + + + Stop Grabbing + + Success, return MV_OK. Failure, return error code + + + + Get one frame of RGB image, this function is using query to get data + query whether the internal cache has data, get data if there has, return error code if no data + + Image data receiving buffer + Buffer size + Image information + Waiting timeout + Success, return MV_OK. Failure, return error code + + + + Get one frame of BGR image, this function is using query to get data + query whether the internal cache has data, get data if there has, return error code if no data + + Image data receiving buffer + Buffer size + Image information + Waiting timeout + Success, return MV_OK. Failure, return error cod + + + + Get a frame of an image using an internal cache + + Image data and image information + Waiting timeout + Success, return MV_OK. Failure, return error code + + + + Free image buffer(used with MV_CC_GetImageBuffer) + + Image data and image information + Success, return MV_OK. Failure, return error code + + + + Get a frame of an image + + Image data receiving buffer + Buffer size + Image information + Waiting timeout + Success, return MV_OK. Failure, return error code + + + + Clear image Buffers to clear old data + + Success, return MV_OK. Failure, return error code + + + + Get the number of valid images in the current image buffer + + The number of valid images in the current image buffer + Success, return MV_OK. Failure, return error code + + + + Display one frame image + + Image information + Success, return MV_OK. Failure, return error code + + + + Display one frame image Ex + + dispaly Handle + Image information + Success, return MV_OK. Failure, return error code + + + + Set the number of the internal image cache nodes in SDK(Greater than or equal to 1, to be called before the capture) + + Number of cache nodes + Success, return MV_OK. Failure, return error code + + + + Set Grab Strategy + + The value of grab strategy + Success, return MV_OK. Failure, return error code + + + + Set The Size of Output Queue(Only work under the strategy of MV_GrabStrategy_LatestImages,rang:1-ImageNodeNum) + + The Size of Output Queue + Success, return MV_OK. Failure, return error code + + + + Get device information(Called before start grabbing) + + device information + Success, return MV_OK. Failure, return error code + + + + Get various type of information + + Various type of information + Success, return MV_OK. Failure, return error code + + + + Get Integer value + + Key value, for example, using "Width" to get width + Value of device features + Success, return MV_OK. Failure, return error code + + + + Set Integer value + + Key value, for example, using "Width" to set width + Feature value to set + Success, return MV_OK. Failure, return error code + + + + Get Enum value + + Key value, for example, using "PixelFormat" to get pixel format + Value of device features + Success, return MV_OK. Failure, return error code + + + + Set Enum value + + Key value, for example, using "PixelFormat" to set pixel format + Feature value to set + Success, return MV_OK. Failure, return error code + + + + Get the symbolic of the specified value of the Enum type node + + Key value, for example, using "PixelFormat" to set pixel format + Symbolic to get + Success, return MV_OK. Failure, return error code + + + + Set Enum value + + Key value, for example, using "PixelFormat" to set pixel format + Feature String to set + Success, return MV_OK. Failure, return error code + + + + Get Float value + + Key value + Value of device features + Success, return MV_OK. Failure, return error code + + + + Set float value + + Key value + Feature value to set + Success, return MV_OK. Failure, return error code + + + + Get Boolean value + + Key value + Value of device features + Success, return MV_OK. Failure, return error code + + + + Set Boolean value + + Key value + Feature value to set + Success, return MV_OK. Failure, return error code + + + + Get String value + + Key value + Value of device features + Success, return MV_OK. Failure, return error code + + + + Set String value + + Key value + Feature value to set + Success, return MV_OK. Failure, return error code + + + + Send Command + + Key value + Success, return MV_OK. Failure, return error code + + + + Read Memory + + Used as a return value, save the read-in memory value(Memory value is stored in accordance with the big end model) + Memory address to be read, which can be obtained from the Camera.xml file of the device, the form xml node value of xxx_RegAddr + Length of the memory to be read + Success, return MV_OK. Failure, return error code + + + + Write Memory + + Memory value to be written ( Note the memory value to be stored in accordance with the big end model) + Memory address to be written, which can be obtained from the Camera.xml file of the device, the form xml node value of xxx_RegAddr + Length of the memory to be written + Success, return MV_OK. Failure, return error code + + + + Invalidate GenICam Nodes + + Success, return MV_OK. Failure, return error code + + + + Get camera feature tree XML + + XML data receiving buffer + Buffer size + Actual data length + Success, return MV_OK. Failure, return error code + + + + Get Access mode of cur node + + Name of node + Access mode of the node + Success, return MV_OK. Failure, return error code + + + + Get Interface Type of cur node + + Name of node + Interface Type of the node + Success, return MV_OK. Failure, return error code + + + + Save camera feature + + File name + Success, return MV_OK. Failure, return error code + + + + Load camera feature + + File name + Success, return MV_OK. Failure, return error code + + + + Read the file from the camera + + File access structure + Success, return MV_OK. Failure, return error code + + + + Read the file from the camera + + File access structure + Success, return MV_OK. Failure, return error code + + + + Write the file to camera + + File access structure + Success, return MV_OK. Failure, return error code + + + + Write the file to camera + + File access structure + Success, return MV_OK. Failure, return error code + + + + Get File Access Progress + + File access Progress + Success, return MV_OK. Failure, return error code + + + + Device Local Upgrade + + File path and name + Success, return MV_OK. Failure, return error code + + + + Get Upgrade Progress + + Value of Progress + Success, return MV_OK. Failure, return error code + + + + Register Exception Message CallBack, call after open device + + Exception Message CallBack Function + User defined variable + Success, return MV_OK. Failure, return error code + + + + Register event callback, which is called after the device is opened + + Event CallBack Function + User defined variable + Success, return MV_OK. Failure, return error code + + + + Register single event callback, which is called after the device is opened + + Event name + Event CallBack Function + User defined variable + Success, return MV_OK. Failure, return error code + + + + Set enumerate device timeout + + time out,default 100ms + Success, return MV_OK. Failure, return error code + + + + Force IP + + IP to set + Subnet mask + Default gateway + Success, return MV_OK. Failure, return error code + + + + IP configuration method + + IP type, refer to MV_IP_CFG_x + Success, return MV_OK. Failure, return error code + + + + Set to use only one mode,type: MV_NET_TRANS_x. When do not set, priority is to use driver by default + + Net transmission mode, refer to MV_NET_TRANS_x + Success, return MV_OK. Failure, return error code + + + + Get net transmission information + + Transmission information + Success, return MV_OK. Failure, return error code + + + + Setting the ACK mode of devices Discovery + + ACK mode(Default-Broadcast),0-Unicast,1-Broadcast + Success, return MV_OK. Failure, return error code + + + + Set GVSP streaming timeout + + Timeout, default 300ms, range: >10ms + Success, return MV_OK. Failure, return error code + + + + Get GVSP streaming timeout + + Timeout, ms as unit + Success, return MV_OK. Failure, return error code + + + + Set GVCP cammand timeout + + Timeout, ms as unit, range: 0-10000 + Success, return MV_OK. Failure, return error code + + + + Get GVCP cammand timeout + + Timeout, ms as unit + Success, return MV_OK. Failure, return error code + + + + Set the number of retry GVCP cammand + + The number of retries,rang:0-100 + Success, return MV_OK. Failure, return error code + + + + Get the number of retry GVCP cammand + + The number of retries + Success, return MV_OK. Failure, return error code + + + + Get the optimal Packet Size, Only support GigE Camera + + Optimal packet size + + + + Set whethe to enable resend, and set resend + + Enable resend + Max resend persent + Resend timeout + Success, return MV_OK. Failure, return error code + + + + Set the max resend retry times + + The max times to retry resending lost packets,default 20 + Success, return MV_OK. Failure, return error code + + + + Get the max resend retry times + + the max times to retry resending lost packets + Success, return MV_OK. Failure, return error code + + + + Set time interval between same resend requests + + The time interval between same resend requests,default 10ms + Success, return MV_OK. Failure, return error code + + + + Get time interval between same resend requests + + The time interval between same resend requests + Success, return MV_OK. Failure, return error code + + + + Set transmission type,Unicast or Multicast + + Struct of transmission type + Success, return MV_OK. Failure, return error code + + + + Issue Action Command + + Action Command info + Action Command Result List + Success, return MV_OK. Failure, return error code + + + + Get Multicast Status + + Device Information + Status of Multicast + Success, return MV_OK. Failure, return error code + + + + Set device baudrate using one of the CL_BAUDRATE_XXXX value + + Baudrate to set. Refer to the 'CameraParams.h' for parameter definitions, for example, #define MV_CAML_BAUDRATE_9600 0x00000001 + Success, return MV_OK. Failure, return error code + + + + Get device baudrate, using one of the CL_BAUDRATE_XXXX value + + Return pointer of baud rate to user. + Refer to the 'CameraParams.h' for parameter definitions, for example, #define MV_CAML_BAUDRATE_9600 0x00000001 + Success, return MV_OK. Failure, return error code + + + + Get supported baudrates of the combined device and host interface + + Return pointer of the supported baudrates to user. 'OR' operation results of the supported baudrates. + Refer to the 'CameraParams.h' for single value definitions, for example, #define MV_CAML_BAUDRATE_9600 0x00000001 + Success, return MV_OK. Failure, return error code + + + + Sets the timeout for operations on the serial port + + Timeout in [ms] for operations on the serial port. + Success, return MV_OK. Failure, return error code + + + + Set transfer size of U3V device + + Transfer size,Byte,default:1M,rang:>=0x10000 + Success, return MV_OK. Failure, return error code + + + + Get transfer size of U3V device + + Transfer size,Byte + Success, return MV_OK. Failure, return error code + + + + Set transfer ways of U3V device + + Transfer ways,rang:1-10 + Success, return MV_OK. Failure, return error code + + + + Get transfer ways of U3V device + + Transfer ways + Success, return MV_OK. Failure, return error code + + + + Register Stream Exception Message CallBack + + Stream Exception Message CallBack Function + User defined variable + Success, return MV_OK. Failure, return error code + + + + Set the number of U3V device event cache nodes + + Event Node Number + Success, return MV_OK. Failure, return error code + + + + Set U3V Camera Synchronisation timeout + + Synchronisation time(ms), default 1000ms + Success, return MV_OK. Failure, return error code + + + + Get U3V Camera Synchronisation timeout + + Synchronisation time(ms), default 1000ms + Success, return MV_OK. Failure, return error code + + + + Enumerate interfaces by GenTL + + Interface information list + Path of GenTL's cti file + + + + + Unload cti library + + GenTL cti file path + Success, return MV_OK. Failure, return error code + + + + Enumerate Device Based On GenTL + + Interface information + Device List + Success, return MV_OK. Failure, return error code + + + + Create Device Handle Based On GenTL Device Info + + Device Information Structure + Success, return MV_OK. Failure, return error code + + + + Save image, support Bmp and Jpeg. + + Save image parameters structure + Success, return MV_OK. Failure, return error code + + + + Save the image file, support Bmp、 Jpeg、Png and Tiff. Encoding quality(50-99] + + Save the image file parameter structure + Success, return MV_OK. Failure, return error code + + + + Save 3D point data, support PLY、CSV and OBJ + + Save 3D point data parameters structure + Success, return MV_OK. Failure, return error code + + + + Rotate Image + + Rotate image parameter structure + Success, return MV_OK. Failure, return error code + + + + Flip Image + + Flip image parameter structure + Success, return MV_OK. Failure, return error code + + + + Pixel format conversion + + Convert Pixel Type parameter structure + Success, return MV_OK. Failure, return error code + + + + Interpolation algorithm type setting + + Bayer interpolation method 0-Fast 1-Equilibrium 2-Optimal + Success, return MV_OK. Failure, return error code + + + + Filter type of the bell interpolation quality algorithm setting + + Filter type enable + Success, return MV_OK. Failure, return error code + + + + Set Bayer Gamma value + + Gamma value[0.1,4.0] + Success, return MV_OK. Failure, return error code + + + + Set Mono8/Bayer Gamma value + + PixelType + Gamma value[0.1,4.0] + Success, return MV_OK. Failure, return error code + + + + Set Gamma param + + Gamma parameter structure + Success, return MV_OK. Failure, return error code + + + + Set CCM param + + CCM parameter structure + Success, return MV_OK. Failure, return error code + + + + Set CCM param + + CCM parameter structure + Success, return MV_OK. Failure, return error code + + + + Adjust image contrast + + Contrast parameter structure + Success, return MV_OK. Failure, return error code + + + + High Bandwidth Decode + + High Bandwidth Decode parameter structure + Success, return MV_OK. Failure, return error code + + + + Draw Rect Auxiliary Line + + Rect Auxiliary Line Info + Success, return MV_OK. Failure, return error code + + + + Draw Circle Auxiliary Line + + Circle Auxiliary Line Info + Success, return MV_OK. Failure, return error code + + + + Draw Line Auxiliary Line + + Linear Auxiliary Line Info + Success, return MV_OK. Failure, return error code + + + + Start Record + + Record param structure + Success, return MV_OK. Failure, return error code + + + + Input RAW data to Record + + Record data structure + Success, return MV_OK. Failure, return error code + + + + Stop Record + + Success, return MV_OK. Failure, return error code + + + + Open the GUI interface for getting or setting camera parameters + + Success, return MV_OK. Failure, return error code + + + + Reconstruct Image(For time-division exposure function) + + Reconstruct image parameters + Success, return MV_OK. Failure, return error code + + + + Byte array to struct + + Byte array + Struct type + Struct object + + + + 判断字符数组是否为utf-8 + + 字符数组 + + + + + Write Error Message + + Message + ErrorNum + + + + Save image, support Bmp and Jpeg. + + Save image parameters structure + Success, return MV_OK. Failure, return error code + + + + Save the image file, support Bmp、 Jpeg、Png and Tiff. Encoding quality(50-99] + + Save the image file parameter structure + Success, return MV_OK. Failure, return error code + + + + Pixel format conversion + + Convert Pixel Type parameter structure + Success, return MV_OK. Failure, return error code + + + + Get basic information of image (Interfaces not recommended) + + + + + + + Get GenICam proxy (Interfaces not recommended) + + + + + + Get root node (Interfaces not recommended) + + + + + + + Get all children node of specific node from xml, root node is Root (Interfaces not recommended) + + + + + + + + Get all children node of specific node from xml, root node is Root (Interfaces not recommended) + + + + + + + + Get current node feature (Interfaces not recommended) + + + + + + + + Update node (Interfaces not recommended) + + + + + + + + Register update callback (Interfaces not recommended) + + + + + + + + Noise estimate of Bayer format + + Noise estimate parameter structure + Success, return MV_OK. Failure, return error code + + + + Spatial Denoise of Bayer format + + Spatial Denoise parameter structure + Success, return MV_OK. Failure, return error code + + + + This interface is abandoned, it is recommended to use the MV_CC_DisplayOneFrame + + + + + + + This interface is abandoned, it is recommended to use the MV_CC_GetOneFrameTimeOut + + + + + + + + + This interface is abandoned, it is recommended to use the MV_CC_GetOneFrameTimeOut + + + + + + + + + This interface is abandoned, it is recommended to use the MV_CC_SaveImageEx + + + + + + + This interface is abandoned, it is recommended to use the MV_GIGE_ForceIpEx + + + + + + + This interface is abandoned, it is recommended to use the MV_CC_RegisterEventCallBackEx + + + + + + + + This interface is abandoned, it is recommended to use the MV_CC_GetIntValueEx + + + + + + + + This interface is abandoned, it is recommended to use the MV_CC_SetIntValueEx + + + + + + + + Set CLUT param + + CLUT parameter structure + Success, return MV_OK. Failure, return error code + + + + Image sharpen + + Sharpen parameter structure + Success, return MV_OK. Failure, return error code + + + + Color Correct(include CCM and CLUT) + + Color Correct parameter structure + Success, return MV_OK. Failure, return error code + + + + Noise Estimate + + Noise Estimate parameter structure + Success, return MV_OK. Failure, return error code + + + + Spatial Denoise + + Spatial Denoise parameter structure + Success, return MV_OK. Failure, return error code + + + + LSC Calib + + LSC Calib parameter structure + Success, return MV_OK. Failure, return error code + + + + LSC Correct + + LSC Correct parameter structure + Success, return MV_OK. Failure, return error code + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + Set PixelFormat + + PixelFormat + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + Get Trigger Source + + Trigger Source + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is replaced by general interface + + + + + + + This interface is abandoned, it is recommended to use the MV_CC_RegisterImageCallBackEx + + + + + + + + Grab callback + + Image data + Frame info + User defined variable + + + + Grab callback + + Image data + Frame info + User defined variable + + + + Xml Update callback(Interfaces not recommended) + + Node type + Current node feature structure + Nodes list + User defined variable + + + + Exception callback + + Msg type + User defined variable + + + + Event callback (Interfaces not recommended) + + User defined ID + User defined variable + + + + Event callback + + Event Info + User defined variable + + + + Stream Exception callback + + Msg type + User defined variable + + + + ch:采集卡信息列表 | en: Interface Information List + + + + + ch:在线设备数量 | en:Online Interface Number + + + + + ch:支持最多64个设备 | en:Support up to 64 Interfaces + + + + + ch:采集卡信息 | en: Interface information + + + + + ch: 采集卡类型; 低16位有效: bits(0~2)代表功能, bits(3~7)代表相机, bits(8-15)代表总线| en: Interface type + + + + + ch: 采集卡的PCIE插槽信息 | en: PCIe slot information of interface + + + + + ch: 采集卡ID | en: Interface ID + + + + + ch 显示名称 | en: Display name + + + + + ch 序列号 |en: Serial number + + + + + ch 型号 | en: model name + + + + + ch: 厂商 |en: manufacturer name + + + + + ch: 版本号| en: device version + + + + + ch: 自定义名称 |en: user defined name + + + + + ch 保留字段 | en Reserved + + + + + 排序方式 + + + + + 按序列号排序 + + + + + 按用户自定义名字排序 + + + + + 按当前IP地址排序(升序) + + + + + 按当前IP地址排序(降序) + + + + + ch: GigE设备信息 | en: GigE device information + + + + + IP 配置选项 + + + + + IP configuration:bit31-static bit30-dhcp bit29-lla + + + + + curtent ip + + + + + curtent subnet mask + + + + + current gateway + + + + + 制造商名 + + + + + 型号名 + + + + + 设备版本信息 + + + + + 制造商特殊信息 + + + + + 序列号 + + + + + 用户自定义名 + + + + + 网口IP地址 + + + + + 预留 + + + + + ch: GigE设备信息 | en: GigE device information + + + + + IP 配置选项 + + + + + IP configuration:bit31-static bit30-dhcp bit29-lla + + + + + curtent ip + + + + + curtent subnet mask + + + + + current gateway + + + + + 制造商名 + + + + + 型号名 + + + + + 设备版本信息 + + + + + 制造商特殊信息 + + + + + 序列号 + + + + + 用户自定义名 + + + + + 网口IP地址 + + + + + 预留 + + + + + ch:USB3 设备信息 | en:USB3 device information + + + + + 控制输入端点 + + + + + 控制输出端点 + + + + + 流端点 + + + + + 事件端点 + + + + + 供应商ID号 + + + + + 产品ID号 + + + + + 设备索引号 + + + + + 设备GUID号 + + + + + 供应商名字 + + + + + 型号名字 + + + + + 家族名字 + + + + + 设备版本号 + + + + + 制造商名字 + + + + + 序列号 + + + + + 用户自定义名字 + + + + + 支持的USB协议 + + + + + 保留字节 + + + + + ch:USB3 设备信息 | en:USB3 device information + + + + + 控制输入端点 + + + + + 控制输出端点 + + + + + 流端点 + + + + + 事件端点 + + + + + 供应商ID号 + + + + + 产品ID号 + + + + + 设备索引号 + + + + + 设备GUID号 + + + + + 供应商名字 + + + + + 型号名字 + + + + + 家族名字 + + + + + 设备版本号 + + + + + 制造商名字 + + + + + 序列号 + + + + + 用户自定义名字 + + + + + 支持的USB协议 + + + + + 保留字节 + + + + + ch:CamLink设备信息 | en:CamLink device information + + + + + 端口号ID + + + + + 模型名 + + + + + 家族名 + + + + + 设备版本信息 + + + + + 制造商名字 + + + + + 序列号 + + + + + 保留字节 + + + + + ch:采集卡Camera Link相机信息 | en:Camera Link device information on frame grabber + + + + + ch 采集卡ID |en Interface ID of Frame Grabber + + + + + ch 供应商名字 |en Vendor name + + + + + ch 型号名字 |en Model name + + + + + ch 厂商信息 |en Manufacturer information + + + + + ch 相机版本 |en Device version + + + + + ch 序列号 |en Serial number + + + + + ch 用户自定义名字 |en User defined name + + + + + ch 相机ID |en Device ID + + + + + ch 保留字段 |en Reserved + + + + + ch:CoaXPress相机信息 | en:CoaXPress device information + + + + + ch 采集卡ID |en Interface ID of Frame Grabber + + + + + ch 供应商名字 |en Vendor name + + + + + ch 型号名字 |en Model name + + + + + ch 厂商信息 |en Manufacturer information + + + + + ch 相机版本 |en Device version + + + + + ch 序列号 |en Serial number + + + + + ch 用户自定义名字 |en User defined name + + + + + ch 相机ID |en Device ID + + + + + ch 保留字段 |en Reserved + + + + + ch:XoFLink相机信息 | en:XoFLink device information + + + + + ch 采集卡ID |en Interface ID of Frame Grabber + + + + + ch 供应商名字 |en Vendor name + + + + + ch 型号名字 |en Model name + + + + + ch 厂商信息 |en Manufacturer information + + + + + ch 相机版本 |en Device version + + + + + ch 序列号 |en Serial number + + + + + ch 用户自定义名字 |en User defined name + + + + + ch 相机ID |en Device ID + + + + + ch 保留字段 |en Reserved + + + + + ch:设备信息 | en:Device information + + + + + 主版本号 + + + + + 次版本号 + + + + + MAC高地址 + + + + + MAC低地址 + + + + + 设备传输层协议类型,e.g. MV_GIGE_DEVICE + + + + + ch 设备类型信息 | en Device Type Info + + + + + 保留字节 + + + + + 设备类型 + + + + + 构造函数 + + 输入任意数,因为不接受无参构造函数 + + + + ch:特定类型的设备信息 | en:Special devcie information + + + + + GigE + + + + + Camera Link + + + + + Usb + + + + + CML + + + + + CXP + + + + + XOF + + + + + 相机列表 + + + + + 在线设备数量 + + + + + 支持最多256个设备 + + + + + ch:通过GenTL枚举到的Interface信息 | en:Interface Information with GenTL + + + + + GenTL接口ID + + + + + 传输层类型 + + + + + 设备显示名称 + + + + + GenTL的cti文件索引 + + + + + 保留字节 + + + + + ch:通过GenTL枚举到的设备信息列表 | en:Interface Information List with GenTL + + + + + ch:在线设备数量 | en:Online Interface Number + + + + + ch:支持最多256个设备 | en:Support up to 256 Interfaces + + + + + ch:通过GenTL枚举到的设备信息 | en:Device Information discovered by with GenTL + + + + + 采集卡ID + + + + + 设备ID + + + + + 供应商名字 + + + + + 模型名 + + + + + 传输类型 + + + + + 显示名 + + + + + 用户自定义名 + + + + + 序列号 + + + + + 设备版本信息 + + + + + cti文件序号 + + + + + 保留字节 + + + + + ch:通过GenTL枚举到的设备信息 | en:Device Information discovered by with GenTL + + + + + 采集卡ID + + + + + 设备ID + + + + + 供应商名字 + + + + + 模型名 + + + + + 传输类型 + + + + + 显示名 + + + + + 用户自定义名 + + + + + 序列号 + + + + + 设备版本信息 + + + + + cti文件序号 + + + + + 保留字节 + + + + + ch:GenTL设备列表 | en:GenTL devices list + + + + + 在线设备数量 + + + + + 支持最多256个设备 + + + + + Net Trans Info + + + + + 已接收数据大小 [统计StartGrabbing和StopGrabbing之间的数据量] + + + + + 丢帧数量 + + + + + 接收帧数 + + + + + 请求重发包数 + + + + + 重发包数 + + + + + Frame Out Info + + + + + 图像宽 + + + + + 图像高 + + + + + 像素格式 + + + + + 帧号 + + + + + 时间戳高32位 + + + + + 时间戳低32位 + + + + + 保留,8字节对齐 + + + + + 主机生成的时间戳 + + + + + 帧数据大小 + + + + + 丢包数量 + + + + + 保留字节 + + + + + Chunk数据信息 + + + + + Chunk数据 + + + + + ChunkID + + + + + Chunk大小 + + + + + 保留字节 + + + + + Frame Out Info Ex + + + + + 图像宽 + + + + + 图像高 + + + + + 像素格式 + + + + + 帧号 + + + + + 时间戳高32位 + + + + + 时间戳低32位 + + + + + 保留,8字节对齐 + + + + + 主机生成的时间戳 + + + + + Frame大小 + + + + + 秒数 + + + + + 周期数 + + + + + 周期偏移量 + + + + + 增益 + + + + + 曝光时间 + + + + + 平均亮度 + + + + + Red + + + + + Green + + + + + Blue + + + + + 帧计数器 + + + + + 触发计数 + + + + + 输入 + + + + + 输出 + + + + + 水平偏移量 + + + + + 垂直偏移量 + + + + + Chunk宽度 + + + + + Chunk高度 + + + + + 丢包数 + + + + + 为解析的Chunk数量 + + + + + 为解析的Chunk列表 + + + + + 图像宽扩展 + + + + + 图像高扩展 + + + + + 保留字节 + + + + + 为解析的Chunk列表 + + + + + 为解析的Chunk内容 + + + + + 对齐结构体,无实际用途 + + + + + 输出帧信息 + + + + + 帧数据地址 + + + + + 帧信息 + + + + + 保留字节 + + + + + 取流策略 + + + + + 从旧到新一帧一帧的获取图像(默认为该策略) + + + + + 获取列表中最新的一帧图像(同时清除列表中的其余图像) + + + + + 获取列表中最新的图像,个数由OutputQueueSize决定,范围为1-ImageNodeNum,设置成1等同于LatestImagesOnly,设置成ImageNodeNum等同于OneByOne + + + + + 等待下一帧图像 + + + + + 显示帧信息 + + + + + 显示窗口的句柄 + + + + + 显示的帧数据 + + + + + 显示的帧数据大小 + + + + + 图像宽 + + + + + 图像高 + + + + + 像素格式 + + + + + 保留字节 + + + + + 显示帧信息 + + + + + 图像宽 + + + + + 图像高 + + + + + 像素格式 + + + + + 显示的帧数据 + + + + + 显示的帧数据大小 + + + + + 保留字节 + + + + + ch:保存3D数据格式 | en:Save 3D file + + + + + 未定义数据格式 + + + + + PLY数据格式 + + + + + CSV数据格式 + + + + + OBJ数据格式 + + + + + 保存的点阵参数 + + + + + [IN] 每一行点的数量 + + + + + [IN] 行数 + + + + + [IN] 输入数据的像素格式 + + + + + [IN] 输入数据缓存 + + + + + [IN] 输入数据大小 + + + + + [OUT] 输出像素数据缓存 + + + + + [IN] 提供的输出缓冲区大小(nLinePntNum * nLineNum * (16*3 + 4) + 2048) + + + + + [OUT] 输出像素数据缓存长度 + + + + + 保存的点阵文件类型 + + + + + 保留字节 + + + + + 保存的图像格式 + + + + + 未定义类型 + + + + + Bmp图像格式 + + + + + Jpeg图像格式 + + + + + Png图像格式 + + + + + Tif图像格式 + + + + + 保存的图像参数 + + + + + [IN] 输入数据缓存 + + + + + [IN] 输入数据大小 + + + + + [IN] 输入数据的像素格式 + + + + + [IN] 图像宽 + + + + + [IN] 图像高 + + + + + [OUT] 输出图片缓存 + + + + + [OUT] 输出图片大小 + + + + + [IN] 提供的输出缓冲区大小 + + + + + [IN] 输出图片格式 + + + + + 保存的图像参数 + + + + + [IN] 输入数据缓存 + + + + + [IN] 输入数据大小 + + + + + [IN] 输入数据的像素格式 + + + + + [IN] 图像宽 + + + + + [IN] 图像高 + + + + + [OUT] 输出图片缓存 + + + + + [OUT] 输出图片大小 + + + + + [IN] 提供的输出缓冲区大小 + + + + + [IN] 输出图片格式 + + + + + [IN] 编码质量, (50-99] + + + + + [IN] Bayer的插值方法 0-快速 1-均衡 2-最优(如果传入其它值则默认为最优) + + + + + 保留字节 + + + + + 保存的图像信息扩展 + + + + + [IN] 输入数据缓存 + + + + + [IN] 输入数据大小 + + + + + [IN] 输入数据的像素格式 + + + + + [IN] 图像宽 + + + + + [IN] 图像高 + + + + + [OUT] 输出图片缓存 + + + + + [OUT] 输出图片大小 + + + + + [IN] 提供的输出缓冲区大小 + + + + + [IN] 输出图片格式 + + + + + [IN] 编码质量, (50-99] + + + + + [IN] Bayer的插值方法 0-快速 1-均衡 2-最优(如果传入其它值则默认为最优) + + + + + 保留字节 + + + + + 保存图像到文件的参数 + + + + + [IN] 输入数据的像素格式 + + + + + [IN] 输入数据缓存 + + + + + [IN] 输入数据大小 + + + + + [IN] 图像宽 + + + + + [IN] 图像高 + + + + + [IN] 输入图片格式 + + + + + [IN] 编码质量, (0-100] + + + + + [IN] 输入文件路径 + + + + + [IN] Bayer的插值方法 0-快速 1-均衡 2-最优(如果传入其它值则默认为最优) + + + + + 保留字节 + + + + + 保存图像到文件信息扩展 + + + + + [IN] 图像宽 + + + + + [IN] 图像高 + + + + + [IN] 输入数据的像素格式 + + + + + [IN] 输入数据缓存 + + + + + [IN] 输入数据大小 + + + + + [IN] 输入图片格式 + + + + + [IN] 输入文件路径 + + + + + [IN] 编码质量, (0-100] + + + + + [IN] Bayer的插值方法 0-快速 1-均衡 2-最优(如果传入其它值则默认为最优) + + + + + 保留字节 + + + + + 旋转角度 + + + + + 旋转90度 + + + + + 旋转180度 + + + + + 旋转270度 + + + + + 旋转图像参数 + + + + + [IN] 像素格式(仅支持Mono8/RGB24/BGR24) + + + + + [IN][OUT] 图像宽 + + + + + [IN][OUT] 图像高 + + + + + [IN] 输入数据缓存 + + + + + [IN] 输入数据大小 + + + + + [OUT] 输出图片缓存 + + + + + [OUT] 输出图片大小 + + + + + [IN] 提供的输出缓冲区大小 + + + + + [IN] 旋转角度 + + + + + 保留字节 + + + + + 图像翻转类型 + + + + + 垂直方向翻转 + + + + + 水平方向翻转 + + + + + 翻转图像参数 + + + + + [IN] 像素格式(仅支持Mono8/RGB24/BGR24) + + + + + [IN] 图像宽 + + + + + [IN] 图像高 + + + + + [IN] 输入数据缓存 + + + + + [IN] 输入数据大小 + + + + + [OUT] 输出图片缓存 + + + + + [OUT] 输出图片大小 + + + + + [IN] 提供的输出缓冲区大小 + + + + + [IN] 翻转类型 + + + + + 保留字节 + + + + + 像素转换参数 + + + + + [IN] 图像宽 + + + + + [IN] 图像高 + + + + + [IN] 源像素格式 + + + + + [IN] 输入数据缓存 + + + + + [IN] 输入数据大小 + + + + + [IN] 目标像素格式 + + + + + [OUT] 输出数据缓存 + + + + + [OUT] 输出数据大小 + + + + + [IN] 提供的输出缓冲区大小 + + + + + 保留字节 + + + + + 图像像素转换信息扩展 + + + + + [IN] 图像宽 + + + + + [IN] 图像高 + + + + + [IN] 源像素格式 + + + + + [IN] 输入数据缓存 + + + + + [IN] 输入数据大小 + + + + + [IN] 目标像素格式 + + + + + [OUT] 输出数据缓存 + + + + + [OUT] 输出数据大小 + + + + + [IN] 提供的输出缓冲区大小 + + + + + 保留字节 + + + + + Gamma类型 + + + + + 不启用 + + + + + GAMMA值 + + + + + GAMMA曲线,8位需要的长度:256*sizeof(unsigned char) + 10位需要的长度:1024*sizeof(unsigned short) + 12位需要的长度:4096*sizeof(unsigned short) + 16位需要的长度:65536*sizeof(unsigned short) + + + + + 线性RGB转非线性RGB + + + + + 非线性RGB转线性RGB + + + + + Gamma参数 + + + + + [IN] Gamma类型 + + + + + [IN] Gamma值 + + + + + [IN] Gamma曲线缓存 + + + + + [IN] Gamma曲线长度 + + + + + 保留字节 + + + + + CCM参数 + + + + + [IN] 是否启用CCM + + + + + [IN] CCM矩阵(-8192~8192) + + + + + 保留字节 + + + + + CCM参数 + + + + + [IN] 是否启用CCM + + + + + [IN] 量化3x3矩阵 + + + + + [IN] 量化系数(2的整数幂) + + + + + 保留字节 + + + + + CLUT参数 + + + + + [IN] 是否启用CLUT + + + + + [IN] 量化系数(2的整数幂) + + + + + [IN] CLUT大小,建议值17 + + + + + [OUT] 量化CLUT + + + + + [IN] 量化CLUT缓存大小(nCLUTSize*nCLUTSize*nCLUTSize*sizeof(int)*3) + + + + + 保留字节 + + + + + 对比度调节参数 + + + + + [IN] 图像宽度(最小8) + + + + + [IN] 图像高度(最小8) + + + + + [IN] 输入图像缓存 + + + + + [IN] 输入图像缓存长度 + + + + + [IN] 输入的像素格式 + + + + + [OUT] 输出像素数据缓存 + + + + + [IN] 提供的输出缓冲区大小 + + + + + [OUT] 输出像素数据缓存长度 + + + + + [IN] 对比度值,范围:[1, 10000] + + + + + 保留字节 + + + + + 锐化参数 + + + + + [IN] 图像宽度(最小8) + + + + + [IN] 图像高度(最小8) + + + + + [IN] 输入图像缓存 + + + + + [IN] 输入图像缓存长度 + + + + + [IN] 输入的像素格式 + + + + + [OUT] 输出像素数据缓存 + + + + + [IN] 提供的输出缓冲区大小 + + + + + [OUT] 输出像素数据缓存长度 + + + + + [IN] 锐度调节强度,范围:[0, 500] + + + + + [IN] 锐度调节半径(半径越大,耗时越长),范围:[1, 21] + + + + + [IN] 锐度调节阈值,范围:[0, 255] + + + + + 保留字节 + + + + + 色彩校正参数(包括CCM和CLUT) + + + + + [IN] 图像宽度 + + + + + [IN] 图像高度 + + + + + [IN] 输入图像缓存 + + + + + [IN] 输入图像缓存长度 + + + + + [IN] 输入的像素格式 + + + + + [OUT] 输出像素数据缓存 + + + + + [IN] 提供的输出缓冲区大小 + + + + + [OUT] 输出像素数据缓存长度 + + + + + [IN] 输入有效图像位数,8 or 10 or 12 or 16 + + + + + [IN] 输入Gamma信息 + + + + + [IN] 输入CCM信息 + + + + + [IN] 输入CLUT信息 + + + + + 保留字节 + + + + + 矩形ROI参数 + + + + + [IN] 矩形左上角X轴坐标 + + + + + [IN] 矩形左上角Y轴坐标 + + + + + [IN] 矩形宽度 + + + + + [IN] 矩形高度 + + + + + 噪声估计参数 + + + + + [IN] 图像宽度 + + + + + [IN] 图像高度 + + + + + [IN] 输入的像素格式 + + + + + [IN] 输入图像缓存 + + + + + [IN] 输入图像缓存长度 + + + + + [IN] 图像ROI + + + + + [IN] ROI个数 + + + + + [IN] 噪声阈值[0-4095] + + + + + [OUT] 输出噪声特性 + + + + + [IN] 提供的输出缓冲区大小 + + + + + [OUT] 输出噪声特性长度 + + + + + 保留字节 + + + + + 空域降噪参数 + + + + + [IN] 图像宽度 + + + + + [IN] 图像高度 + + + + + [IN] 输入的像素格式 + + + + + [IN] 输入图像缓存 + + + + + [IN] 输入图像缓存长度 + + + + + [OUT] 输出降噪后的数据 + + + + + [IN] 提供的输出缓冲区大小 + + + + + [OUT] 输出降噪后的数据长度 + + + + + [IN] 输入噪声特性 + + + + + [IN] 输入噪声特性长度 + + + + + [IN] 降噪强度(0-100) + + + + + [IN] 锐化强度(0-32) + + + + + [IN] 噪声校正系数(0-1280) + + + + + [IN] 亮度校正系数(1-2000) + + + + + [IN] 色调校正系数(1-2000) + + + + + [IN] 亮度降噪强度(0-100) + + + + + [IN] 色调降噪强度(0-100) + + + + + [IN] 锐化强度(1-1000) + + + + + 保留字节 + + + + + LSC标定参数 + + + + + [IN] 图像宽度(16~65536) + + + + + [IN] 图像高度(16~65536) + + + + + [IN] 输入的像素格式 + + + + + [IN] 输入图像缓存 + + + + + [IN] 输入图像缓存长度 + + + + + [OUT] 输出标定表缓存 + + + + + [IN] 提供的标定表缓冲大小(nWidth*nHeight*sizeof(unsigned short)) + + + + + [OUT] 输出标定表缓存长度 + + + + + [IN] 宽度分块数 + + + + + [IN] 高度分块数 + + + + + [IN] 边缘填充系数,范围1~5 + + + + + [IN] 标定方式,0-中心为基准 + 1-最亮区域为基准 + 2-目标亮度 + + + + + [IN] 目标亮度(8bits,[0,255]) + (10bits,[0,1023]) + (12bits,[0,4095]) + (16bits,[0,65535]) + + + + + 保留字节 + + + + + LSC校正参数 + + + + + [IN] 图像宽度(16~65536) + + + + + [IN] 图像高度(16~65536) + + + + + [IN] 输入的像素格式 + + + + + [IN] 输入图像缓存 + + + + + [IN] 输入图像缓存长度 + + + + + [OUT] 输出像素数据缓存 + + + + + [IN] 提供的输出缓冲区大小 + + + + + [OUT] 输出像素数据缓存长度 + + + + + [IN] 输入校正表缓存 + + + + + [IN] 输入校正表缓存长度 + + + + + 保留字节 + + + + + 噪声特性类型 + + + + + 无效 + + + + + 噪声曲线 + + + + + 噪声水平 + + + + + 默认值 + + + + + 噪声基本信息 + + + + + 版本 + + + + + 噪声特性类型 + + + + + 图像格式 + + + + + 平均噪声水平 + + + + + 曲线点数 + + + + + 噪声曲线 + + + + + 亮度曲线 + + + + + 保留字节 + + + + + 噪声估计参数 + + + + + [IN] 图像宽(大于等于8) + + + + + [IN] 图像高(大于等于8) + + + + + [IN] 像素格式 + + + + + [IN] 输入数据缓存 + + + + + [IN] 输入数据大小 + + + + + [IN] 噪声阈值(0-4095) + + + + + [IN] 用于存储噪声曲线和亮度曲线(需要外部分配,缓存大小:4096 * sizeof(int) * 2) + + + + + [OUT] 降噪特性信息 + + + + + [IN] 线程数量,0表示算法库根据硬件自适应;1表示单线程(默认);大于1表示线程数目 + + + + + 保留字节 + + + + + 降噪参数 + + + + + [IN] 图像宽(大于等于8) + + + + + [IN] 图像高(大于等于8) + + + + + [IN] 像素格式 + + + + + [IN] 输入数据缓存 + + + + + [IN] 输入数据大小 + + + + + [OUT] 输出降噪后的数据 + + + + + [IN] 提供的输出缓冲区大小 + + + + + [OUT] 输出降噪后的数据长度 + + + + + [IN] 降噪特性信息(来源于噪声估计) + + + + + [IN] 降噪强度(0-100) + + + + + [IN] 锐化强度(0-32) + + + + + [IN] 噪声校正系数(0-1280) + + + + + [IN] 线程数量,0表示算法库根据硬件自适应;1表示单线程(默认);大于1表示线程数目 + + + + + 保留字节 + + + + + 帧特殊信息 + + + + + [OUT] 秒数 + + + + + [OUT] 周期数 + + + + + [OUT] 周期偏移量 + + + + + [OUT] 增益 + + + + + [OUT] 曝光时间 + + + + + [OUT] 平均亮度 + + + + + [OUT] 红色 + + + + + [OUT] 绿色 + + + + + [OUT] 蓝色 + + + + + [OUT] 总帧数 + + + + + [OUT] 触发计数 + + + + + [OUT] 输入 + + + + + [OUT] 输出 + + + + + [OUT] 水平偏移量 + + + + + [OUT] 垂直偏移量 + + + + + [OUT] 水印宽 + + + + + [OUT] 水印高 + + + + + 保留字节 + + + + + HB解码参数 + + + + + [IN] 输入数据缓存 + + + + + [IN] 输入数据大小 + + + + + [OUT] 图像宽 + + + + + [OUT] 图像高 + + + + + [OUT] 输出数据缓存 + + + + + [IN] 提供的输出缓冲区大小 + + + + + [OUT] 输出数据大小 + + + + + [OUT] 输出的像素格式 + + + + + [OUT] 水印信息 + + + + + 保留字节 + + + + + 录像格式定义 + + + + + 未定义格式 + + + + + AVI格式 + + + + + 录像参数 + + + + + [IN] 输入数据的像素格式 + + + + + [IN] 图像宽(指定目标参数时需为8的倍数) + + + + + [IN] 图像高(指定目标参数时需为8的倍数) + + + + + [IN] 帧率fps(大于1/16) + + + + + [IN] 码率kbps(128kbps-16Mbps) + + + + + [IN] 录像格式 + + + + + [IN] 录像文件存放路径 + + + + + 保留字节 + + + + + 输入帧信息 + + + + + [IN] 图像数据指针 + + + + + [IN] 图像大小 + + + + + 保留字节 + + + + + 采集模式 + + + + + 单帧模式 + + + + + 多帧模式 + + + + + 持续采集模式 + + + + + 增益模式 + + + + + 关闭 + + + + + 一次 + + + + + 连续 + + + + + 曝光模式 + + + + + Timed + + + + + TriggerWidth + + + + + 自动曝光模式 + + + + + 关闭 + + + + + 一次 + + + + + 连续 + + + + + 相机触发模式 + + + + + 关闭 + + + + + 打开 + + + + + Gamma选择器 + + + + + USER + + + + + SRGB + + + + + 自动白平衡 + + + + + 关闭自动白平衡 + + + + + 一次自动白平衡 + + + + + 连续自动白平衡 + + + + + 触发源 + + + + + LINE0 + + + + + LINE1 + + + + + LINE2 + + + + + LINE3 + + + + + COUNTER0 + + + + + SOFTWARE + + + + + FrequencyConverter + + + + + ALL MATHCH INFO + + + + + 需要输出的信息类型,e.g. MV_MATCH_TYPE_NET_DETECT + + + + + 输出的信息缓存,由调用者分配 + + + + + 信息缓存的大小 + + + + + + + + + + 已接收数据大小 [统计StartGrabbing和StopGrabbing之间的数据量] + + + + + 丢失的包数量 + + + + + 丢帧数量 + + + + + 帧数 + + + + + 请求重发包数 + + + + + 重发包数 + + + + + USB + + + + + 已接收数据大小 [统计OpenDevicce和CloseDevice之间的数据量] + + + + + 已收到的帧数 + + + + + 错误帧数 + + + + + 保留字节 + + + + + 图像的基本信息 + + + + + 宽度值 + + + + + 宽度最小值 + + + + + 宽度最大值 + + + + + Width Inc + + + + + 高度值 + + + + + 高度最小值 + + + + + 高度最大值 + + + + + Height Inc + + + + + 帧率 + + + + + 最小帧率 + + + + + 最大帧率 + + + + + 当前的像素格式 + + + + + 支持的像素格式种类 + + + + + 像素列表 + + + + + 保留字节 + + + + + 节点是否可见的权限等级 + + + + + Always visible + + + + + Visible for experts or Gurus + + + + + Visible for Gurus + + + + + Not Visible + + + + + Object is not yet initialized + + + + + 事件信息 + + + + + 事件名 + + + + + Event号 + + + + + 流通到序号 + + + + + 帧号高位 + + + + + 帧号低位 + + + + + 时间戳高位 + + + + + 时间戳低位 + + + + + Event数据 + + + + + Event数据长度 + + + + + 保留字节 + + + + + 文件存取 + + + + + 用户文件名 + + + + + 设备文件名 + + + + + 保留字节 + + + + + 文件存取 + + + + + 用户文件数据缓存空间 + + + + + 用户数据缓存大小 + + + + + 文件实际缓存大小 + + + + + 设备文件名 + + + + + 保留字节 + + + + + 文件存取进度 + + + + + 已完成的长度 + + + + + 总长度 + + + + + 保留字节 + + + + + GigE传输类型 + + + + + 表示单播(默认) + + + + + 表示组播 + + + + + 表示局域网内广播,暂不支持 + + + + + 表示子网内广播,暂不支持 + + + + + 表示从相机获取,暂不支持 + + + + + 表示用户自定义应用端接收图像数据Port号 + + + + + 表示设置了单播,但本实例不接收图像数据 + + + + + 表示组播模式,但本实例不接收图像数据 + + + + + 传输模式,可以为单播模式、组播模式等 + + + + + 传输模式 + + + + + 目标IP,组播模式下有意义 + + + + + 目标Port,组播模式下有意义 + + + + + 保留字节 + + + + + 动作命令信息 + + + + + 设备密钥 + + + + + 组键 + + + + + 组掩码 + + + + + 只有设置成1时Action Time才有效,非1时无效 + + + + + 预定的时间,和主频有关 + + + + + 广播包地址 + + + + + 等待ACK的超时时间,如果为0表示不需要ACK + + + + + 保留字节 + + + + + 动作命令结果 + + + + + IP address of the device + + + + + status code returned by the device + + + + + 保留字节 + + + + + 动作命令结果列表 + + + + + 返回值个数 + + + + + 返回的结果 + + + + + 每个节点对应的接口类型 + + + + + IValue接口类型 + + + + + IBase接口类型 + + + + + IInteger接口类型 + + + + + IBoolean接口类型 + + + + + ICommand接口类型 + + + + + IFloat接口类型 + + + + + IString接口类型 + + + + + IRegister接口类型 + + + + + ICategory接口类型 + + + + + IEnumeration接口类型 + + + + + IEnumEntry接口类型 + + + + + IPort接口类型 + + + + + XML节点特点 + + + + + 节点类型 + + + + + 是否可见 + + + + + 节点描述 + + + + + 显示名称 + + + + + 节点名 + + + + + 提示 + + + + + 保留字节 + + + + + XML节点列表 + + + + + 节点个数 + + + + + 节点列表 + + + + + 整型节点值 + + + + + 当前值 + + + + + 最大值 + + + + + 最小值 + + + + + Inc + + + + + 保留字节 + + + + + 整型节点值 + + + + + 当前值 + + + + + 最大值 + + + + + 最小值 + + + + + Inc + + + + + 保留字节 + + + + + 浮点型节点值 + + + + + 当前值 + + + + + 最大值 + + + + + 最小值 + + + + + 保留字节 + + + + + 枚举型节点值 + + + + + 当前值 + + + + + 有效数据个数 + + + + + 保留字节 + + + + + 保留字节 + + + + + 字符串型节点值 + + + + + 当前值 + + + + + 节点值的最大长度 + + + + + 保留字节 + + + + + 节点的读写性 + + + + + 未实现 + + + + + 不可获取 + + + + + 只写 + + + + + 只读 + + + + + 可读可写 + + + + + 未定义 + + + + + 内部用于AccessMode循环检测 + + + + + 整型节点 + + + + + 节点名 + + + + + 显示名称 + + + + + 节点描述 + + + + + 提示 + + + + + 是否可见 + + + + + 访问模式 + + + + + 是否锁定。0-否;1-是 + + + + + 当前值 + + + + + 最小值 + + + + + 最大值 + + + + + 增量 + + + + + 保留字节 + + + + + 布尔型节点 + + + + + 节点名 + + + + + 显示名称 + + + + + 节点描述 + + + + + 提示 + + + + + 是否可见 + + + + + 访问模式 + + + + + 是否锁定。0-否;1-是 + + + + + 当前值 + + + + + 保留字节 + + + + + 命令型节点 + + + + + 节点名 + + + + + 显示名称 + + + + + 节点描述 + + + + + 提示 + + + + + 是否可见 + + + + + 访问模式 + + + + + 是否锁定。0-否;1-是 + + + + + 保留字节 + + + + + 浮点型节点 + + + + + 节点名 + + + + + 显示名称 + + + + + 节点描述 + + + + + 提示 + + + + + 是否可见 + + + + + 访问模式 + + + + + 是否锁定。0-否;1-是 + + + + + 当前值 + + + + + 最小值 + + + + + 最大值 + + + + + 增量 + + + + + 保留字节 + + + + + 字符串类型节点 + + + + + 节点名 + + + + + 显示名称 + + + + + 节点描述 + + + + + 提示 + + + + + 是否可见 + + + + + 访问模式 + + + + + 是否锁定。0-否;1-是 + + + + + 当前值 + + + + + 保留字节 + + + + + 寄存器型节点 + + + + + 节点名 + + + + + 显示名称 + + + + + 节点描述 + + + + + 提示 + + + + + 是否可见 + + + + + 访问模式 + + + + + 是否锁定。0-否;1-是 + + + + + 当前值 + + + + + 保留字节 + + + + + 类别属性 + + + + + 节点描述 + + + + + 显示名称 + + + + + 节点名 + + + + + 提示 + + + + + 是否可见 + + + + + 保留字节 + + + + + EnumEntry属性节点 + + + + + 节点名 + + + + + 显示名称 + + + + + 节点描述 + + + + + 提示 + + + + + + + + + + 父节点数 + + + + + 父节点列表 + + + + + 是否可见 + + + + + 当前值 + + + + + 访问模式 + + + + + 是否锁定。0-否;1-是 + + + + + 保留字节 + + + + + 节点描述 + + + + + 节点描述 + + + + + Enumeration属性节点 + + + + + 是否可见 + + + + + 节点描述 + + + + + 显示名称 + + + + + 节点名 + + + + + 提示 + + + + + Symbolic数 + + + + + 当前Symbolic索引 + + + + + Symbolic索引 + + + + + 访问模式 + + + + + 是否锁定。0-否;1-是 + + + + + 当前值 + + + + + 保留字节 + + + + + Port属性节点 + + + + + 是否可见 + + + + + 节点描述 + + + + + 显示名称 + + + + + 节点名 + + + + + 提示 + + + + + 访问模式 + + + + + 是否锁定。0-否;1-是 + + + + + 保留字节 + + + + 辅助线颜色 + + + [0.0 , 1.0] + + + [0.0 , 1.0] + + + [0.0 , 1.0] + + + [0.0 , 1.0] + + + 预留字节 + + + 自定义点坐标 + + + [0.0 , 1.0] + + + [0.0 , 1.0] + + + 预留字节 + + + 矩形框区域信息 + + + [0.0 , 1.0] + + + [0.0 , 1.0] + + + [0.0 , 1.0] + + + [0.0 , 1.0] + + + 辅助线颜色 + + + 辅助线宽度 + + + 预留字节 + + + 圆形框区域信息 + + + 圆心信息 + + + 宽向半径,根据图像的相对位置[0, 1.0] + + + 高向半径,根据图像的相对位置[0, 1.0] + + + 辅助线颜色信息 + + + 辅助线宽度 + + + 预留字节 + + + 线条辅助线信息 + + + 线条辅助线的起始点坐标 + + + 线条辅助线的终点坐标 + + + 辅助线颜色信息 + + + 辅助线宽度 + + + 预留字节 + + + 枚举类型指定条目信息 + + + 指定值 + + + 指定值对应的符号 + + + 预留字节 + + + U3V流异常类型 + + + 异常的图像,该帧被丢弃 + + + 缓存列表溢出,清除最旧的一帧 + + + 缓存列表为空,该帧被丢弃 + + + 断流恢复 + + + 断流,恢复失败,取流被中止 + + + 设备异常,取流被中止 + + + 重构后的图像列表 + + + 源图像宽 + + + 源图像高 + + + 像素格式 + + + 输出数据缓存 + + + 输出数据长度 + + + 提供的输出缓冲区大小 + + + 预留字节 + + + + 分时曝光的图像处理方式 + + + + + 源图像按行拆分成多张图像 + + + + 重构图像参数信息 + + + 源图像宽 + + + 源图像高 + + + 像素格式 + + + 输入数据缓存 + + + 输入数据长度 + + + 曝光个数(1-8] + + + 图像重构方式 + + + + 输出数据缓存信息 + + + + 预留字节 + + + + 像素格式定义 + + + + + 未定义像素格式 + + + + + Mono1p + + + + + Mono2p + + + + + Mono4p + + + + + Mono8 + + + + + Mono8_Signed + + + + + Mono10 + + + + + Mono10_Packed + + + + + Mono12 + + + + + Mono12_Packed + + + + + Mono14 + + + + + Mono16 + + + + + BayerGR8 + + + + + BayerRG8 + + + + + BayerGB8 + + + + + BayerBG8 + + + + + BayerRBGG8 + + + + + BayerGR10 + + + + + BayerRG10 + + + + + BayerGB10 + + + + + BayerBG10 + + + + + BayerGR12 + + + + + BayerRG12 + + + + + BayerGB12 + + + + + BayerBG12 + + + + + BayerGR10_Packed + + + + + BayerRG10_Packed + + + + + BayerGB10_Packed + + + + + BayerBG10_Packed + + + + + BayerGR12_Packed + + + + BayerRG12_Packed + + + BayerGB12_Packed + + + BayerBG12_Packed + + + BayerGR16 + + + BayerRG16 + + + BayerGB16 + + + BayerBG16 + + + RGB8_Packed + + + BGR8_Packed + + + RGBA8_Packed + + + BGRA8_Packed + + + RGB10_Packed + + + BGR10_Packed + + + RGB12_Packed + + + BGR12_Packed + + + RGB16_Packed + + + BGR16_Packed/// + + + RGBA16_Packed + + + BGRA16_Packed + + + RGB10V1_Packe + + + RGB10V2_Packed + + + RGB12V1_Packed + + + RGB565_Packed + + + BGR565_Packed + + + YUV411_Packed + + + YUV422_Packed + + + YUV422_YUYV_Packed + + + YUV444_Packed + + + YCBCR8_CBYCR + + + YCBCR422_8 + + + YCBCR422_8_CBYCRY + + + YCBCR411_8_CBYYCRYY + + + YCBCR601_8_CBYCR + + + YCBCR601_422_8 + + + YCBCR601_422_8_CBYCRY + + + YCBCR601_411_8_CBYYCRYY + + + YCBCR709_8_CBYCR + + + YCBCR709_422_8 + + + YCBCR709_422_8_CBYCRY + + + YCBCR709_411_8_CBYYCRYY + + + YUV420SP_NV12 + + + YUV420SP_NV21 + + + RGB8_Planar + + + RGB10_Planar + + + RGB12_Planar + + + RGB16_Planar + + + Jpeg + + + Coord3D_ABC32f + + + Coord3D_ABC32f_Planar + + + Coord3D_AC32f + + + COORD3D_DEPTH_PLUS_MASK + + + Coord3D_ABC32 + + + Coord3D_AB32f + + + Coord3D_AB32 + + + Coord3D_AC32f_64 + + + Coord3D_AC32f_Planar + + + Coord3D_AC32 + + + Coord3D_A32f + + + Coord3D_A32 + + + Coord3D_C32f + + + Coord3D_C32 + + + Coord3D_ABC16 + + + Coord3D_C16 + + + Float32 + + + HB_Mono8 + + + HB_Mono10 + + + HB_Mono10_Packed + + + HB_Mono12 + + + HB_Mono12_Packed + + + HB_Mono16 + + + HB_BayerGR8 + + + HB_BayerRG8 + + + HB_BayerGB8 + + + HB_BayerBG8 + + + HB_BayerRBGG8 + + + HB_BayerGR10 + + + HB_BayerRG10 + + + HB_BayerGB10 + + + HB_BayerBG10 + + + HB_BayerGR12 + + + HB_BayerRG12 + + + HB_BayerGB12 + + + HB_BayerBG12 + + + HB_BayerGR10_Packed + + + HB_BayerRG10_Packed + + + HB_BayerGB10_Packed + + + HB_BayerBG10_Packed + + + HB_BayerGR12_Packed + + + HB_BayerRG12_Packed + + + HB_BayerGB12_Packed + + + HB_BayerBG12_Packed + + + HB_YUV422_Packed + + + HB_YUV422_YUYV_Packed + + + HB_RGB8_Packed + + + HB_BGR8_Packed + + + HB_RGBA8_Packed + + + HB_BGRA8_Packed + + + HB_RGB16_Packed + + + HB_BGR16_Packed + + + HB_RGBA16_Packed + + + HB_BGRA16_Packed + + + + 像素格式 + + + + + 未定义像素格式 + + + + + Mono1p + + + + + Mono2p + + + + + Mono4p + + + + + Mono8 + + + + + Mono8_Signed + + + + + Mono10 + + + + + Mono10_Packed + + + + + Mono12 + + + + + Mono12_Packed + + + + + Mono14 + + + + + Mono16 + + + + + BayerGR8 + + + + + BayerRG8 + + + + + BayerGB8 + + + + + BayerBG8 + + + + + BayerRBGG8 + + + + + BayerGR10 + + + + + BayerRG10 + + + + + BayerGB10 + + + + + BayerBG10 + + + + + BayerGR12 + + + + + BayerRG12 + + + + + BayerGB12 + + + + + BayerBG12 + + + + + BayerGR10_Packed + + + + + BayerRG10_Packed + + + + + BayerGB10_Packed + + + + + BayerBG10_Packed + + + + + BayerGR12_Packed + + + + BayerRG12_Packed + + + BayerGB12_Packed + + + BayerBG12_Packed + + + BayerGR16 + + + BayerRG16 + + + BayerGB16 + + + BayerBG16 + + + RGB8_Packed + + + BGR8_Packed + + + RGBA8_Packed + + + BGRA8_Packed + + + RGB10_Packed + + + BGR10_Packed + + + RGB12_Packed + + + BGR12_Packed + + + RGB16_Packed + + + BGR16_Packed/// + + + RGBA16_Packed + + + BGRA16_Packed + + + RGB10V1_Packe + + + RGB10V2_Packed + + + RGB12V1_Packed + + + RGB565_Packed + + + BGR565_Packed + + + YUV411_Packed + + + YUV422_Packed + + + YUV422_YUYV_Packed + + + YUV444_Packed + + + YCBCR8_CBYCR + + + YCBCR422_8 + + + YCBCR422_8_CBYCRY + + + YCBCR411_8_CBYYCRYY + + + YCBCR601_8_CBYCR + + + YCBCR601_422_8 + + + YCBCR601_422_8_CBYCRY + + + YCBCR601_411_8_CBYYCRYY + + + YCBCR709_8_CBYCR + + + YCBCR709_422_8 + + + YCBCR709_422_8_CBYCRY + + + YCBCR709_411_8_CBYYCRYY + + + YUV420SP_NV12 + + + YUV420SP_NV21 + + + RGB8_Planar + + + RGB10_Planar + + + RGB12_Planar + + + RGB16_Planar + + + Jpeg + + + Coord3D_ABC32f + + + Coord3D_ABC32f_Planar + + + Coord3D_AC32f + + + COORD3D_DEPTH_PLUS_MASK + + + Coord3D_ABC32 + + + Coord3D_AB32f + + + Coord3D_AB32 + + + Coord3D_AC32f_64 + + + Coord3D_AC32f_Planar + + + Coord3D_AC32 + + + Coord3D_A32f + + + Coord3D_A32 + + + Coord3D_C32f + + + Coord3D_C32 + + + Coord3D_ABC16 + + + Coord3D_C16 + + + Float32 + + + HB_Mono8 + + + HB_Mono10 + + + HB_Mono10_Packed + + + HB_Mono12 + + + HB_Mono12_Packed + + + HB_Mono16 + + + HB_BayerGR8 + + + HB_BayerRG8 + + + HB_BayerGB8 + + + HB_BayerBG8 + + + HB_BayerRBGG8 + + + HB_BayerGR10 + + + HB_BayerRG10 + + + HB_BayerGB10 + + + HB_BayerBG10 + + + HB_BayerGR12 + + + HB_BayerRG12 + + + HB_BayerGB12 + + + HB_BayerBG12 + + + HB_BayerGR10_Packed + + + HB_BayerRG10_Packed + + + HB_BayerGB10_Packed + + + HB_BayerBG10_Packed + + + HB_BayerGR12_Packed + + + HB_BayerRG12_Packed + + + HB_BayerGB12_Packed + + + HB_BayerBG12_Packed + + + HB_YUV422_Packed + + + HB_YUV422_YUYV_Packed + + + HB_RGB8_Packed + + + HB_BGR8_Packed + + + HB_RGBA8_Packed + + + HB_BGRA8_Packed + + + HB_RGB16_Packed + + + HB_BGR16_Packed + + + HB_RGBA16_Packed + + + HB_BGRA16_Packed + + + diff --git a/ExternalLibraries/MvCameraControl.Net.dll b/ExternalLibraries/MvCameraControl.Net.dll new file mode 100644 index 0000000..a2e4c8b Binary files /dev/null and b/ExternalLibraries/MvCameraControl.Net.dll differ diff --git a/ExternalLibraries/config.json b/ExternalLibraries/config.json index 28f9fdf..0200def 100644 --- a/ExternalLibraries/config.json +++ b/ExternalLibraries/config.json @@ -1,4 +1,5 @@ { "Language": "zh-CN", - "LogLevel": "Debug" + "LogLevel": "Debug", + "CameraType": "Hikvision" } \ No newline at end of file diff --git a/XP.Camera/Converters/PixelConverter.cs b/XP.Camera/Converters/PixelConverter.cs index 2c965b5..601f13a 100644 --- a/XP.Camera/Converters/PixelConverter.cs +++ b/XP.Camera/Converters/PixelConverter.cs @@ -10,6 +10,7 @@ public static class PixelConverter { /// /// 将原始像素数据转换为 WPF 的 BitmapSource 对象。 + /// 支持 Mono8、BGR8、RGB8、BGRA8 以及 Bayer 8-bit 格式(自动解码为 BGR24)。 /// 返回的 BitmapSource 已调用 Freeze(),可跨线程访问。 /// public static BitmapSource ToBitmapSource(byte[] pixelData, int width, int height, string pixelFormat) @@ -19,11 +20,23 @@ public static class PixelConverter if (height <= 0) throw new ArgumentException("Height must be a positive integer.", nameof(height)); ArgumentNullException.ThrowIfNull(pixelFormat); - var (format, stride) = pixelFormat switch + string normalized = NormalizePixelFormat(pixelFormat); + + // Bayer 格式需要解码 + if (normalized.StartsWith("Bayer")) + { + byte[] bgrData = DemosaicBayer(pixelData, width, height, normalized); + var bmp = BitmapSource.Create(width, height, 96, 96, PixelFormats.Bgr24, null, bgrData, width * 3); + bmp.Freeze(); + return bmp; + } + + var (format, stride) = normalized switch { "Mono8" => (PixelFormats.Gray8, width), "BGR8" => (PixelFormats.Bgr24, width * 3), "BGRA8" => (PixelFormats.Bgra32, width * 4), + "RGB8" => (PixelFormats.Rgb24, width * 3), _ => throw new NotSupportedException($"Pixel format '{pixelFormat}' is not supported.") }; @@ -31,4 +44,136 @@ public static class PixelConverter bitmap.Freeze(); return bitmap; } -} \ No newline at end of file + + /// + /// 将不同 SDK 的像素格式名称统一为标准名称。 + /// + private static string NormalizePixelFormat(string pixelFormat) + { + if (pixelFormat is "Mono8" or "BGR8" or "BGRA8" or "RGB8") + return pixelFormat; + + var upper = pixelFormat.ToUpperInvariant(); + + if (upper.Contains("MONO8")) return "Mono8"; + if (upper.Contains("BGR8")) return "BGR8"; + if (upper.Contains("BGRA8")) return "BGRA8"; + if (upper.Contains("RGB8") && !upper.Contains("BAYER")) return "RGB8"; + + // Bayer 格式 + if (upper.Contains("BAYERRG8") || upper.Contains("BAYER_RG8")) return "BayerRG8"; + if (upper.Contains("BAYERGR8") || upper.Contains("BAYER_GR8")) return "BayerGR8"; + if (upper.Contains("BAYERGB8") || upper.Contains("BAYER_GB8")) return "BayerGB8"; + if (upper.Contains("BAYERBG8") || upper.Contains("BAYER_BG8")) return "BayerBG8"; + + return pixelFormat; + } + + /// + /// 简单 Bayer 解码(双线性插值),输出 BGR24。 + /// + private static byte[] DemosaicBayer(byte[] bayer, int width, int height, string pattern) + { + // pattern: BayerRG8, BayerGR8, BayerGB8, BayerBG8 + // RG: R G GR: G R GB: G B BG: B G + // G B B G R G G R + + int rRow, rCol; // 红色像素在2x2块中的位置 + switch (pattern) + { + case "BayerRG8": rRow = 0; rCol = 0; break; + case "BayerGR8": rRow = 0; rCol = 1; break; + case "BayerGB8": rRow = 1; rCol = 0; break; + case "BayerBG8": rRow = 1; rCol = 1; break; + default: rRow = 0; rCol = 0; break; + } + + byte[] bgr = new byte[width * height * 3]; + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + int srcIdx = y * width + x; + int dstIdx = (y * width + x) * 3; + + // 确定当前像素在 Bayer 模式中的角色 + int py = (y + rRow) % 2; // 0=红行, 1=蓝行 + int px = (x + rCol) % 2; // 0=红列/蓝列, 1=绿列 + + byte r, g, b; + + if (py == 0 && px == 0) + { + // 红色像素位置 + r = bayer[srcIdx]; + g = AvgNeighbors4(bayer, width, height, x, y); + b = AvgDiagonal(bayer, width, height, x, y); + } + else if (py == 1 && px == 1) + { + // 蓝色像素位置 + b = bayer[srcIdx]; + g = AvgNeighbors4(bayer, width, height, x, y); + r = AvgDiagonal(bayer, width, height, x, y); + } + else if (py == 0 && px == 1) + { + // 绿色像素(红行) + g = bayer[srcIdx]; + r = AvgHorizontal(bayer, width, x, y); + b = AvgVertical(bayer, width, height, x, y); + } + else + { + // 绿色像素(蓝行) + g = bayer[srcIdx]; + b = AvgHorizontal(bayer, width, x, y); + r = AvgVertical(bayer, width, height, x, y); + } + + bgr[dstIdx] = b; + bgr[dstIdx + 1] = g; + bgr[dstIdx + 2] = r; + } + } + + return bgr; + } + + private static byte AvgNeighbors4(byte[] data, int w, int h, int x, int y) + { + int sum = 0, count = 0; + if (x > 0) { sum += data[y * w + x - 1]; count++; } + if (x < w - 1) { sum += data[y * w + x + 1]; count++; } + if (y > 0) { sum += data[(y - 1) * w + x]; count++; } + if (y < h - 1) { sum += data[(y + 1) * w + x]; count++; } + return count > 0 ? (byte)(sum / count) : (byte)0; + } + + private static byte AvgDiagonal(byte[] data, int w, int h, int x, int y) + { + int sum = 0, count = 0; + if (x > 0 && y > 0) { sum += data[(y - 1) * w + x - 1]; count++; } + if (x < w - 1 && y > 0) { sum += data[(y - 1) * w + x + 1]; count++; } + if (x > 0 && y < h - 1) { sum += data[(y + 1) * w + x - 1]; count++; } + if (x < w - 1 && y < h - 1) { sum += data[(y + 1) * w + x + 1]; count++; } + return count > 0 ? (byte)(sum / count) : (byte)0; + } + + private static byte AvgHorizontal(byte[] data, int w, int x, int y) + { + int sum = 0, count = 0; + if (x > 0) { sum += data[y * w + x - 1]; count++; } + if (x < w - 1) { sum += data[y * w + x + 1]; count++; } + return count > 0 ? (byte)(sum / count) : (byte)0; + } + + private static byte AvgVertical(byte[] data, int w, int h, int x, int y) + { + int sum = 0, count = 0; + if (y > 0) { sum += data[(y - 1) * w + x]; count++; } + if (y < h - 1) { sum += data[(y + 1) * w + x]; count++; } + return count > 0 ? (byte)(sum / count) : (byte)0; + } +} diff --git a/XP.Camera/Core/CameraFactory.cs b/XP.Camera/Core/CameraFactory.cs index de85f89..e8baf3b 100644 --- a/XP.Camera/Core/CameraFactory.cs +++ b/XP.Camera/Core/CameraFactory.cs @@ -11,8 +11,8 @@ public class CameraFactory : ICameraFactory return cameraType switch { "Basler" => new BaslerCameraController(), - // "Hikvision" => new HikvisionCameraController(), - _ => throw new NotSupportedException($"不支持的相机品牌: {cameraType}") + "Hikvision" => new HikvisionCameraController(), + _ => throw new NotSupportedException($"Unsupported Camera Type: {cameraType}") }; } } \ No newline at end of file diff --git a/XP.Camera/Hikvision/HikvisionCameraController.cs b/XP.Camera/Hikvision/HikvisionCameraController.cs new file mode 100644 index 0000000..5e0a33c --- /dev/null +++ b/XP.Camera/Hikvision/HikvisionCameraController.cs @@ -0,0 +1,535 @@ +using MvCameraControl; +using Serilog; + +namespace XP.Camera; + +/// +/// 海康威视相机控制器,封装 MvCameraControl.Net SDK 实现 。 +/// +/// +/// 所有公共方法通过内部 _syncLock 对象进行 lock 同步,保证线程安全。 +/// 事件回调(ImageGrabbed、GrabError)在 SDK 回调线程上触发,不持有 _syncLock,避免死锁。 +/// +public class HikvisionCameraController : ICameraController +{ + private static readonly ILogger _logger = Log.ForContext(); + private static bool _sdkInitialized; + private static readonly object _sdkInitLock = new(); + + private readonly object _syncLock = new(); + private IDevice? _device; + private CameraInfo? _cachedCameraInfo; + private bool _isConnected; + private bool _isGrabbing; + + public HikvisionCameraController() + { + // SDK 初始化延迟到 Open() 中执行 + } + + /// + public bool IsConnected + { + get { lock (_syncLock) { return _isConnected; } } + } + + /// + public bool IsGrabbing + { + get { lock (_syncLock) { return _isGrabbing; } } + } + + /// + public event EventHandler? ImageGrabbed; + + /// + public event EventHandler? GrabError; + + /// + public event EventHandler? ConnectionLost; + + /// + public CameraInfo Open() + { + lock (_syncLock) + { + if (_isConnected && _cachedCameraInfo != null) + { + _logger.Information("Hikvision camera already connected, returning cached info."); + return _cachedCameraInfo; + } + + try + { + _logger.Information("Opening Hikvision camera connection..."); + + // 确保 SDK 初始化 + EnsureSdkInitialized(); + + // 枚举设备 + DeviceTLayerType layerType = DeviceTLayerType.MvGigEDevice + | DeviceTLayerType.MvUsbDevice; + + List deviceInfoList; + int ret = DeviceEnumerator.EnumDevices(layerType, out deviceInfoList); + _logger.Information("EnumDevices(GigE|USB) returned: 0x{RetCode:X8}, device count: {Count}", + ret, deviceInfoList?.Count ?? 0); + + // 如果没找到,分别尝试 + if (ret == MvError.MV_OK && (deviceInfoList == null || deviceInfoList.Count == 0)) + { + // 单独尝试 GigE + List gigeList; + int retGige = DeviceEnumerator.EnumDevices(DeviceTLayerType.MvGigEDevice, out gigeList); + _logger.Information("EnumDevices(GigE only) returned: 0x{RetCode:X8}, count: {Count}", + retGige, gigeList?.Count ?? 0); + + // 单独尝试 USB + List usbList; + int retUsb = DeviceEnumerator.EnumDevices(DeviceTLayerType.MvUsbDevice, out usbList); + _logger.Information("EnumDevices(USB only) returned: 0x{RetCode:X8}, count: {Count}", + retUsb, usbList?.Count ?? 0); + + // 合并结果 + deviceInfoList = new List(); + if (gigeList != null) deviceInfoList.AddRange(gigeList); + if (usbList != null) deviceInfoList.AddRange(usbList); + } + + if (ret != MvError.MV_OK) + { + throw new CameraException($"Enumerate Hikvision devices failed: 0x{ret:X8}"); + } + + if (deviceInfoList == null || deviceInfoList.Count == 0) + { + throw new DeviceNotFoundException("No Hikvision camera device found."); + } + + // 选择第一个设备 + IDeviceInfo deviceInfo = deviceInfoList[0]; + _logger.Information("Found Hikvision device: {Model} (SN: {Serial})", + deviceInfo.ModelName, deviceInfo.SerialNumber); + + // 创建设备 + _device = DeviceFactory.CreateDevice(deviceInfo); + + // 打开设备 + ret = _device.Open(); + if (ret != MvError.MV_OK) + { + _device.Dispose(); + _device = null; + throw new CameraException($"Open Hikvision device failed: 0x{ret:X8}"); + } + + // GigE 设备优化包大小 + if (_device is IGigEDevice gigEDevice) + { + int packetSize; + ret = gigEDevice.GetOptimalPacketSize(out packetSize); + if (ret == MvError.MV_OK && packetSize > 0) + { + _device.Parameters.SetIntValue("GevSCPSPacketSize", packetSize); + _logger.Debug("Set GigE packet size to {PacketSize}", packetSize); + } + } + + // 配置软件触发模式 + _device.Parameters.SetEnumValueByString("TriggerMode", "On"); + _device.Parameters.SetEnumValueByString("TriggerSource", "Software"); + + // 彩色相机:尝试设置输出为 BGR8 以便直接显示 + // 如果相机不支持 BGR8(如只支持 Bayer),则保持默认 + int fmtRet = _device.Parameters.SetEnumValueByString("PixelFormat", "BGR8Packed"); + if (fmtRet != MvError.MV_OK) + { + // 尝试 Mono8(黑白相机) + fmtRet = _device.Parameters.SetEnumValueByString("PixelFormat", "Mono8"); + } + _logger.Debug("Set PixelFormat result: 0x{Ret:X8}", fmtRet); + + _cachedCameraInfo = new CameraInfo( + ModelName: deviceInfo.ModelName ?? "", + SerialNumber: deviceInfo.SerialNumber ?? "", + VendorName: deviceInfo.ManufacturerName ?? "", + DeviceType: deviceInfo.TLayerType.ToString() + ); + + _isConnected = true; + _logger.Information("Hikvision camera connected: {ModelName} (SN: {SerialNumber})", + _cachedCameraInfo.ModelName, _cachedCameraInfo.SerialNumber); + + return _cachedCameraInfo; + } + catch (Exception ex) when (ex is not CameraException) + { + _device?.Dispose(); + _device = null; + _logger.Error(ex, "Failed to open Hikvision camera."); + throw new CameraException("Failed to open Hikvision camera device.", ex); + } + } + } + + /// + public void Close() + { + lock (_syncLock) + { + if (!_isConnected) + { + _logger.Information("Hikvision camera not connected, Close() ignored."); + return; + } + + try + { + if (_isGrabbing) + { + StopGrabbingInternal(); + } + + _logger.Information("Closing Hikvision camera connection..."); + _device?.Close(); + _device?.Dispose(); + _device = null; + _isConnected = false; + _cachedCameraInfo = null; + _logger.Information("Hikvision camera connection closed."); + } + catch (Exception ex) when (ex is not CameraException) + { + _device = null; + _isConnected = false; + _isGrabbing = false; + _cachedCameraInfo = null; + _logger.Error(ex, "Error while closing Hikvision camera."); + throw new CameraException("Failed to close Hikvision camera device.", ex); + } + } + } + + /// + public void StartGrabbing() + { + lock (_syncLock) + { + EnsureConnected(); + + if (_isGrabbing) + { + _logger.Information("Already grabbing, StartGrabbing() ignored."); + return; + } + + try + { + _logger.Information("Starting Hikvision grabbing with software trigger..."); + + // 设置缓存节点数 + _device!.StreamGrabber.SetImageNodeNum(5); + + // 注册回调 + _device.StreamGrabber.FrameGrabedEvent += OnFrameGrabbed; + + // 开始采集 + int ret = _device.StreamGrabber.StartGrabbing(); + if (ret != MvError.MV_OK) + { + _device.StreamGrabber.FrameGrabedEvent -= OnFrameGrabbed; + throw new CameraException($"Start grabbing failed: 0x{ret:X8}"); + } + + _isGrabbing = true; + _logger.Information("Hikvision grabbing started."); + } + catch (Exception ex) when (ex is not CameraException) + { + _logger.Error(ex, "Failed to start Hikvision grabbing."); + throw new CameraException("Failed to start grabbing.", ex); + } + } + } + + /// + public void ExecuteSoftwareTrigger() + { + lock (_syncLock) + { + if (!_isGrabbing) + { + throw new InvalidOperationException("Cannot execute software trigger: camera is not grabbing."); + } + + try + { + int ret = _device!.Parameters.SetCommandValue("TriggerSoftware"); + if (ret != MvError.MV_OK) + { + throw new CameraException($"Execute software trigger failed: 0x{ret:X8}"); + } + } + catch (Exception ex) when (ex is not CameraException and not InvalidOperationException) + { + _logger.Error(ex, "Failed to execute software trigger."); + throw new CameraException("Failed to execute software trigger.", ex); + } + } + } + + /// + public void StopGrabbing() + { + lock (_syncLock) + { + if (!_isGrabbing) return; + StopGrabbingInternal(); + } + } + + /// + public double GetExposureTime() + { + lock (_syncLock) + { + EnsureConnected(); + IFloatValue floatValue; + int ret = _device!.Parameters.GetFloatValue("ExposureTime", out floatValue); + if (ret != MvError.MV_OK) + throw new CameraException($"Get ExposureTime failed: 0x{ret:X8}"); + return floatValue.CurValue; + } + } + + /// + public void SetExposureTime(double microseconds) + { + lock (_syncLock) + { + EnsureConnected(); + // 关闭自动曝光 + _device!.Parameters.SetEnumValueByString("ExposureAuto", "Off"); + int ret = _device.Parameters.SetFloatValue("ExposureTime", (float)microseconds); + if (ret != MvError.MV_OK) + throw new CameraException($"Set ExposureTime failed: 0x{ret:X8}"); + _logger.Information("Hikvision exposure time set to {Microseconds} µs.", microseconds); + } + } + + /// + public double GetGain() + { + lock (_syncLock) + { + EnsureConnected(); + IFloatValue floatValue; + int ret = _device!.Parameters.GetFloatValue("Gain", out floatValue); + if (ret != MvError.MV_OK) + throw new CameraException($"Get Gain failed: 0x{ret:X8}"); + return floatValue.CurValue; + } + } + + /// + public void SetGain(double value) + { + lock (_syncLock) + { + EnsureConnected(); + _device!.Parameters.SetEnumValueByString("GainAuto", "Off"); + int ret = _device.Parameters.SetFloatValue("Gain", (float)value); + if (ret != MvError.MV_OK) + throw new CameraException($"Set Gain failed: 0x{ret:X8}"); + _logger.Information("Hikvision gain set to {Value}.", value); + } + } + + /// + public int GetWidth() + { + lock (_syncLock) + { + EnsureConnected(); + IIntValue intValue; + int ret = _device!.Parameters.GetIntValue("Width", out intValue); + if (ret != MvError.MV_OK) + throw new CameraException($"Get Width failed: 0x{ret:X8}"); + return (int)intValue.CurValue; + } + } + + /// + public void SetWidth(int value) + { + lock (_syncLock) + { + EnsureConnected(); + int ret = _device!.Parameters.SetIntValue("Width", value); + if (ret != MvError.MV_OK) + throw new CameraException($"Set Width failed: 0x{ret:X8}"); + _logger.Information("Hikvision width set to {Value}.", value); + } + } + + /// + public int GetHeight() + { + lock (_syncLock) + { + EnsureConnected(); + IIntValue intValue; + int ret = _device!.Parameters.GetIntValue("Height", out intValue); + if (ret != MvError.MV_OK) + throw new CameraException($"Get Height failed: 0x{ret:X8}"); + return (int)intValue.CurValue; + } + } + + /// + public void SetHeight(int value) + { + lock (_syncLock) + { + EnsureConnected(); + int ret = _device!.Parameters.SetIntValue("Height", value); + if (ret != MvError.MV_OK) + throw new CameraException($"Set Height failed: 0x{ret:X8}"); + _logger.Information("Hikvision height set to {Value}.", value); + } + } + + /// + public string GetPixelFormat() + { + lock (_syncLock) + { + EnsureConnected(); + IEnumValue enumValue; + int ret = _device!.Parameters.GetEnumValue("PixelFormat", out enumValue); + if (ret != MvError.MV_OK) + throw new CameraException($"Get PixelFormat failed: 0x{ret:X8}"); + return enumValue.CurEnumEntry.Symbolic; + } + } + + /// + public void SetPixelFormat(string format) + { + lock (_syncLock) + { + EnsureConnected(); + int ret = _device!.Parameters.SetEnumValueByString("PixelFormat", format); + if (ret != MvError.MV_OK) + throw new CameraException($"Set PixelFormat failed: 0x{ret:X8}"); + _logger.Information("Hikvision pixel format set to {Format}.", format); + } + } + + /// + public void Dispose() + { + Close(); + GC.SuppressFinalize(this); + } + + // ══════════════════════════════════════════════════════════════ + // 私有方法 + // ══════════════════════════════════════════════════════════════ + + /// + /// SDK 回调:图像采集完成 + /// + private void OnFrameGrabbed(object? sender, FrameGrabbedEventArgs e) + { + try + { + var frameOut = e.FrameOut; + if (frameOut == null || frameOut.Image == null) + { + _logger.Warning("Hikvision OnFrameGrabbed: FrameOut or Image is null"); + GrabError?.Invoke(this, new GrabErrorEventArgs(-1, "FrameOut or Image is null.")); + return; + } + + var image = frameOut.Image; + int width = (int)image.Width; + int height = (int)image.Height; + int imageSize = (int)image.ImageSize; + string pixelFormat = image.PixelType.ToString(); + + // 提取像素数据 + byte[] pixelData = image.PixelData ?? Array.Empty(); + + _logger.Debug("Hikvision frame: {Width}x{Height}, format={Format}, dataLen={Len}", + width, height, pixelFormat, pixelData.Length); + + if (pixelData.Length == 0) + { + _logger.Warning("Hikvision OnFrameGrabbed: PixelData is empty"); + return; + } + + var args = new ImageGrabbedEventArgs(pixelData, width, height, pixelFormat); + ImageGrabbed?.Invoke(this, args); + } + catch (Exception ex) + { + _logger.Error(ex, "Exception in Hikvision OnFrameGrabbed handler."); + } + } + + private void StopGrabbingInternal() + { + if (!_isGrabbing) return; + + try + { + _device?.StreamGrabber.StopGrabbing(); + if (_device != null) + _device.StreamGrabber.FrameGrabedEvent -= OnFrameGrabbed; + _isGrabbing = false; + _logger.Information("Hikvision grabbing stopped."); + } + catch (Exception ex) when (ex is not CameraException) + { + _isGrabbing = false; + _logger.Error(ex, "Error while stopping Hikvision grabbing."); + throw new CameraException("Failed to stop grabbing.", ex); + } + } + + private void EnsureConnected() + { + if (!_isConnected) + throw new InvalidOperationException("Hikvision camera is not connected. Call Open() first."); + } + + /// + /// 确保 SDK 全局初始化(只调用一次) + /// + private static void EnsureSdkInitialized() + { + if (_sdkInitialized) return; + lock (_sdkInitLock) + { + if (_sdkInitialized) return; + try + { + int ret = SDKSystem.Initialize(); + if (ret != MvError.MV_OK) + { + _logger.Error("Hikvision SDK Initialize failed: 0x{ErrorCode:X8}", ret); + throw new CameraException($"Hikvision SDK Initialize failed: 0x{ret:X8}"); + } + _sdkInitialized = true; + _logger.Information("Hikvision SDK initialized successfully."); + } + catch (Exception ex) when (ex is not CameraException) + { + _logger.Error(ex, "Failed to initialize Hikvision SDK."); + throw new CameraException("Failed to initialize Hikvision SDK.", ex); + } + } + } +} diff --git a/XP.Camera/XP.Camera.csproj b/XP.Camera/XP.Camera.csproj index 1510592..0a4b749 100644 --- a/XP.Camera/XP.Camera.csproj +++ b/XP.Camera/XP.Camera.csproj @@ -7,12 +7,19 @@ enable XP.Camera XP.Camera + true + true ..\ExternalLibraries\Basler.Pylon.dll + + ..\ExternalLibraries\MvCameraControl.Net.dll + true + true + diff --git a/XP.Common/Resources/Resources.en-US.resx b/XP.Common/Resources/Resources.en-US.resx index 3154237..75a2e64 100644 --- a/XP.Common/Resources/Resources.en-US.resx +++ b/XP.Common/Resources/Resources.en-US.resx @@ -1887,4 +1887,122 @@ Reprojection error: {1:F4} pixels Image{0}: {1:F4} pixels + + + + Edge Find Line Fit + + + Place calipers along a search line to detect edge points and fit a line (supports Least Squares and RANSAC) + + + Caliper Count + + + Number of calipers placed evenly along the search line + + + Caliper Width + + + Search length of each caliper (pixels), perpendicular to the search line + + + Edge Polarity + + + Edge direction: BrightToDark, DarkToBright, or Both + + + Edge Threshold + + + Gradient strength threshold; edges below this value are ignored + + + Smoothing Sigma + + + Gaussian smoothing standard deviation for noise suppression (larger = smoother) + + + Fit Method + + + Line fitting algorithm: LeastSquares or RANSAC (robust, rejects outliers) + + + RANSAC Threshold + + + RANSAC inlier distance threshold (pixels); points closer than this to the line are inliers + + + Line Thickness + + + Drawing thickness for result visualization + + + + + Edge Find Circle Fit + + + Place calipers along estimated circle to detect edge points and fit a circle (supports Least Squares and RANSAC) + + + Caliper Count + + + Number of calipers placed evenly around the circle + + + Caliper Width + + + Search length of each caliper along radial direction (pixels) + + + Edge Polarity + + + Edge direction: BrightToDark, DarkToBright, or Both + + + Edge Threshold + + + Gradient strength threshold; edges below this value are ignored + + + Smoothing Sigma + + + Gaussian smoothing standard deviation for noise suppression + + + Search Direction + + + Caliper search direction: Inward (toward center), Outward (away from center), Both + + + Fit Method + + + Circle fitting algorithm: LeastSquares or RANSAC (robust, rejects outliers) + + + RANSAC Threshold + + + RANSAC inlier distance threshold (pixels); points closer than this to the circle are inliers + + + Line Thickness + + + Drawing thickness for result visualization + \ No newline at end of file diff --git a/XP.Common/Resources/Resources.resx b/XP.Common/Resources/Resources.resx index 7f0cc41..e102321 100644 --- a/XP.Common/Resources/Resources.resx +++ b/XP.Common/Resources/Resources.resx @@ -1920,4 +1920,122 @@ 图像{0}: {1:F4} 像素 + + + + 边缘查找拟合直线 + + + 沿搜索线放置卡尺检测边缘点,拟合直线(支持最小二乘和RANSAC) + + + 卡尺数量 + + + 沿搜索线等间距放置的卡尺数量 + + + 卡尺宽度 + + + 每个卡尺的搜索长度(像素),沿垂直于搜索线方向 + + + 边缘极性 + + + 边缘方向:BrightToDark(亮到暗)、DarkToBright(暗到亮)、Both(双向) + + + 边缘阈值 + + + 边缘梯度强度阈值,低于此值的边缘将被忽略 + + + 平滑Sigma + + + 高斯平滑的标准差,用于抑制噪声(越大越平滑) + + + 拟合方法 + + + 直线拟合算法:LeastSquares(最小二乘)、RANSAC(鲁棒拟合,可剔除异常点) + + + RANSAC阈值 + + + RANSAC内点判定距离阈值(像素),点到直线距离小于此值视为内点 + + + 线条粗细 + + + 绘制结果的线条粗细 + + + + + 边缘查找拟合圆 + + + 沿预估圆周放置卡尺检测边缘点,拟合圆(支持最小二乘和RANSAC) + + + 卡尺数量 + + + 沿圆周等角度放置的卡尺数量 + + + 卡尺宽度 + + + 每个卡尺沿径向的搜索长度(像素) + + + 边缘极性 + + + 边缘方向:BrightToDark(亮到暗)、DarkToBright(暗到亮)、Both(双向) + + + 边缘阈值 + + + 边缘梯度强度阈值,低于此值的边缘将被忽略 + + + 平滑Sigma + + + 高斯平滑的标准差,用于抑制噪声 + + + 搜索方向 + + + 卡尺搜索方向:Inward(向圆心)、Outward(背离圆心)、Both(双向) + + + 拟合方法 + + + 圆拟合算法:LeastSquares(最小二乘)、RANSAC(鲁棒拟合) + + + RANSAC阈值 + + + RANSAC内点判定距离阈值(像素),点到圆周距离小于此值视为内点 + + + 线条粗细 + + + 绘制结果的线条粗细 + \ No newline at end of file diff --git a/XP.Common/Resources/Resources.zh-CN.resx b/XP.Common/Resources/Resources.zh-CN.resx index 7dc08f9..f3ce870 100644 --- a/XP.Common/Resources/Resources.zh-CN.resx +++ b/XP.Common/Resources/Resources.zh-CN.resx @@ -1881,4 +1881,122 @@ 图像{0}: {1:F4} 像素 + + + + 边缘查找拟合直线 + + + 沿搜索线放置卡尺检测边缘点,拟合直线(支持最小二乘和RANSAC) + + + 卡尺数量 + + + 沿搜索线等间距放置的卡尺数量 + + + 卡尺宽度 + + + 每个卡尺的搜索长度(像素),沿垂直于搜索线方向 + + + 边缘极性 + + + 边缘方向:BrightToDark(亮到暗)、DarkToBright(暗到亮)、Both(双向) + + + 边缘阈值 + + + 边缘梯度强度阈值,低于此值的边缘将被忽略 + + + 平滑Sigma + + + 高斯平滑的标准差,用于抑制噪声(越大越平滑) + + + 拟合方法 + + + 直线拟合算法:LeastSquares(最小二乘)、RANSAC(鲁棒拟合,可剔除异常点) + + + RANSAC阈值 + + + RANSAC内点判定距离阈值(像素),点到直线距离小于此值视为内点 + + + 线条粗细 + + + 绘制结果的线条粗细 + + + + + 边缘查找拟合圆 + + + 沿预估圆周放置卡尺检测边缘点,拟合圆(支持最小二乘和RANSAC) + + + 卡尺数量 + + + 沿圆周等角度放置的卡尺数量 + + + 卡尺宽度 + + + 每个卡尺沿径向的搜索长度(像素) + + + 边缘极性 + + + 边缘方向:BrightToDark(亮到暗)、DarkToBright(暗到亮)、Both(双向) + + + 边缘阈值 + + + 边缘梯度强度阈值,低于此值的边缘将被忽略 + + + 平滑Sigma + + + 高斯平滑的标准差,用于抑制噪声 + + + 搜索方向 + + + 卡尺搜索方向:Inward(向圆心)、Outward(背离圆心)、Both(双向) + + + 拟合方法 + + + 圆拟合算法:LeastSquares(最小二乘)、RANSAC(鲁棒拟合) + + + RANSAC阈值 + + + RANSAC内点判定距离阈值(像素),点到圆周距离小于此值视为内点 + + + 线条粗细 + + + 绘制结果的线条粗细 + \ No newline at end of file diff --git a/XP.ImageProcessing.Core/Alignment/AlignmentRecipe.cs b/XP.ImageProcessing.Core/Alignment/AlignmentRecipe.cs new file mode 100644 index 0000000..ff33afb --- /dev/null +++ b/XP.ImageProcessing.Core/Alignment/AlignmentRecipe.cs @@ -0,0 +1,21 @@ +namespace XP.ImageProcessing.Core.Alignment; + +/// +/// 示教阶段保存的对齐配方:基准位姿 + 示教图像素坐标下的检测 ROI。 +/// +public sealed class AlignmentRecipe +{ + /// 示教图上的基准位姿(建议示教图自匹配得到,或与模板 ROI 中心 + 角度 0 一致)。 + public Pose2D ReferencePose { get; set; } + + /// 示教图上的 ROI 多边形顶点(至少 3 点)。 + public List RoiPoints { get; set; } = new(); + + /// 将示教 ROI 变换到运行图坐标。 + public Point2D[] TransformRoi(Pose2D measuredPose) + => RoiAlignment.TransformPolygon(RoiPoints, ReferencePose, measuredPose); + + /// 变换为整型顶点,供检测算子注入。 + public (int X, int Y)[] TransformRoiToInt(Pose2D measuredPose) + => RoiAlignment.TransformPolygonToInt(RoiPoints, ReferencePose, measuredPose); +} diff --git a/XP.ImageProcessing.Core/Alignment/Point2D.cs b/XP.ImageProcessing.Core/Alignment/Point2D.cs new file mode 100644 index 0000000..26d1bb1 --- /dev/null +++ b/XP.ImageProcessing.Core/Alignment/Point2D.cs @@ -0,0 +1,4 @@ +namespace XP.ImageProcessing.Core.Alignment; + +/// 图像像素平面上的点(与 WPF/Emgu 解耦)。 +public readonly record struct Point2D(double X, double Y); diff --git a/XP.ImageProcessing.Core/Alignment/Pose2D.cs b/XP.ImageProcessing.Core/Alignment/Pose2D.cs new file mode 100644 index 0000000..5a80ae0 --- /dev/null +++ b/XP.ImageProcessing.Core/Alignment/Pose2D.cs @@ -0,0 +1,18 @@ +namespace XP.ImageProcessing.Core.Alignment; + +/// +/// 图像平面上的刚体位姿:绕 / 旋转 (度)。 +/// 与 TemplateMatchLib 的 CenterX/CenterY/Angle 约定一致。 +/// +public readonly record struct Pose2D(double X, double Y, double AngleDegrees) +{ + /// 示教/标准姿态(角度 0,中心由调用方指定)。 + public static Pose2D IdentityAt(double x, double y) => new(x, y, 0); + + /// + /// 由模板学习 ROI 矩形估计示教位姿中心(pattern 几何中心),角度默认 0。 + /// 更稳妥的做法是在示教图上自匹配得到 。 + /// + public static Pose2D FromTemplateRoiCenter(int roiX, int roiY, int roiWidth, int roiHeight, double angleDegrees = 0) + => new(roiX + roiWidth * 0.5, roiY + roiHeight * 0.5, angleDegrees); +} diff --git a/XP.ImageProcessing.Core/Alignment/RoiAlignment.cs b/XP.ImageProcessing.Core/Alignment/RoiAlignment.cs new file mode 100644 index 0000000..2770573 --- /dev/null +++ b/XP.ImageProcessing.Core/Alignment/RoiAlignment.cs @@ -0,0 +1,96 @@ +namespace XP.ImageProcessing.Core.Alignment; + +/// +/// 将示教图(模板坐标系)上的 ROI 点变换到运行图坐标。 +/// 旋转中心与模板匹配一致:绕 /(pattern 中心)。 +/// +public static class RoiAlignment +{ + /// + /// 刚体变换:示教图点 → 运行图点。 + /// :示教图上的基准位姿; + /// :运行图匹配位姿。 + /// + public static Point2D TransformPoint(Point2D point, Pose2D reference, Pose2D measured) + { + double dTheta = DegreesToRadians(measured.AngleDegrees - reference.AngleDegrees); + double cos = Math.Cos(dTheta); + double sin = Math.Sin(dTheta); + double dx = point.X - reference.X; + double dy = point.Y - reference.Y; + return new Point2D( + measured.X + cos * dx - sin * dy, + measured.Y + sin * dx + cos * dy); + } + + public static Point2D TransformPoint(double x, double y, Pose2D reference, Pose2D measured) + => TransformPoint(new Point2D(x, y), reference, measured); + + /// 变换多边形顶点(顺序不变)。 + public static Point2D[] TransformPolygon(IReadOnlyList templatePoints, Pose2D reference, Pose2D measured) + { + if (templatePoints == null || templatePoints.Count == 0) + return Array.Empty(); + + var result = new Point2D[templatePoints.Count]; + for (int i = 0; i < templatePoints.Count; i++) + result[i] = TransformPoint(templatePoints[i], reference, measured); + return result; + } + + /// 变换后四舍五入为整型顶点,供 BGA 等算子 PolyX/PolyY 注入。 + public static (int X, int Y)[] TransformPolygonToInt( + IReadOnlyList templatePoints, + Pose2D reference, + Pose2D measured) + { + var transformed = TransformPolygon(templatePoints, reference, measured); + var result = new (int X, int Y)[transformed.Length]; + for (int i = 0; i < transformed.Length; i++) + { + result[i] = ( + (int)Math.Round(transformed[i].X, MidpointRounding.AwayFromZero), + (int)Math.Round(transformed[i].Y, MidpointRounding.AwayFromZero)); + } + + return result; + } + + /// 变换轴对齐矩形为四个顶点(左上、右上、右下、左下)。 + public static Point2D[] TransformRect(double x, double y, double width, double height, Pose2D reference, Pose2D measured) + { + var corners = new[] + { + new Point2D(x, y), + new Point2D(x + width, y), + new Point2D(x + width, y + height), + new Point2D(x, y + height) + }; + return TransformPolygon(corners, reference, measured); + } + + /// + /// 校验匹配结果四角质心是否与 Center 一致(用于确认库的中心/角度约定)。 + /// + public static bool IsMatchCenterConsistentWithCorners( + double centerX, + double centerY, + double ltX, + double ltY, + double rtX, + double rtY, + double rbX, + double rbY, + double lbX, + double lbY, + double tolerancePixels = 1.0) + { + double cx = (ltX + rtX + rbX + lbX) * 0.25; + double cy = (ltY + rtY + rbY + lbY) * 0.25; + double dx = cx - centerX; + double dy = cy - centerY; + return dx * dx + dy * dy <= tolerancePixels * tolerancePixels; + } + + private static double DegreesToRadians(double degrees) => degrees * (Math.PI / 180.0); +} diff --git a/XP.ImageProcessing.Processors/定位识别/TemplateMatchAlignmentExtensions.cs b/XP.ImageProcessing.Processors/定位识别/TemplateMatchAlignmentExtensions.cs new file mode 100644 index 0000000..873650f --- /dev/null +++ b/XP.ImageProcessing.Processors/定位识别/TemplateMatchAlignmentExtensions.cs @@ -0,0 +1,27 @@ +using XP.ImageProcessing.Core.Alignment; + +namespace XP.ImageProcessing.Processors; + +/// +/// 将 TemplateMatchLib 匹配结果转换为对齐工具使用的 。 +/// +public static class TemplateMatchAlignmentExtensions +{ + public static Pose2D ToPose2D(this TM_Result result) + => new(result.CenterX, result.CenterY, result.Angle); + + /// 四角质心是否与 Center 一致(容差默认 1 像素)。 + public static bool IsCenterConsistentWithCorners(this TM_Result result, double tolerancePixels = 1.0) + => RoiAlignment.IsMatchCenterConsistentWithCorners( + result.CenterX, + result.CenterY, + result.LtX, + result.LtY, + result.RtX, + result.RtY, + result.RbX, + result.RbY, + result.LbX, + result.LbY, + tolerancePixels); +} diff --git a/XP.ImageProcessing.Processors/定位识别/TemplateMatchNative.cs b/XP.ImageProcessing.Processors/定位识别/TemplateMatchNative.cs index 35e0364..043315d 100644 --- a/XP.ImageProcessing.Processors/定位识别/TemplateMatchNative.cs +++ b/XP.ImageProcessing.Processors/定位识别/TemplateMatchNative.cs @@ -30,6 +30,12 @@ public struct TM_Params /// 是否亚像素估计 (1=是, 0=否) public int UseSubPixel; + /// + /// 开启亚像素且角度容差绝对值超过该值时,托管封装会在调用原生库前关闭亚像素, + /// 以避免部分版本 TemplateMatchLib 在 Debug 下出现 vector 越界断言。 + /// + public const double SubPixelAngleSafetyLimitDegrees = 90.0; + /// /// 创建默认参数 /// @@ -168,9 +174,33 @@ public sealed class TemplateMatcherHandle : IDisposable public TM_Result[] Match(IntPtr srcData, int srcWidth, int srcHeight, int srcStep, TM_Params param) { ThrowIfDisposed(); - var results = new TM_Result[param.MaxCount]; + + // 与库默认一致并对齐已知崩溃组合:Debug 下亚像素 + 大角度容差易触发 vector 越界断言; + // 金字塔最小面积过小也可能与内部层级假设不一致。 + int tw = 0, th = 0, _pyramidLayers = 0; + _ = GetTemplateInfo(out tw, out th, out _pyramidLayers); + int templatePixels = Math.Max(0, tw) * Math.Max(0, th); + + int maxCount = Math.Clamp(param.MaxCount, 1, 100); + int minReduce = (int)Math.Clamp(param.MinReduceArea, 64, 4096); + if (templatePixels >= 512) + minReduce = Math.Max(256, minReduce); + if (templatePixels > 0) + minReduce = Math.Min(minReduce, templatePixels); + minReduce = Math.Max(64, minReduce); + + int useSubPixel = param.UseSubPixel; + if (useSubPixel != 0 && Math.Abs(param.ToleranceAngle) > TM_Params.SubPixelAngleSafetyLimitDegrees) + useSubPixel = 0; + + var p = param; + p.MaxCount = maxCount; + p.MinReduceArea = minReduce; + p.UseSubPixel = useSubPixel; + + var results = new TM_Result[p.MaxCount]; int count = TemplateMatchNative.TM_Match(_handle, srcData, srcWidth, srcHeight, srcStep, - ref param, results, param.MaxCount); + ref p, results, p.MaxCount); if (count <= 0) return Array.Empty(); diff --git a/XP.ImageProcessing.Processors/检测分析/BackgroundDefectAnalyzer.cs b/XP.ImageProcessing.Processors/检测分析/BackgroundDefectAnalyzer.cs new file mode 100644 index 0000000..36a1776 --- /dev/null +++ b/XP.ImageProcessing.Processors/检测分析/BackgroundDefectAnalyzer.cs @@ -0,0 +1,146 @@ +// ============================================================================ +// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. +// 文件名: BackgroundDefectAnalyzer.cs +// 描述: 白底/黑底对比下的缺陷斑点分析(仅 ROI 内计算,不接入流水线算子) +// 算法: Otsu 二值化 → 形态学开运算 → 外轮廓 → 面积过滤 → 轮廓顶点最远弦(物理长度与历史等效直径同一标定:mm/px → μm) +// 作者: 李伟 wei.lw.li@hexagon.com +// ============================================================================ + +using System.Collections.Generic; +using System.Drawing; +using Emgu.CV; +using Emgu.CV.CvEnum; +using Emgu.CV.Structure; +using Emgu.CV.Util; + +namespace XP.ImageProcessing.Processors; + +/// +/// 底色类型:决定 Otsu 后保留的前景是暗区还是亮区。 +/// +public enum BackgroundDefectMode +{ + /// 白底图像上检测偏暗区域(BinaryInv + Otsu)。 + WhiteBackground, + + /// 黑底图像上检测偏亮区域(Binary + Otsu)。 + BlackBackground +} + +/// +/// 单个斑点:轮廓顶点相对于 ROI 左上角; 为轮廓顶点间欧氏距离最大值(微米)。 +/// +public sealed class BackgroundDefectBlob +{ + public Point[] ContourInRoi { get; init; } = Array.Empty(); + public double MaxChordMicrometers { get; init; } + public Point MaxChordEndAInRoi { get; init; } + public Point MaxChordEndBInRoi { get; init; } +} + +/// +/// 在灰度 ROI 上执行底色缺陷斑点检测。调用方负责构造与释放 。 +/// +public static class BackgroundDefectAnalyzer +{ + /// + /// 在 ROI 灰度图上检测斑点。 + /// + /// ROI 灰度图(单通道 8 位)。 + /// 白底或黑底模式。 + /// 轮廓最小面积(像素²),小于此值的轮廓丢弃。 + /// 像素物理尺寸(毫米/像素),用于轮廓最远弦换算为微米。 + /// 形态学开运算核尺寸(奇数,默认 3)。 + public static List DetectBlobs( + Image roiGray, + BackgroundDefectMode mode, + int minAreaPixels = 50, + double mmPerPixel = 0.139, + int morphKernelSize = 3) + { + if (roiGray == null) throw new ArgumentNullException(nameof(roiGray)); + if (minAreaPixels < 1) minAreaPixels = 1; + if (mmPerPixel <= 0) mmPerPixel = 0.139; + if (morphKernelSize < 1) morphKernelSize = 1; + if ((morphKernelSize & 1) == 0) morphKernelSize++; + + int rw = roiGray.Width; + int rh = roiGray.Height; + if (rw < 1 || rh < 1) return new List(); + + var thresholdType = mode == BackgroundDefectMode.WhiteBackground + ? ThresholdType.BinaryInv | ThresholdType.Otsu + : ThresholdType.Binary | ThresholdType.Otsu; + + using var binary = new Image(rw, rh); + CvInvoke.Threshold(roiGray, binary, 0, 255, thresholdType); + + using var kernel = CvInvoke.GetStructuringElement( + ElementShape.Ellipse, new Size(morphKernelSize, morphKernelSize), new Point(-1, -1)); + CvInvoke.MorphologyEx(binary, binary, MorphOp.Open, kernel, new Point(-1, -1), 1, + BorderType.Default, new MCvScalar(0)); + + using var contours = new VectorOfVectorOfPoint(); + using var hierarchy = new Mat(); + CvInvoke.FindContours(binary, contours, hierarchy, RetrType.External, ChainApproxMethod.ChainApproxSimple); + + var result = new List(); + + for (int i = 0; i < contours.Size; i++) + { + double area = CvInvoke.ContourArea(contours[i]); + if (area < minAreaPixels) continue; + + int n = contours[i].Size; + if (n < 2) continue; + + var pts = new Point[n]; + for (int j = 0; j < n; j++) + pts[j] = contours[i][j]; + + MaxChordInPixelSpace(pts, out double maxChordPx, out Point pa, out Point pb); + double maxChordMicrometers = maxChordPx * mmPerPixel * 1000.0; + + result.Add(new BackgroundDefectBlob + { + ContourInRoi = pts, + MaxChordMicrometers = maxChordMicrometers, + MaxChordEndAInRoi = pa, + MaxChordEndBInRoi = pb + }); + } + + return result; + } + + /// 轮廓顶点集合上的最远点对(欧氏距离,像素)。 + private static void MaxChordInPixelSpace(Point[] pts, out double maxChordPx, out Point a, out Point b) + { + maxChordPx = 0; + a = pts[0]; + b = pts.Length > 1 ? pts[1] : pts[0]; + long bestSq = 0; + int bestI = 0, bestJ = 1; + int n = pts.Length; + for (int i = 0; i < n; i++) + { + int iX = pts[i].X, iY = pts[i].Y; + for (int j = i + 1; j < n; j++) + { + long dx = iX - pts[j].X; + long dy = iY - pts[j].Y; + long sq = dx * dx + dy * dy; + if (sq > bestSq) + { + bestSq = sq; + bestI = i; + bestJ = j; + } + } + } + + a = pts[bestI]; + b = pts[bestJ]; + maxChordPx = Math.Sqrt(bestSq); + } +} diff --git a/XP.ImageProcessing.Processors/检测分析/EdgeCircleFitProcessor.cs b/XP.ImageProcessing.Processors/检测分析/EdgeCircleFitProcessor.cs new file mode 100644 index 0000000..2386c81 --- /dev/null +++ b/XP.ImageProcessing.Processors/检测分析/EdgeCircleFitProcessor.cs @@ -0,0 +1,582 @@ +// ============================================================================ +// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. +// 文件名: EdgeCircleFitProcessor.cs +// 描述: 边缘查找拟合圆算子 +// 功能: +// - 沿预估圆周等角度放置卡尺,每个卡尺沿径向搜索边缘点 +// - 支持亚像素精度(抛物线插值) +// - 支持边缘极性选择和搜索方向(向内/向外) +// - 使用最小二乘或RANSAC算法拟合圆 +// - 输出拟合圆参数、边缘点、内点/外点、拟合误差 +// 算法: 卡尺边缘检测 + 最小二乘/RANSAC圆拟合 +// 作者: 李伟 wei.lw.li@hexagon.com +// ============================================================================ + +using Emgu.CV; +using Emgu.CV.Structure; +using XP.ImageProcessing.Core; +using Serilog; +using System.Drawing; + +namespace XP.ImageProcessing.Processors; + +/// +/// 圆拟合结果 +/// +public class CircleFitResult +{ + /// 拟合是否成功 + public bool Success { get; set; } + + /// 拟合圆心X + public double CenterX { get; set; } + + /// 拟合圆心Y + public double CenterY { get; set; } + + /// 拟合半径 + public double Radius { get; set; } + + /// 所有检测到的边缘点 + public List EdgePoints { get; set; } = new(); + + /// 内点列表 + public List Inliers { get; set; } = new(); + + /// 外点列表 + public List Outliers { get; set; } = new(); + + /// 平均拟合误差(像素) + public double FitError { get; set; } + + /// 有效边缘点数 + public int EdgePointCount { get; set; } +} + +/// +/// 边缘查找拟合圆算子 - 沿预估圆周放置卡尺检测边缘点并拟合圆 +/// +public class EdgeCircleFitProcessor : ImageProcessorBase +{ + private static readonly ILogger _logger = Log.ForContext(); + private static readonly Random _random = new(); + + public EdgeCircleFitProcessor() + { + Name = LocalizationHelper.GetString("EdgeCircleFitProcessor_Name"); + Description = LocalizationHelper.GetString("EdgeCircleFitProcessor_Description"); + } + + protected override void InitializeParameters() + { + // ── 预估圆参数(由UI交互注入,不可见) ── + Parameters.Add("CenterX", new ProcessorParameter( + "CenterX", "CenterX", typeof(int), 200, null, null, "") { IsVisible = false }); + Parameters.Add("CenterY", new ProcessorParameter( + "CenterY", "CenterY", typeof(int), 200, null, null, "") { IsVisible = false }); + Parameters.Add("Radius", new ProcessorParameter( + "Radius", "Radius", typeof(int), 100, null, null, "") { IsVisible = false }); + + // ── 卡尺参数 ── + Parameters.Add("CaliperCount", new ProcessorParameter( + "CaliperCount", + LocalizationHelper.GetString("EdgeCircleFitProcessor_CaliperCount"), + typeof(int), 36, 3, 360, + LocalizationHelper.GetString("EdgeCircleFitProcessor_CaliperCount_Desc"))); + + Parameters.Add("CaliperWidth", new ProcessorParameter( + "CaliperWidth", + LocalizationHelper.GetString("EdgeCircleFitProcessor_CaliperWidth"), + typeof(int), 40, 5, 500, + LocalizationHelper.GetString("EdgeCircleFitProcessor_CaliperWidth_Desc"))); + + // ── 边缘检测参数 ── + Parameters.Add("EdgePolarity", new ProcessorParameter( + "EdgePolarity", + LocalizationHelper.GetString("EdgeCircleFitProcessor_EdgePolarity"), + typeof(string), "Both", null, null, + LocalizationHelper.GetString("EdgeCircleFitProcessor_EdgePolarity_Desc"), + new string[] { "BrightToDark", "DarkToBright", "Both" })); + + Parameters.Add("EdgeThreshold", new ProcessorParameter( + "EdgeThreshold", + LocalizationHelper.GetString("EdgeCircleFitProcessor_EdgeThreshold"), + typeof(int), 20, 1, 255, + LocalizationHelper.GetString("EdgeCircleFitProcessor_EdgeThreshold_Desc"))); + + Parameters.Add("Sigma", new ProcessorParameter( + "Sigma", + LocalizationHelper.GetString("EdgeCircleFitProcessor_Sigma"), + typeof(double), 1.0, 0.1, 10.0, + LocalizationHelper.GetString("EdgeCircleFitProcessor_Sigma_Desc"))); + + Parameters.Add("SearchDirection", new ProcessorParameter( + "SearchDirection", + LocalizationHelper.GetString("EdgeCircleFitProcessor_SearchDirection"), + typeof(string), "Both", null, null, + LocalizationHelper.GetString("EdgeCircleFitProcessor_SearchDirection_Desc"), + new string[] { "Inward", "Outward", "Both" })); + + // ── 拟合参数 ── + Parameters.Add("FitMethod", new ProcessorParameter( + "FitMethod", + LocalizationHelper.GetString("EdgeCircleFitProcessor_FitMethod"), + typeof(string), "RANSAC", null, null, + LocalizationHelper.GetString("EdgeCircleFitProcessor_FitMethod_Desc"), + new string[] { "LeastSquares", "RANSAC" })); + + Parameters.Add("RansacThreshold", new ProcessorParameter( + "RansacThreshold", + LocalizationHelper.GetString("EdgeCircleFitProcessor_RansacThreshold"), + typeof(double), 2.0, 0.5, 20.0, + LocalizationHelper.GetString("EdgeCircleFitProcessor_RansacThreshold_Desc"))); + + Parameters.Add("Thickness", new ProcessorParameter( + "Thickness", + LocalizationHelper.GetString("EdgeCircleFitProcessor_Thickness"), + typeof(int), 2, 1, 10, + LocalizationHelper.GetString("EdgeCircleFitProcessor_Thickness_Desc"))); + } + + public override Image Process(Image inputImage) + { + int centerX = GetParameter("CenterX"); + int centerY = GetParameter("CenterY"); + int radius = GetParameter("Radius"); + int caliperCount = GetParameter("CaliperCount"); + int caliperWidth = GetParameter("CaliperWidth"); + string edgePolarity = GetParameter("EdgePolarity"); + int edgeThreshold = GetParameter("EdgeThreshold"); + double sigma = GetParameter("Sigma"); + string searchDirection = GetParameter("SearchDirection"); + string fitMethod = GetParameter("FitMethod"); + double ransacThreshold = GetParameter("RansacThreshold"); + + OutputData.Clear(); + + _logger.Debug( + "EdgeCircleFit started: Center=({CX},{CY}), R={R}, Calipers={Count}, Width={Width}", + centerX, centerY, radius, caliperCount, caliperWidth); + + if (radius < 5) + { + _logger.Warning("Radius too small for circle fitting"); + OutputData["CircleFitResult"] = new CircleFitResult { Success = false }; + return inputImage.Clone(); + } + + // 沿圆周等角度放置卡尺 + var edgePoints = new List(); + double angleStep = 2.0 * Math.PI / caliperCount; + + for (int i = 0; i < caliperCount; i++) + { + double angle = angleStep * i; + // 圆周上的采样点 + double sampleX = centerX + radius * Math.Cos(angle); + double sampleY = centerY + radius * Math.Sin(angle); + + // 径向方向(从圆心指向外) + double dirX = Math.Cos(angle); + double dirY = Math.Sin(angle); + + // 根据搜索方向确定卡尺搜索方向 + double searchDirX, searchDirY; + if (searchDirection == "Inward") + { + searchDirX = -dirX; + searchDirY = -dirY; + } + else if (searchDirection == "Outward") + { + searchDirX = dirX; + searchDirY = dirY; + } + else // Both: 搜索方向为径向(从内到外),卡尺中心在圆周上 + { + searchDirX = dirX; + searchDirY = dirY; + } + + var edgePoint = FindEdgeInCaliper( + inputImage, sampleX, sampleY, searchDirX, searchDirY, + caliperWidth, edgePolarity, edgeThreshold, sigma, i); + + if (edgePoint != null) + { + edgePoints.Add(edgePoint); + } + } + + _logger.Debug("Found {Count} edge points from {Total} calipers", edgePoints.Count, caliperCount); + + // 拟合圆 + var result = FitCircle(edgePoints, fitMethod, ransacThreshold); + + // 存储输出 + OutputData["CircleFitResult"] = result; + OutputData["EdgePoints"] = edgePoints.Select(p => p.Position).ToArray(); + OutputData["EdgePointCount"] = edgePoints.Count; + OutputData["Thickness"] = GetParameter("Thickness"); + + if (result.Success) + { + OutputData["FittedCenterX"] = result.CenterX; + OutputData["FittedCenterY"] = result.CenterY; + OutputData["FittedRadius"] = result.Radius; + OutputData["InlierPoints"] = result.Inliers.ToArray(); + OutputData["OutlierPoints"] = result.Outliers.ToArray(); + OutputData["FitError"] = result.FitError; + + _logger.Information( + "EdgeCircleFit completed: Center=({CX:F2},{CY:F2}), R={R:F2}, Inliers={Inliers}/{Total}, Error={Error:F3}px", + result.CenterX, result.CenterY, result.Radius, + result.Inliers.Count, edgePoints.Count, result.FitError); + } + else + { + _logger.Warning("EdgeCircleFit failed: insufficient edge points"); + } + + return inputImage.Clone(); + } + + // ══════════════════════════════════════════════════════════════ + // 卡尺边缘检测(复用直线拟合中的逻辑) + // ══════════════════════════════════════════════════════════════ + + private EdgePointInfo? FindEdgeInCaliper( + Image image, + double centerX, double centerY, + double dirX, double dirY, + int caliperWidth, string polarity, + int threshold, double sigma, int caliperIndex) + { + int halfWidth = caliperWidth / 2; + int profileLength = caliperWidth; + + var profile = new double[profileLength]; + int validCount = 0; + + for (int i = 0; i < profileLength; i++) + { + double offset = i - halfWidth; + double px = centerX + dirX * offset; + double py = centerY + dirY * offset; + + int ix = (int)Math.Round(px); + int iy = (int)Math.Round(py); + + if (ix >= 0 && ix < image.Width && iy >= 0 && iy < image.Height) + { + profile[i] = image.Data[iy, ix, 0]; + validCount++; + } + else + { + profile[i] = 0; + } + } + + if (validCount < profileLength * 0.5) + return null; + + if (sigma > 0.1) + profile = GaussianSmooth1D(profile, sigma); + + var derivative = new double[profileLength]; + for (int i = 1; i < profileLength - 1; i++) + derivative[i] = (profile[i + 1] - profile[i - 1]) / 2.0; + + int bestIdx = -1; + double bestStrength = 0; + + for (int i = 2; i < profileLength - 2; i++) + { + double strength = derivative[i]; + bool validPolarity = polarity switch + { + "BrightToDark" => strength < 0, + "DarkToBright" => strength > 0, + _ => true + }; + + if (!validPolarity) continue; + + double absStrength = Math.Abs(strength); + if (absStrength >= threshold && absStrength > bestStrength) + { + bestStrength = absStrength; + bestIdx = i; + } + } + + if (bestIdx < 0) + return null; + + // 亚像素插值 + double subPixelOffset = 0; + if (bestIdx > 0 && bestIdx < profileLength - 1) + { + double left = Math.Abs(derivative[bestIdx - 1]); + double center = Math.Abs(derivative[bestIdx]); + double right = Math.Abs(derivative[bestIdx + 1]); + double denom = 2.0 * (2.0 * center - left - right); + if (Math.Abs(denom) > 1e-6) + { + subPixelOffset = (left - right) / denom; + subPixelOffset = Math.Clamp(subPixelOffset, -0.5, 0.5); + } + } + + double edgeOffset = (bestIdx + subPixelOffset) - halfWidth; + float edgeX = (float)(centerX + dirX * edgeOffset); + float edgeY = (float)(centerY + dirY * edgeOffset); + + return new EdgePointInfo + { + Position = new PointF(edgeX, edgeY), + Strength = bestStrength, + CaliperIndex = caliperIndex, + IsInlier = true + }; + } + + private static double[] GaussianSmooth1D(double[] data, double sigma) + { + int kernelRadius = (int)Math.Ceiling(sigma * 3); + int kernelSize = kernelRadius * 2 + 1; + var kernel = new double[kernelSize]; + double sum = 0; + + for (int i = 0; i < kernelSize; i++) + { + double x = i - kernelRadius; + kernel[i] = Math.Exp(-x * x / (2.0 * sigma * sigma)); + sum += kernel[i]; + } + for (int i = 0; i < kernelSize; i++) + kernel[i] /= sum; + + var result = new double[data.Length]; + for (int i = 0; i < data.Length; i++) + { + double val = 0, wSum = 0; + for (int k = 0; k < kernelSize; k++) + { + int idx = i + k - kernelRadius; + if (idx >= 0 && idx < data.Length) + { + val += data[idx] * kernel[k]; + wSum += kernel[k]; + } + } + result[i] = wSum > 0 ? val / wSum : data[i]; + } + return result; + } + + // ══════════════════════════════════════════════════════════════ + // 圆拟合 + // ══════════════════════════════════════════════════════════════ + + private CircleFitResult FitCircle(List edgePoints, string method, double ransacThreshold) + { + var result = new CircleFitResult(); + + if (edgePoints.Count < 3) + { + result.Success = false; + return result; + } + + if (method == "RANSAC" && edgePoints.Count >= 4) + return FitCircleRANSAC(edgePoints, ransacThreshold); + else + return FitCircleLeastSquares(edgePoints); + } + + /// + /// 最小二乘拟合圆(Kasa方法) + /// 将 (x-a)² + (y-b)² = r² 展开为: x² + y² = 2ax + 2by + (r²-a²-b²) + /// 令 c = r²-a²-b², 线性方程: 2ax + 2by + c = x² + y² + /// + private CircleFitResult FitCircleLeastSquares(List edgePoints) + { + var points = edgePoints.Select(p => p.Position).ToArray(); + var (cx, cy, r) = KasaFit(points); + + var result = new CircleFitResult + { + Success = true, + CenterX = cx, + CenterY = cy, + Radius = r, + Inliers = points.ToList(), + Outliers = new List(), + EdgePointCount = edgePoints.Count, + EdgePoints = edgePoints + }; + + foreach (var ep in edgePoints) + ep.IsInlier = true; + + result.FitError = ComputeCircleFitError(points, cx, cy, r); + return result; + } + + /// + /// RANSAC 圆拟合 + /// + private CircleFitResult FitCircleRANSAC(List edgePoints, double threshold) + { + var result = new CircleFitResult(); + var points = edgePoints.Select(p => p.Position).ToArray(); + int n = points.Length; + + int maxIterations = Math.Min(2000, n * (n - 1) * (n - 2) / 6); + int bestInlierCount = 0; + double bestCx = 0, bestCy = 0, bestR = 0; + List bestInlierIndices = new(); + + for (int iter = 0; iter < maxIterations; iter++) + { + // 随机选3个点 + int i1 = _random.Next(n), i2 = _random.Next(n), i3 = _random.Next(n); + if (i1 == i2 || i1 == i3 || i2 == i3) continue; + + var (cx, cy, r) = FitCircleFrom3Points(points[i1], points[i2], points[i3]); + if (r <= 0 || double.IsNaN(r)) continue; + + // 统计内点 + var inlierIndices = new List(); + for (int i = 0; i < n; i++) + { + double dist = Math.Abs(Distance(points[i], cx, cy) - r); + if (dist <= threshold) + inlierIndices.Add(i); + } + + if (inlierIndices.Count > bestInlierCount) + { + bestInlierCount = inlierIndices.Count; + bestInlierIndices = inlierIndices; + + // 用所有内点重新拟合 + var inlierPoints = inlierIndices.Select(i => points[i]).ToArray(); + (bestCx, bestCy, bestR) = KasaFit(inlierPoints); + } + + if (bestInlierCount > n * 0.95) + break; + } + + if (bestInlierCount < 3) + { + result.Success = false; + return result; + } + + result.Success = true; + result.CenterX = bestCx; + result.CenterY = bestCy; + result.Radius = bestR; + + var inlierSet = new HashSet(bestInlierIndices); + for (int i = 0; i < n; i++) + { + if (inlierSet.Contains(i)) + { + result.Inliers.Add(points[i]); + edgePoints[i].IsInlier = true; + } + else + { + result.Outliers.Add(points[i]); + edgePoints[i].IsInlier = false; + } + } + + result.FitError = ComputeCircleFitError(result.Inliers.ToArray(), bestCx, bestCy, bestR); + result.EdgePointCount = edgePoints.Count; + result.EdgePoints = edgePoints; + + return result; + } + + /// + /// Kasa 最小二乘圆拟合 + /// + private static (double cx, double cy, double r) KasaFit(PointF[] points) + { + int n = points.Length; + if (n < 3) return (0, 0, 0); + + // 构建线性方程组: A * [a, b, c]^T = B + // 其中 2*a*xi + 2*b*yi + c = xi² + yi² + double sumX = 0, sumY = 0, sumX2 = 0, sumY2 = 0; + double sumXY = 0, sumX3 = 0, sumY3 = 0, sumX2Y = 0, sumXY2 = 0; + + for (int i = 0; i < n; i++) + { + double x = points[i].X, y = points[i].Y; + double x2 = x * x, y2 = y * y; + sumX += x; sumY += y; + sumX2 += x2; sumY2 += y2; + sumXY += x * y; + sumX3 += x2 * x; sumY3 += y2 * y; + sumX2Y += x2 * y; sumXY2 += x * y2; + } + + double A = n * sumX2 - sumX * sumX; + double B = n * sumXY - sumX * sumY; + double C = n * sumY2 - sumY * sumY; + double D = 0.5 * (n * (sumX3 + sumXY2) - sumX * (sumX2 + sumY2)); + double E = 0.5 * (n * (sumX2Y + sumY3) - sumY * (sumX2 + sumY2)); + + double denom = A * C - B * B; + if (Math.Abs(denom) < 1e-10) + return (0, 0, 0); + + double cx = (D * C - B * E) / denom; + double cy = (A * E - B * D) / denom; + double r = Math.Sqrt((sumX2 + sumY2 - 2 * cx * sumX - 2 * cy * sumY) / n + cx * cx + cy * cy); + + return (cx, cy, r); + } + + /// + /// 3点拟合圆 + /// + private static (double cx, double cy, double r) FitCircleFrom3Points(PointF p1, PointF p2, PointF p3) + { + double ax = p1.X, ay = p1.Y; + double bx = p2.X, by = p2.Y; + double cx = p3.X, cy = p3.Y; + + double d = 2 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by)); + if (Math.Abs(d) < 1e-10) + return (0, 0, -1); + + double ux = ((ax * ax + ay * ay) * (by - cy) + (bx * bx + by * by) * (cy - ay) + (cx * cx + cy * cy) * (ay - by)) / d; + double uy = ((ax * ax + ay * ay) * (cx - bx) + (bx * bx + by * by) * (ax - cx) + (cx * cx + cy * cy) * (bx - ax)) / d; + double r = Math.Sqrt((ax - ux) * (ax - ux) + (ay - uy) * (ay - uy)); + + return (ux, uy, r); + } + + private static double Distance(PointF p, double cx, double cy) + { + double dx = p.X - cx, dy = p.Y - cy; + return Math.Sqrt(dx * dx + dy * dy); + } + + private static double ComputeCircleFitError(PointF[] points, double cx, double cy, double r) + { + if (points.Length == 0) return 0; + double total = 0; + foreach (var p in points) + total += Math.Abs(Distance(p, cx, cy) - r); + return total / points.Length; + } +} diff --git a/XP.ImageProcessing.Processors/检测分析/EdgeLineFitProcessor.cs b/XP.ImageProcessing.Processors/检测分析/EdgeLineFitProcessor.cs new file mode 100644 index 0000000..0fad857 --- /dev/null +++ b/XP.ImageProcessing.Processors/检测分析/EdgeLineFitProcessor.cs @@ -0,0 +1,638 @@ +// ============================================================================ +// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. +// 文件名: EdgeLineFitProcessor.cs +// 描述: 边缘查找拟合直线算子 +// 功能: +// - 沿用户定义的搜索线等间距放置多个卡尺(Caliper) +// - 在每个卡尺内沿垂直方向提取灰度投影并求导,定位边缘点 +// - 支持亚像素精度(抛物线插值) +// - 支持边缘极性选择(亮到暗/暗到亮/双向) +// - 使用最小二乘或RANSAC算法拟合直线 +// - 输出拟合直线参数、边缘点、内点/外点、拟合误差 +// 算法: 卡尺边缘检测 + 最小二乘/RANSAC直线拟合 +// 作者: 李伟 wei.lw.li@hexagon.com +// ============================================================================ + +using Emgu.CV; +using Emgu.CV.CvEnum; +using Emgu.CV.Structure; +using Emgu.CV.Util; +using XP.ImageProcessing.Core; +using Serilog; +using System.Drawing; + +namespace XP.ImageProcessing.Processors; + +/// +/// 边缘点信息 +/// +public class EdgePointInfo +{ + /// 边缘点坐标(亚像素) + public PointF Position { get; set; } + + /// 边缘强度(梯度绝对值) + public double Strength { get; set; } + + /// 卡尺索引 + public int CaliperIndex { get; set; } + + /// 是否为拟合内点 + public bool IsInlier { get; set; } = true; +} + +/// +/// 直线拟合结果 +/// +public class LineFitResult +{ + /// 拟合是否成功 + public bool Success { get; set; } + + /// 直线方向向量 (vx, vy) + public PointF Direction { get; set; } + + /// 直线上一点 (x0, y0) + public PointF PointOnLine { get; set; } + + /// 直线角度(度,相对于X轴) + public double AngleDegrees { get; set; } + + /// 直线端点1(用于绘制) + public PointF Endpoint1 { get; set; } + + /// 直线端点2(用于绘制) + public PointF Endpoint2 { get; set; } + + /// 所有检测到的边缘点 + public List EdgePoints { get; set; } = new(); + + /// 内点列表 + public List Inliers { get; set; } = new(); + + /// 外点列表 + public List Outliers { get; set; } = new(); + + /// 平均拟合误差(像素) + public double FitError { get; set; } + + /// 有效边缘点数 + public int EdgePointCount { get; set; } +} + +/// +/// 边缘查找拟合直线算子 - 使用卡尺法检测边缘点并拟合直线 +/// +public class EdgeLineFitProcessor : ImageProcessorBase +{ + private static readonly ILogger _logger = Log.ForContext(); + private static readonly Random _random = new(); + + public EdgeLineFitProcessor() + { + Name = LocalizationHelper.GetString("EdgeLineFitProcessor_Name"); + Description = LocalizationHelper.GetString("EdgeLineFitProcessor_Description"); + } + + protected override void InitializeParameters() + { + // ── 搜索线起止点(由UI交互控件注入,不可见) ── + Parameters.Add("StartX", new ProcessorParameter( + "StartX", "StartX", typeof(int), 100, null, null, "") { IsVisible = false }); + Parameters.Add("StartY", new ProcessorParameter( + "StartY", "StartY", typeof(int), 200, null, null, "") { IsVisible = false }); + Parameters.Add("EndX", new ProcessorParameter( + "EndX", "EndX", typeof(int), 400, null, null, "") { IsVisible = false }); + Parameters.Add("EndY", new ProcessorParameter( + "EndY", "EndY", typeof(int), 200, null, null, "") { IsVisible = false }); + + // ── 卡尺参数 ── + Parameters.Add("CaliperCount", new ProcessorParameter( + "CaliperCount", + LocalizationHelper.GetString("EdgeLineFitProcessor_CaliperCount"), + typeof(int), 20, 3, 200, + LocalizationHelper.GetString("EdgeLineFitProcessor_CaliperCount_Desc"))); + + Parameters.Add("CaliperWidth", new ProcessorParameter( + "CaliperWidth", + LocalizationHelper.GetString("EdgeLineFitProcessor_CaliperWidth"), + typeof(int), 40, 5, 500, + LocalizationHelper.GetString("EdgeLineFitProcessor_CaliperWidth_Desc"))); + + // ── 边缘检测参数 ── + Parameters.Add("EdgePolarity", new ProcessorParameter( + "EdgePolarity", + LocalizationHelper.GetString("EdgeLineFitProcessor_EdgePolarity"), + typeof(string), "Both", null, null, + LocalizationHelper.GetString("EdgeLineFitProcessor_EdgePolarity_Desc"), + new string[] { "BrightToDark", "DarkToBright", "Both" })); + + Parameters.Add("EdgeThreshold", new ProcessorParameter( + "EdgeThreshold", + LocalizationHelper.GetString("EdgeLineFitProcessor_EdgeThreshold"), + typeof(int), 30, 1, 255, + LocalizationHelper.GetString("EdgeLineFitProcessor_EdgeThreshold_Desc"))); + + Parameters.Add("Sigma", new ProcessorParameter( + "Sigma", + LocalizationHelper.GetString("EdgeLineFitProcessor_Sigma"), + typeof(double), 1.0, 0.1, 10.0, + LocalizationHelper.GetString("EdgeLineFitProcessor_Sigma_Desc"))); + + // ── 拟合参数 ── + Parameters.Add("FitMethod", new ProcessorParameter( + "FitMethod", + LocalizationHelper.GetString("EdgeLineFitProcessor_FitMethod"), + typeof(string), "RANSAC", null, null, + LocalizationHelper.GetString("EdgeLineFitProcessor_FitMethod_Desc"), + new string[] { "LeastSquares", "RANSAC" })); + + Parameters.Add("RansacThreshold", new ProcessorParameter( + "RansacThreshold", + LocalizationHelper.GetString("EdgeLineFitProcessor_RansacThreshold"), + typeof(double), 2.0, 0.5, 20.0, + LocalizationHelper.GetString("EdgeLineFitProcessor_RansacThreshold_Desc"))); + + Parameters.Add("Thickness", new ProcessorParameter( + "Thickness", + LocalizationHelper.GetString("EdgeLineFitProcessor_Thickness"), + typeof(int), 2, 1, 10, + LocalizationHelper.GetString("EdgeLineFitProcessor_Thickness_Desc"))); + } + + public override Image Process(Image inputImage) + { + // 读取参数 + int startX = GetParameter("StartX"); + int startY = GetParameter("StartY"); + int endX = GetParameter("EndX"); + int endY = GetParameter("EndY"); + int caliperCount = GetParameter("CaliperCount"); + int caliperWidth = GetParameter("CaliperWidth"); + string edgePolarity = GetParameter("EdgePolarity"); + int edgeThreshold = GetParameter("EdgeThreshold"); + double sigma = GetParameter("Sigma"); + string fitMethod = GetParameter("FitMethod"); + double ransacThreshold = GetParameter("RansacThreshold"); + int thickness = GetParameter("Thickness"); + + OutputData.Clear(); + + _logger.Debug( + "EdgeLineFit started: Search({StartX},{StartY})->({EndX},{EndY}), Calipers={Count}, Width={Width}, Polarity={Polarity}", + startX, startY, endX, endY, caliperCount, caliperWidth, edgePolarity); + + // 计算搜索线方向和垂直方向 + double searchDx = endX - startX; + double searchDy = endY - startY; + double searchLen = Math.Sqrt(searchDx * searchDx + searchDy * searchDy); + + if (searchLen < 1.0) + { + _logger.Warning("Search line too short, cannot perform edge detection"); + OutputData["LineFitResult"] = new LineFitResult { Success = false }; + return inputImage.Clone(); + } + + // 搜索线单位方向 + double ux = searchDx / searchLen; + double uy = searchDy / searchLen; + + // 垂直于搜索线的方向(卡尺搜索方向) + double perpX = -uy; + double perpY = ux; + + // 沿搜索线等间距放置卡尺 + var edgePoints = new List(); + double step = searchLen / (caliperCount + 1); + + for (int i = 0; i < caliperCount; i++) + { + // 卡尺中心点 + double cx = startX + ux * step * (i + 1); + double cy = startY + uy * step * (i + 1); + + // 在卡尺内沿垂直方向提取灰度剖面 + var edgePoint = FindEdgeInCaliper( + inputImage, cx, cy, perpX, perpY, + caliperWidth, edgePolarity, edgeThreshold, sigma, i); + + if (edgePoint != null) + { + edgePoints.Add(edgePoint); + } + } + + _logger.Debug("Found {Count} edge points from {Total} calipers", edgePoints.Count, caliperCount); + + // 拟合直线 + var result = FitLine(edgePoints, fitMethod, ransacThreshold, inputImage.Size); + + // 存储输出数据 + OutputData["LineFitResult"] = result; + OutputData["EdgePoints"] = edgePoints.Select(p => p.Position).ToArray(); + OutputData["EdgePointCount"] = edgePoints.Count; + OutputData["Thickness"] = thickness; + + if (result.Success) + { + OutputData["FittedLineDirection"] = result.Direction; + OutputData["FittedLinePoint"] = result.PointOnLine; + OutputData["LineAngle"] = result.AngleDegrees; + OutputData["LineEndpoint1"] = result.Endpoint1; + OutputData["LineEndpoint2"] = result.Endpoint2; + OutputData["InlierPoints"] = result.Inliers.ToArray(); + OutputData["OutlierPoints"] = result.Outliers.ToArray(); + OutputData["FitError"] = result.FitError; + + _logger.Information( + "EdgeLineFit completed: Angle={Angle:F2}°, Inliers={Inliers}/{Total}, Error={Error:F3}px", + result.AngleDegrees, result.Inliers.Count, edgePoints.Count, result.FitError); + } + else + { + _logger.Warning("EdgeLineFit failed: insufficient edge points for line fitting"); + } + + // 搜索区域信息(供UI绘制) + OutputData["SearchStart"] = new PointF(startX, startY); + OutputData["SearchEnd"] = new PointF(endX, endY); + OutputData["CaliperWidth"] = caliperWidth; + OutputData["CaliperCount"] = caliperCount; + OutputData["PerpDirection"] = new PointF((float)perpX, (float)perpY); + + return inputImage.Clone(); + } + + /// + /// 在单个卡尺内查找边缘点 + /// + private EdgePointInfo? FindEdgeInCaliper( + Image image, + double centerX, double centerY, + double perpX, double perpY, + int caliperWidth, string polarity, + int threshold, double sigma, int caliperIndex) + { + int halfWidth = caliperWidth / 2; + int profileLength = caliperWidth; + + // 提取灰度剖面 + var profile = new double[profileLength]; + int validCount = 0; + + for (int i = 0; i < profileLength; i++) + { + double offset = i - halfWidth; + double px = centerX + perpX * offset; + double py = centerY + perpY * offset; + + int ix = (int)Math.Round(px); + int iy = (int)Math.Round(py); + + if (ix >= 0 && ix < image.Width && iy >= 0 && iy < image.Height) + { + profile[i] = image.Data[iy, ix, 0]; + validCount++; + } + else + { + profile[i] = 0; + } + } + + if (validCount < profileLength * 0.5) + return null; + + // 高斯平滑 + if (sigma > 0.1) + { + profile = GaussianSmooth1D(profile, sigma); + } + + // 求一阶导数 + var derivative = new double[profileLength]; + for (int i = 1; i < profileLength - 1; i++) + { + derivative[i] = (profile[i + 1] - profile[i - 1]) / 2.0; + } + + // 根据极性查找最强边缘 + int bestIdx = -1; + double bestStrength = 0; + + for (int i = 2; i < profileLength - 2; i++) + { + double strength = derivative[i]; + bool validPolarity = polarity switch + { + "BrightToDark" => strength < 0, // 亮到暗:导数为负 + "DarkToBright" => strength > 0, // 暗到亮:导数为正 + _ => true // Both:任意方向 + }; + + if (!validPolarity) continue; + + double absStrength = Math.Abs(strength); + if (absStrength >= threshold && absStrength > bestStrength) + { + bestStrength = absStrength; + bestIdx = i; + } + } + + if (bestIdx < 0) + return null; + + // 亚像素精度:抛物线插值 + double subPixelOffset = 0; + if (bestIdx > 0 && bestIdx < profileLength - 1) + { + double left = Math.Abs(derivative[bestIdx - 1]); + double center = Math.Abs(derivative[bestIdx]); + double right = Math.Abs(derivative[bestIdx + 1]); + double denom = 2.0 * (2.0 * center - left - right); + if (Math.Abs(denom) > 1e-6) + { + subPixelOffset = (left - right) / denom; + subPixelOffset = Math.Clamp(subPixelOffset, -0.5, 0.5); + } + } + + double edgeOffset = (bestIdx + subPixelOffset) - halfWidth; + float edgeX = (float)(centerX + perpX * edgeOffset); + float edgeY = (float)(centerY + perpY * edgeOffset); + + return new EdgePointInfo + { + Position = new PointF(edgeX, edgeY), + Strength = bestStrength, + CaliperIndex = caliperIndex, + IsInlier = true + }; + } + + /// + /// 一维高斯平滑 + /// + private static double[] GaussianSmooth1D(double[] data, double sigma) + { + int kernelRadius = (int)Math.Ceiling(sigma * 3); + int kernelSize = kernelRadius * 2 + 1; + var kernel = new double[kernelSize]; + double sum = 0; + + for (int i = 0; i < kernelSize; i++) + { + double x = i - kernelRadius; + kernel[i] = Math.Exp(-x * x / (2.0 * sigma * sigma)); + sum += kernel[i]; + } + for (int i = 0; i < kernelSize; i++) + kernel[i] /= sum; + + var result = new double[data.Length]; + for (int i = 0; i < data.Length; i++) + { + double val = 0; + double wSum = 0; + for (int k = 0; k < kernelSize; k++) + { + int idx = i + k - kernelRadius; + if (idx >= 0 && idx < data.Length) + { + val += data[idx] * kernel[k]; + wSum += kernel[k]; + } + } + result[i] = wSum > 0 ? val / wSum : data[i]; + } + return result; + } + + /// + /// 拟合直线 + /// + private LineFitResult FitLine(List edgePoints, string method, + double ransacThreshold, Size imageSize) + { + var result = new LineFitResult(); + + if (edgePoints.Count < 2) + { + result.Success = false; + return result; + } + + if (method == "RANSAC" && edgePoints.Count >= 3) + { + return FitLineRANSAC(edgePoints, ransacThreshold, imageSize); + } + else + { + return FitLineLeastSquares(edgePoints, imageSize); + } + } + + /// + /// 最小二乘直线拟合(使用OpenCV FitLine) + /// + private LineFitResult FitLineLeastSquares(List edgePoints, Size imageSize) + { + var result = new LineFitResult(); + var points = edgePoints.Select(p => p.Position).ToArray(); + + using var pointVector = new VectorOfPointF(points); + using var lineMat = new Mat(); + CvInvoke.FitLine(pointVector, lineMat, DistType.L2, 0, 0.01, 0.01); + var lineParams = new float[4]; + System.Runtime.InteropServices.Marshal.Copy(lineMat.DataPointer, lineParams, 0, 4); + + float vx = lineParams[0], vy = lineParams[1]; + float x0 = lineParams[2], y0 = lineParams[3]; + + result.Success = true; + result.Direction = new PointF(vx, vy); + result.PointOnLine = new PointF(x0, y0); + result.AngleDegrees = Math.Atan2(vy, vx) * 180.0 / Math.PI; + + // 计算端点(延伸到图像边界或搜索范围) + ComputeLineEndpoints(result, points, imageSize); + + // 所有点都是内点 + result.Inliers = points.ToList(); + result.Outliers = new List(); + foreach (var ep in edgePoints) + ep.IsInlier = true; + + // 计算拟合误差 + result.FitError = ComputeFitError(points, vx, vy, x0, y0); + result.EdgePointCount = edgePoints.Count; + result.EdgePoints = edgePoints; + + return result; + } + + /// + /// RANSAC直线拟合 + /// + private LineFitResult FitLineRANSAC(List edgePoints, double threshold, Size imageSize) + { + var result = new LineFitResult(); + var points = edgePoints.Select(p => p.Position).ToArray(); + int n = points.Length; + + // RANSAC参数 + int maxIterations = Math.Min(1000, n * (n - 1) / 2); + int bestInlierCount = 0; + float bestVx = 0, bestVy = 0, bestX0 = 0, bestY0 = 0; + List bestInlierIndices = new(); + + for (int iter = 0; iter < maxIterations; iter++) + { + // 随机选择2个点 + int idx1 = _random.Next(n); + int idx2 = _random.Next(n); + if (idx1 == idx2) continue; + + PointF p1 = points[idx1], p2 = points[idx2]; + float dx = p2.X - p1.X, dy = p2.Y - p1.Y; + float len = (float)Math.Sqrt(dx * dx + dy * dy); + if (len < 1e-6f) continue; + + float vx = dx / len, vy = dy / len; + + // 统计内点 + var inlierIndices = new List(); + for (int i = 0; i < n; i++) + { + double dist = PointToLineDistance(points[i], p1, vx, vy); + if (dist <= threshold) + { + inlierIndices.Add(i); + } + } + + if (inlierIndices.Count > bestInlierCount) + { + bestInlierCount = inlierIndices.Count; + bestInlierIndices = inlierIndices; + + // 用所有内点重新拟合 + var inlierPoints = inlierIndices.Select(i => points[i]).ToArray(); + using var pv = new VectorOfPointF(inlierPoints); + using var lpMat = new Mat(); + CvInvoke.FitLine(pv, lpMat, DistType.L2, 0, 0.01, 0.01); + var lp = new float[4]; + System.Runtime.InteropServices.Marshal.Copy(lpMat.DataPointer, lp, 0, 4); + bestVx = lp[0]; bestVy = lp[1]; bestX0 = lp[2]; bestY0 = lp[3]; + } + + // 如果内点比例已经很高,提前退出 + if (bestInlierCount > n * 0.95) + break; + } + + if (bestInlierCount < 2) + { + result.Success = false; + return result; + } + + result.Success = true; + result.Direction = new PointF(bestVx, bestVy); + result.PointOnLine = new PointF(bestX0, bestY0); + result.AngleDegrees = Math.Atan2(bestVy, bestVx) * 180.0 / Math.PI; + + // 分类内点/外点 + var inliers = new List(); + var outliers = new List(); + var inlierSet = new HashSet(bestInlierIndices); + + for (int i = 0; i < n; i++) + { + if (inlierSet.Contains(i)) + { + inliers.Add(points[i]); + edgePoints[i].IsInlier = true; + } + else + { + outliers.Add(points[i]); + edgePoints[i].IsInlier = false; + } + } + + result.Inliers = inliers; + result.Outliers = outliers; + + // 计算端点 + ComputeLineEndpoints(result, inliers.ToArray(), imageSize); + + // 计算拟合误差(仅内点) + result.FitError = ComputeFitError(inliers.ToArray(), bestVx, bestVy, bestX0, bestY0); + result.EdgePointCount = edgePoints.Count; + result.EdgePoints = edgePoints; + + return result; + } + + /// + /// 计算点到直线的距离 + /// + private static double PointToLineDistance(PointF point, PointF linePoint, float vx, float vy) + { + // 直线法向量 (-vy, vx) + double dx = point.X - linePoint.X; + double dy = point.Y - linePoint.Y; + return Math.Abs(-vy * dx + vx * dy); + } + + /// + /// 计算直线端点(基于边缘点的投影范围) + /// + private static void ComputeLineEndpoints(LineFitResult result, PointF[] points, Size imageSize) + { + float vx = result.Direction.X, vy = result.Direction.Y; + float x0 = result.PointOnLine.X, y0 = result.PointOnLine.Y; + + // 将所有点投影到直线方向上,找最小和最大投影值 + double minT = double.MaxValue, maxT = double.MinValue; + foreach (var p in points) + { + double t = (p.X - x0) * vx + (p.Y - y0) * vy; + if (t < minT) minT = t; + if (t > maxT) maxT = t; + } + + // 稍微延伸一点 + double extend = (maxT - minT) * 0.05; + minT -= extend; + maxT += extend; + + result.Endpoint1 = new PointF( + (float)(x0 + vx * minT), + (float)(y0 + vy * minT)); + result.Endpoint2 = new PointF( + (float)(x0 + vx * maxT), + (float)(y0 + vy * maxT)); + } + + /// + /// 计算平均拟合误差 + /// + private static double ComputeFitError(PointF[] points, float vx, float vy, float x0, float y0) + { + if (points.Length == 0) return 0; + + double totalError = 0; + foreach (var p in points) + { + double dx = p.X - x0; + double dy = p.Y - y0; + double dist = Math.Abs(-vy * dx + vx * dy); + totalError += dist; + } + return totalError / points.Length; + } +} diff --git a/XP.ImageProcessing.RoiControl/Controls/PolygonRoiCanvas.xaml.cs b/XP.ImageProcessing.RoiControl/Controls/PolygonRoiCanvas.xaml.cs index 8b101e1..0eaf82d 100644 --- a/XP.ImageProcessing.RoiControl/Controls/PolygonRoiCanvas.xaml.cs +++ b/XP.ImageProcessing.RoiControl/Controls/PolygonRoiCanvas.xaml.cs @@ -504,6 +504,12 @@ namespace XP.ImageProcessing.RoiControl.Controls private Point? _bgaPendingCenter; // 等待第二次点击定半径 private Ellipse _bgaPendingDot; + // 边缘查找拟合直线临时状态 + private int _elfClickCount; + private Ellipse _elfTempDot1; + private Line _elfTempLine; + private Point? _elfTempStart; + // 气泡测量状态 public enum BubbleSubTool { Roi, RoiCircle, RoiPolygon, Wand, Brush, Eraser } private BubbleSubTool _bubbleTool = BubbleSubTool.Roi; @@ -690,6 +696,8 @@ namespace XP.ImageProcessing.RoiControl.Controls HandleFillRateClick(pos); else if (CurrentMeasureMode == Models.MeasureMode.BgaVoid) HandleBgaVoidClick(pos); + else if (CurrentMeasureMode == Models.MeasureMode.EdgeLineFit) + HandleEdgeLineFitClick(pos); // BubbleMeasure 的点击在 MouseDown/Move/Up 中处理(拖拽画 ROI 和画笔) } @@ -870,6 +878,49 @@ namespace XP.ImageProcessing.RoiControl.Controls return g; } + // ── 边缘查找拟合直线 ── + + private void HandleEdgeLineFitClick(Point pos) + { + _elfClickCount++; + + if (_elfClickCount == 1) + { + _elfTempStart = pos; + _elfTempDot1 = CreateMDot(Brushes.Cyan); + _measureOverlay.Children.Add(_elfTempDot1); + SetDotPos(_elfTempDot1, pos); + RaiseMeasureStatusChanged($"直线拟合 - 搜索线起点: ({pos.X:F0}, {pos.Y:F0}),请点击搜索线终点"); + } + else if (_elfClickCount == 2) + { + // 绘制搜索线 + _elfTempLine = new Line + { + Stroke = Brushes.Cyan, + StrokeThickness = 1, + StrokeDashArray = new DoubleCollection { 4, 2 }, + IsHitTestVisible = false, + X1 = _elfTempStart.Value.X, + Y1 = _elfTempStart.Value.Y, + X2 = pos.X, + Y2 = pos.Y + }; + _measureOverlay.Children.Add(_elfTempLine); + + // 触发完成事件,传递搜索线起止点 + RaiseMeasureCompleted(_elfTempStart.Value, pos, 0, MeasureCount, "EdgeLineFit"); + RaiseMeasureStatusChanged($"直线拟合 - 搜索线已定义: ({_elfTempStart.Value.X:F0},{_elfTempStart.Value.Y:F0}) → ({pos.X:F0},{pos.Y:F0})"); + + // 清理临时状态 + if (_elfTempDot1 != null) _measureOverlay.Children.Remove(_elfTempDot1); + _elfTempDot1 = null; + _elfTempStart = null; + _elfClickCount = 0; + CurrentMeasureMode = Models.MeasureMode.None; + } + } + // ── 角度测量 ── private void HandleAngleClick(Point pos) diff --git a/XP.ImageProcessing.RoiControl/Models/MeasureMode.cs b/XP.ImageProcessing.RoiControl/Models/MeasureMode.cs index 039b8d1..d726a79 100644 --- a/XP.ImageProcessing.RoiControl/Models/MeasureMode.cs +++ b/XP.ImageProcessing.RoiControl/Models/MeasureMode.cs @@ -8,6 +8,7 @@ namespace XP.ImageProcessing.RoiControl.Models Angle, FillRate, BgaVoid, - BubbleMeasure + BubbleMeasure, + EdgeLineFit } } diff --git a/XplorePlane/App.xaml.cs b/XplorePlane/App.xaml.cs index 955dcc6..1e99625 100644 --- a/XplorePlane/App.xaml.cs +++ b/XplorePlane/App.xaml.cs @@ -55,6 +55,7 @@ using XplorePlane.Services.Storage; using XplorePlane.ViewModels; using XplorePlane.ViewModels.Cnc; using XplorePlane.ViewModels.Debug; +using XplorePlane.ViewModels.ImageProcessing; using XplorePlane.Views; using XplorePlane.Views.Cnc; using XplorePlane.Views.Debug; @@ -174,12 +175,12 @@ namespace XplorePlane { var cameraVm = bootstrapper.Container.Resolve(); cameraVm?.Dispose(); - Log.Information("导航相机 ViewModel 已释放"); + Log.Information("Navigation camera ViewModel has been released"); } } catch (Exception ex) { - Log.Error(ex, "导航相机 ViewModel 释放失败"); + Log.Error(ex, "Navigation camera ViewModel release failed"); } // 释放导航相机服务资源 @@ -190,12 +191,12 @@ namespace XplorePlane { var cameraService = bootstrapper.Container.Resolve(); cameraService?.Dispose(); - Log.Information("导航相机服务资源已释放"); + Log.Information("Navigation camera service resources have been released"); } } catch (Exception ex) { - Log.Error(ex, "导航相机服务资源释放失败"); + Log.Error(ex, "Navigation camera service resource release failed"); } // 释放主界面探测器帧流水线资源 @@ -350,9 +351,6 @@ namespace XplorePlane // 主窗体加载完成后再连接相机,确保所有模块和原生 DLL 已完成初始化 shell.Loaded += async (s, e) => { - // [DEV] 导航相机连接已屏蔽,开发阶段跳过以加快启动速度 - // TryConnectCamera(); - // 初始化主界面探测器帧流水线,开始接收探测器图像事件 try { @@ -376,11 +374,22 @@ namespace XplorePlane // { // Log.Error(ex, "通知相机 ViewModel 失败"); // } + // [DEV] 导航相机连接已屏蔽,开发阶段跳过以加快启动速度 + //TryConnectCamera(); + //try + //{ + // var cameraVm = Container.Resolve(); + // cameraVm.OnCameraReady(); + //} + //catch (Exception ex) + //{ + // Log.Error(ex, "Failed to notify the camera ViewModel"); + //} - // if (_cameraError != null) - // { - // HexMessageBox.Show(_cameraError, MessageBoxButton.OK, MessageBoxImage.Error); - // } + //if (_cameraError != null) + //{ + // HexMessageBox.Show(_cameraError, MessageBoxButton.OK, MessageBoxImage.Error); + //} }; return shell; @@ -531,17 +540,17 @@ namespace XplorePlane try { var info = camera.Open(); - Log.Information("导航相机已连接: {ModelName} (SN: {SerialNumber})", info.ModelName, info.SerialNumber); + Log.Information("Navigation camera connected: {ModelName} (SN: {SerialNumber})", info.ModelName, info.SerialNumber); } catch (DeviceNotFoundException) { - Log.Warning("未检测到导航相机"); - _cameraError = "未检测到导航相机,请检查连接后重启软件。"; + Log.Warning("Navigation camera not detected"); + _cameraError = "Navigation camera not detected,Please check the connection and restart the software.。"; } catch (Exception ex) { - Log.Warning(ex, "导航相机自动连接失败: {Message}", ex.Message); - _cameraError = $"导航相机连接失败: {ex.Message}"; + Log.Warning(ex, "Automatic connection of navigation camera failed: {Message}", ex.Message); + _cameraError = $"Navigation camera connection failed: {ex.Message}"; } } @@ -575,7 +584,6 @@ namespace XplorePlane containerRegistry.RegisterSingleton(); containerRegistry.Register(); - // 注册流水线服务(单例,共享 IImageProcessingService) containerRegistry.RegisterSingleton(); containerRegistry.RegisterSingleton(); @@ -593,6 +601,7 @@ namespace XplorePlane // 注册流水线 ViewModel(每次解析创建新实例) containerRegistry.Register(); containerRegistry.Register(); + containerRegistry.Register(); // 注册硬件库的 ViewModel(供 ViewModelLocator 自动装配) containerRegistry.Register(); @@ -634,7 +643,26 @@ namespace XplorePlane // ── 导航相机服务(单例)── containerRegistry.RegisterSingleton(); containerRegistry.RegisterSingleton(() => - new CameraFactory().CreateController("Basler")); + { + string cameraType = "Hikvision"; // 默认值 + try + { + var configPath = Path.Combine(AppContext.BaseDirectory, "config.json"); + if (File.Exists(configPath)) + { + var json = File.ReadAllText(configPath); + var match = System.Text.RegularExpressions.Regex.Match(json, "\"CameraType\"\\s*:\\s*\"([^\"]+)\""); + if (match.Success) + cameraType = match.Groups[1].Value; + } + } + catch (Exception ex) + { + Log.Warning(ex, "Failed to read CameraType configuration, using default value Hikvision"); + } + Log.Information("Camera Type: {CameraType}", cameraType); + return new CameraFactory().CreateController(cameraType); + }); containerRegistry.RegisterSingleton(); // ── 录制服务(单例)── @@ -662,4 +690,4 @@ namespace XplorePlane base.ConfigureModuleCatalog(moduleCatalog); } } -} +} \ No newline at end of file diff --git a/XplorePlane/Assets/Icons/FittedCircle.png b/XplorePlane/Assets/Icons/FittedCircle.png new file mode 100644 index 0000000..13ac0d8 Binary files /dev/null and b/XplorePlane/Assets/Icons/FittedCircle.png differ diff --git a/XplorePlane/Assets/Icons/FittedLine.png b/XplorePlane/Assets/Icons/FittedLine.png new file mode 100644 index 0000000..d6e7f7c Binary files /dev/null and b/XplorePlane/Assets/Icons/FittedLine.png differ diff --git a/XplorePlane/Assets/Icons/Matching.png b/XplorePlane/Assets/Icons/Matching.png new file mode 100644 index 0000000..bcf4ca2 Binary files /dev/null and b/XplorePlane/Assets/Icons/Matching.png differ diff --git a/XplorePlane/Events/MeasurementToolEvent.cs b/XplorePlane/Events/MeasurementToolEvent.cs index daa45a2..2253239 100644 --- a/XplorePlane/Events/MeasurementToolEvent.cs +++ b/XplorePlane/Events/MeasurementToolEvent.cs @@ -1,7 +1,21 @@ +using System.Collections.Generic; +using System.Drawing; using Prism.Events; namespace XplorePlane.Events { + /// + /// 白底/黑底检测单条结果:全局图像坐标下的轮廓与最远弦(微米与既有展示规则一致)。 + /// + public class BackgroundDefectDetectionItem + { + public List Contour { get; set; } = new(); + /// 轮廓顶点间最远距离(微米)。 + public double SizeMicrometers { get; set; } + public Point ChordP1 { get; set; } + public Point ChordP2 { get; set; } + } + /// /// 测量工具模式 /// @@ -13,7 +27,8 @@ namespace XplorePlane.Events Angle, ThroughHoleFillRate, BgaVoid, - BubbleMeasure + BubbleMeasure, + EdgeLineFit } /// @@ -25,4 +40,51 @@ namespace XplorePlane.Events /// 十字辅助线切换事件 /// public class ToggleCrosshairEvent : PubSubEvent { } + + /// + /// 行灰度分布切换事件 + /// + public class ToggleLineProfileEvent : PubSubEvent { } + + /// + /// 白底检测事件(进入ROI绘制模式) + /// + public class WhiteBackgroundDetectionEvent : PubSubEvent { } + + /// + /// 白底检测ROI绘制完成事件 + /// + public class WhiteBackgroundRoiDrawnEvent : PubSubEvent { } + + /// + /// 白底检测结果事件 + /// + public class WhiteBackgroundResultEvent : PubSubEvent { } + + public class WhiteBackgroundResultPayload + { + public System.Drawing.Rectangle RoiRect { get; set; } + public List Detections { get; set; } = new(); + } + + /// + /// 黑底检测事件(进入ROI绘制模式) + /// + public class BlackBackgroundDetectionEvent : PubSubEvent { } + + /// + /// 黑底检测ROI绘制完成事件 + /// + public class BlackBackgroundRoiDrawnEvent : PubSubEvent { } + + /// + /// 黑底检测结果事件 + /// + public class BlackBackgroundResultEvent : PubSubEvent { } + + public class BlackBackgroundResultPayload + { + public System.Drawing.Rectangle RoiRect { get; set; } + public List Detections { get; set; } = new(); + } } diff --git a/XplorePlane/Events/TemplateMatchAssistantEvents.cs b/XplorePlane/Events/TemplateMatchAssistantEvents.cs new file mode 100644 index 0000000..f83747e --- /dev/null +++ b/XplorePlane/Events/TemplateMatchAssistantEvents.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; +using System.Windows; +using Prism.Events; + +namespace XplorePlane.Events; + +/// +/// 进入「在视口上框选模板 ROI」模式(与主画布 Preview 鼠标逻辑配合)。 +/// +public class TemplateMatchEnterRoiModeEvent : PubSubEvent { } + +/// +/// 模板 ROI 框选完成(图像/画布像素坐标,与白底检测 ROI 约定一致)。仅表示区域已确定,不表示已训练。 +/// +public class TemplateMatchRoiDrawnEvent : PubSubEvent { } + +/// +/// 清除视口上的模板助手持久 ROI 框(例如加载模型后或重置时)。 +/// +public class TemplateMatchClearRoiOverlayEvent : PubSubEvent { } + +/// +/// 单次模板匹配试跑结果,供主视图叠加层绘制。 +/// +public class TemplateMatchHitDto +{ + public double CenterX { get; set; } + public double CenterY { get; set; } + public double Angle { get; set; } + public double Score { get; set; } + public double LtX { get; set; } + public double LtY { get; set; } + public double RtX { get; set; } + public double RtY { get; set; } + public double RbX { get; set; } + public double RbY { get; set; } + public double LbX { get; set; } + public double LbY { get; set; } +} + +public class TemplateMatchPreviewPayload +{ + public List Hits { get; set; } = new(); + public double MatchTimeMs { get; set; } +} + +public class TemplateMatchPreviewResultEvent : PubSubEvent { } diff --git a/XplorePlane/ViewModels/ImageProcessing/EdgeCircleFitViewModel.cs b/XplorePlane/ViewModels/ImageProcessing/EdgeCircleFitViewModel.cs new file mode 100644 index 0000000..2bee916 --- /dev/null +++ b/XplorePlane/ViewModels/ImageProcessing/EdgeCircleFitViewModel.cs @@ -0,0 +1,497 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Shapes; +using Emgu.CV; +using Emgu.CV.Structure; +using Prism.Commands; +using Prism.Mvvm; +using XP.ImageProcessing.Processors; +using XP.ImageProcessing.RoiControl.Controls; +using XplorePlane.Services.MainViewport; +using Brushes = System.Windows.Media.Brushes; +using Ellipse = System.Windows.Shapes.Ellipse; +using Point = System.Windows.Point; + +namespace XplorePlane.ViewModels.ImageProcessing +{ + /// + /// 边缘查找拟合圆 ViewModel + /// 交互:3点定义预估圆,手柄可调整圆心和半径,点击拟合执行 + /// + public class EdgeCircleFitViewModel : BindableBase + { + private readonly IMainViewportService _viewportService; + private PolygonRoiCanvas _canvas; + private Canvas _mainCanvas; + + // 预估圆 + private Point _center; + private double _radius; + private bool _circleDefined; + + // 可视化 + private readonly List _tempOverlays = new(); + private readonly List _committedOverlays = new(); + + // 手柄位置 + private Point _handleCenterPos; + private Point _handleRadiusPos; // 圆周上0°位置 + + // 交互 + private enum DragTarget { None, Center, Radius } + private DragTarget _dragging = DragTarget.None; + private bool _isDrawing; + private int _fitCount; + + private const double HandleSize = 12; + private const double HitRadius = 10; + private static readonly SolidColorBrush CaliperStroke; + private static readonly SolidColorBrush CaliperFill; + private static readonly SolidColorBrush FitCircleBrush; + private static readonly SolidColorBrush HandleFill; + + static EdgeCircleFitViewModel() + { + CaliperStroke = new SolidColorBrush(Color.FromRgb(0, 255, 0)); + CaliperStroke.Freeze(); + CaliperFill = new SolidColorBrush(Color.FromArgb(15, 0, 255, 0)); + CaliperFill.Freeze(); + FitCircleBrush = new SolidColorBrush(Color.FromRgb(30, 144, 255)); + FitCircleBrush.Freeze(); + HandleFill = new SolidColorBrush(Color.FromArgb(220, 255, 255, 255)); + HandleFill.Freeze(); + } + + public EdgeCircleFitViewModel(IMainViewportService viewportService) + { + _viewportService = viewportService; + FitCommand = new DelegateCommand(ExecuteFit, () => _circleDefined); + ClearAllCommand = new DelegateCommand(ExecuteClearAll); + DrawCircleCommand = new DelegateCommand(ExecuteDrawCircle); + } + + // ── 命令 ── + public DelegateCommand FitCommand { get; } + public DelegateCommand ClearAllCommand { get; } + public DelegateCommand DrawCircleCommand { get; } + + // ── 参数 ── + private int _caliperCount = 36; + public int CaliperCount { get => _caliperCount; set { if (SetProperty(ref _caliperCount, value)) RedrawTemp(); } } + + private int _caliperWidth = 40; + public int CaliperWidth { get => _caliperWidth; set { if (SetProperty(ref _caliperWidth, value)) RedrawTemp(); } } + + private string _edgePolarity = "Both"; + public string EdgePolarity { get => _edgePolarity; set { if (SetProperty(ref _edgePolarity, value)) RedrawTemp(); } } + + private int _edgeThreshold = 20; + public int EdgeThreshold { get => _edgeThreshold; set => SetProperty(ref _edgeThreshold, value); } + + private double _sigma = 1.0; + public double Sigma { get => _sigma; set => SetProperty(ref _sigma, value); } + + private string _searchDirection = "Both"; + public string SearchDirection { get => _searchDirection; set => SetProperty(ref _searchDirection, value); } + + private string _fitMethod = "RANSAC"; + public string FitMethod { get => _fitMethod; set => SetProperty(ref _fitMethod, value); } + + private double _ransacThreshold = 2.0; + public double RansacThreshold { get => _ransacThreshold; set => SetProperty(ref _ransacThreshold, value); } + + private string _resultText = "Ready - click Draw Circle"; + public string ResultText { get => _resultText; set => SetProperty(ref _resultText, value); } + + // ── 初始化 ── + public void SetCanvas(PolygonRoiCanvas canvas) + { + _canvas = canvas; + _mainCanvas = FindChild(canvas, "mainCanvas"); + } + + public void OnPanelClosed() + { + UnregisterAll(); + ClearTempOverlays(); + } + + // ══════════════════════════════════════════════════════════════ + // 命令 + // ══════════════════════════════════════════════════════════════ + + private void ExecuteDrawCircle() + { + ClearTempOverlays(); + UnregisterAll(); + _circleDefined = false; + _dragging = DragTarget.None; + FitCommand.RaiseCanExecuteChanged(); + _isDrawing = true; + ResultText = "Press and drag to define circle (center → radius)"; + RegisterInteraction(); + } + + private void ExecuteFit() + { + if (!_circleDefined) return; + + // 清除上一次拟合结果 + ClearCommitted(); + + var imageSource = _viewportService?.CurrentDisplayImage as BitmapSource; + if (imageSource == null) { ResultText = "Error: no image"; return; } + + try + { + BitmapSource source = imageSource; + if (imageSource.Format != PixelFormats.Gray8) + source = new FormatConvertedBitmap(imageSource, PixelFormats.Gray8, null, 0); + + int w = source.PixelWidth, h = source.PixelHeight; + int stride = w; + byte[] px = new byte[h * stride]; + source.CopyPixels(px, stride, 0); + + using var img = new Image(w, h); + for (int y = 0; y < h; y++) + for (int x = 0; x < w; x++) + img.Data[y, x, 0] = px[y * stride + x]; + + var proc = new EdgeCircleFitProcessor(); + proc.SetParameter("CenterX", (int)_center.X); + proc.SetParameter("CenterY", (int)_center.Y); + proc.SetParameter("Radius", (int)_radius); + proc.SetParameter("CaliperCount", CaliperCount); + proc.SetParameter("CaliperWidth", CaliperWidth); + proc.SetParameter("EdgePolarity", EdgePolarity); + proc.SetParameter("EdgeThreshold", EdgeThreshold); + proc.SetParameter("Sigma", Sigma); + proc.SetParameter("SearchDirection", SearchDirection); + proc.SetParameter("FitMethod", FitMethod); + proc.SetParameter("RansacThreshold", RansacThreshold); + + var result = proc.Process(img); + var od = proc.OutputData; + + if (od.ContainsKey("CircleFitResult")) + { + var fr = od["CircleFitResult"] as CircleFitResult; + if (fr != null && fr.Success) + { + _fitCount++; + DrawFitResult(fr); + ResultText = $"[#{_fitCount}] Fit OK\nCenter: ({fr.CenterX:F1}, {fr.CenterY:F1})\n" + + $"Radius: {fr.Radius:F2} px\n" + + $"Inliers: {fr.Inliers.Count}/{fr.EdgePointCount}\n" + + $"Error: {fr.FitError:F3} px\n\nAdjust and fit again, or draw new"; + } + else + { + int ec = od.ContainsKey("EdgePointCount") ? (int)od["EdgePointCount"] : 0; + ResultText = $"Fit failed\nEdge points: {ec}\nAdjust params or circle"; + } + } + result.Dispose(); + } + catch (Exception ex) { ResultText = $"Exception: {ex.Message}"; } + } + + private void ExecuteClearAll() + { + ClearTempOverlays(); + if (_mainCanvas != null) + foreach (var el in _committedOverlays) _mainCanvas.Children.Remove(el); + _committedOverlays.Clear(); + _fitCount = 0; + UnregisterAll(); + _circleDefined = false; + FitCommand.RaiseCanExecuteChanged(); + ResultText = "Cleared"; + } + + // ══════════════════════════════════════════════════════════════ + // 拟合结果绘制(永久) + // ══════════════════════════════════════════════════════════════ + + private void DrawFitResult(CircleFitResult fr) + { + if (_mainCanvas == null) return; + + // 拟合圆(蓝色) + var circle = new Ellipse + { + Width = fr.Radius * 2, Height = fr.Radius * 2, + Stroke = FitCircleBrush, StrokeThickness = 2, Fill = Brushes.Transparent, + IsHitTestVisible = false + }; + Canvas.SetLeft(circle, fr.CenterX - fr.Radius); + Canvas.SetTop(circle, fr.CenterY - fr.Radius); + AddCommitted(circle); + + // 圆心十字 + double cs = 6; + AddCommitted(new Line { X1 = fr.CenterX - cs, Y1 = fr.CenterY, X2 = fr.CenterX + cs, Y2 = fr.CenterY, Stroke = FitCircleBrush, StrokeThickness = 1.5, IsHitTestVisible = false }); + AddCommitted(new Line { X1 = fr.CenterX, Y1 = fr.CenterY - cs, X2 = fr.CenterX, Y2 = fr.CenterY + cs, Stroke = FitCircleBrush, StrokeThickness = 1.5, IsHitTestVisible = false }); + + // 标注 + var lbl = new TextBlock + { + Text = $"R:{fr.Radius:F1} C:({fr.CenterX:F1},{fr.CenterY:F1})", + Foreground = FitCircleBrush, FontSize = 11, FontWeight = FontWeights.Bold, IsHitTestVisible = false + }; + Canvas.SetLeft(lbl, fr.CenterX + 5); Canvas.SetTop(lbl, fr.CenterY - fr.Radius - 18); + AddCommitted(lbl); + } + + private void AddCommitted(UIElement el) { _mainCanvas.Children.Add(el); _committedOverlays.Add(el); } + + private void ClearCommitted() + { + if (_mainCanvas == null) return; + foreach (var el in _committedOverlays) _mainCanvas.Children.Remove(el); + _committedOverlays.Clear(); + } + + // ══════════════════════════════════════════════════════════════ + // 临时卡尺可视化 + // ══════════════════════════════════════════════════════════════ + + private void RedrawTemp() + { + if (!_circleDefined || _mainCanvas == null) return; + ClearTempOverlays(); + DrawTempCaliper(); + } + + private void DrawTempCaliper() + { + if (_mainCanvas == null || _radius < 5) return; + + // 预估圆(虚线) + var previewCircle = new Ellipse + { + Width = _radius * 2, Height = _radius * 2, + Stroke = CaliperStroke, StrokeThickness = 1, + StrokeDashArray = new DoubleCollection { 4, 3 }, + Fill = CaliperFill, IsHitTestVisible = false + }; + Canvas.SetLeft(previewCircle, _center.X - _radius); + Canvas.SetTop(previewCircle, _center.Y - _radius); + AddTemp(previewCircle); + + // 卡尺径向线 + int count = CaliperCount; + double halfW = CaliperWidth / 2.0; + double angleStep = 2.0 * Math.PI / count; + + for (int i = 0; i < count; i++) + { + double angle = angleStep * i; + double dirX = Math.Cos(angle), dirY = Math.Sin(angle); + double cx = _center.X + _radius * dirX; + double cy = _center.Y + _radius * dirY; + + AddTemp(new Line + { + X1 = cx - dirX * halfW, Y1 = cy - dirY * halfW, + X2 = cx + dirX * halfW, Y2 = cy + dirY * halfW, + Stroke = CaliperStroke, StrokeThickness = 1, Opacity = 0.4, IsHitTestVisible = false + }); + } + + // 手柄 + _handleCenterPos = _center; + _handleRadiusPos = new Point(_center.X + _radius, _center.Y); + + AddTemp(MakeHandle(_handleCenterPos)); + AddTemp(MakeHandle(_handleRadiusPos)); + } + + private void CommitCurrentCaliper() + { + if (_mainCanvas == null) return; + foreach (var el in _tempOverlays) _mainCanvas.Children.Remove(el); + _tempOverlays.Clear(); + + // 绘制永久卡尺(半透明) + var circle = new Ellipse + { + Width = _radius * 2, Height = _radius * 2, + Stroke = CaliperStroke, StrokeThickness = 1, Opacity = 0.4, + Fill = Brushes.Transparent, IsHitTestVisible = false + }; + Canvas.SetLeft(circle, _center.X - _radius); + Canvas.SetTop(circle, _center.Y - _radius); + AddCommitted(circle); + } + + private Ellipse MakeHandle(Point pos) + { + var h = new Ellipse + { + Width = HandleSize, Height = HandleSize, + Fill = HandleFill, Stroke = CaliperStroke, StrokeThickness = 2, + IsHitTestVisible = false + }; + Canvas.SetLeft(h, pos.X - HandleSize / 2); + Canvas.SetTop(h, pos.Y - HandleSize / 2); + return h; + } + + private void AddTemp(UIElement el) { _mainCanvas.Children.Add(el); _tempOverlays.Add(el); } + private void ClearTempOverlays() + { + if (_mainCanvas == null) return; + foreach (var el in _tempOverlays) _mainCanvas.Children.Remove(el); + _tempOverlays.Clear(); + } + + // ══════════════════════════════════════════════════════════════ + // 鼠标交互 + // ══════════════════════════════════════════════════════════════ + + private bool _interactionRegistered; + + private void RegisterInteraction() + { + if (_canvas == null || _interactionRegistered) return; + _canvas.PreviewMouseLeftButtonDown += OnMouseDown; + _canvas.PreviewMouseMove += OnMouseMove; + _canvas.PreviewMouseLeftButtonUp += OnMouseUp; + _interactionRegistered = true; + } + + private void UnregisterAll() + { + if (_canvas == null || !_interactionRegistered) return; + _canvas.PreviewMouseLeftButtonDown -= OnMouseDown; + _canvas.PreviewMouseMove -= OnMouseMove; + _canvas.PreviewMouseLeftButtonUp -= OnMouseUp; + _interactionRegistered = false; + _isDrawing = false; + _dragging = DragTarget.None; + } + + private void OnMouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e) + { + if (_mainCanvas == null) return; + var pos = e.GetPosition(_mainCanvas); + + // 绘制模式:按下鼠标确定圆心,拖拽确定半径 + if (_isDrawing) + { + _center = pos; + _radius = 0; + _dragging = DragTarget.Radius; // 复用 Radius 拖拽逻辑 + _canvas.CaptureMouse(); + e.Handled = true; + return; + } + + // 拖拽手柄 + if (_circleDefined) + { + var target = HitTest(pos); + if (target != DragTarget.None) + { + _dragging = target; + _canvas.CaptureMouse(); + e.Handled = true; + } + } + } + + private void OnMouseMove(object sender, System.Windows.Input.MouseEventArgs e) + { + if (_dragging == DragTarget.None || _mainCanvas == null) return; + var pos = e.GetPosition(_mainCanvas); + + if (_dragging == DragTarget.Center) + { + _center = pos; + } + else if (_dragging == DragTarget.Radius) + { + _radius = Math.Max(5, Dist(pos, _center)); + } + + // 实时预览 + if (_radius >= 5) + { + _circleDefined = true; + ClearTempOverlays(); + DrawTempCaliper(); + } + e.Handled = true; + } + + private void OnMouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e) + { + if (_dragging == DragTarget.None) return; + + // 绘制模式完成 + if (_isDrawing) + { + _isDrawing = false; + _dragging = DragTarget.None; + _canvas.ReleaseMouseCapture(); + + if (_radius >= 5) + { + _circleDefined = true; + FitCommand.RaiseCanExecuteChanged(); + RedrawTemp(); + ResultText = $"Circle defined: R={_radius:F0}px\nDrag handles to adjust\nClick Fit to execute"; + } + else + { + _circleDefined = false; + ResultText = "Circle too small, try again"; + } + e.Handled = true; + return; + } + + _dragging = DragTarget.None; + _canvas.ReleaseMouseCapture(); + ResultText = $"Circle: R={_radius:F0}px\nClick Fit to execute"; + e.Handled = true; + } + + private DragTarget HitTest(Point pos) + { + if (Dist(pos, _handleCenterPos) <= HitRadius) return DragTarget.Center; + if (Dist(pos, _handleRadiusPos) <= HitRadius) return DragTarget.Radius; + return DragTarget.None; + } + + // ══════════════════════════════════════════════════════════════ + // 辅助 + // ══════════════════════════════════════════════════════════════ + + private static double Dist(Point a, Point b) + { + double dx = a.X - b.X, dy = a.Y - b.Y; + return Math.Sqrt(dx * dx + dy * dy); + } + + private static T FindChild(DependencyObject parent, string name) where T : FrameworkElement + { + int count = VisualTreeHelper.GetChildrenCount(parent); + for (int i = 0; i < count; i++) + { + var child = VisualTreeHelper.GetChild(parent, i); + if (child is T t && t.Name == name) return t; + var r = FindChild(child, name); + if (r != null) return r; + } + return null; + } + } +} diff --git a/XplorePlane/ViewModels/ImageProcessing/EdgeLineFitViewModel.cs b/XplorePlane/ViewModels/ImageProcessing/EdgeLineFitViewModel.cs new file mode 100644 index 0000000..f7fcd8b --- /dev/null +++ b/XplorePlane/ViewModels/ImageProcessing/EdgeLineFitViewModel.cs @@ -0,0 +1,620 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Shapes; +using Emgu.CV; +using Emgu.CV.Structure; +using Prism.Commands; +using Prism.Mvvm; +using XP.ImageProcessing.Processors; +using XP.ImageProcessing.RoiControl.Controls; +using XplorePlane.Services.MainViewport; +using Brushes = System.Windows.Media.Brushes; +using Ellipse = System.Windows.Shapes.Ellipse; +using Point = System.Windows.Point; + +namespace XplorePlane.ViewModels.ImageProcessing +{ + /// + /// 边缘查找拟合直线 ViewModel + /// 支持多次拟合,每次点击"画卡尺"开始一次新的测量,结果累积保留 + /// 关闭面板时保留所有结果,仅清除当前正在编辑的临时卡尺 + /// + public class EdgeLineFitViewModel : BindableBase + { + private readonly IMainViewportService _viewportService; + private PolygonRoiCanvas _canvas; + private Canvas _mainCanvas; + + // 当前正在编辑的搜索线 + private Point _lineStart; + private Point _lineEnd; + private double _halfWidth = 30; + private bool _lineDefined; + + // 当前编辑中的临时可视化(卡尺框+手柄,拟合前可调整) + private readonly List _tempOverlays = new(); + + // 已完成的拟合结果(永久保留在画布上) + // 不由本类管理生命周期,关闭面板后仍保留 + private readonly List _committedOverlays = new(); + + // 手柄位置 + private Point _handleStartPos, _handleEndPos, _handleTopPos, _handleBottomPos; + + // 交互状态 + private enum DragTarget { None, Start, End, Top, Bottom } + private DragTarget _dragging = DragTarget.None; + private bool _isDrawingLine; + private int _drawClickCount; + private int _fitCount; + + private const double HandleSize = 12; + private const double HitRadius = 10; + private static readonly SolidColorBrush CaliperStroke; + private static readonly SolidColorBrush CaliperFill; + private static readonly SolidColorBrush FitLineBrush; + private static readonly SolidColorBrush HandleFill; + + static EdgeLineFitViewModel() + { + CaliperStroke = new SolidColorBrush(Color.FromRgb(0, 255, 0)); + CaliperStroke.Freeze(); + CaliperFill = new SolidColorBrush(Color.FromArgb(20, 0, 255, 0)); + CaliperFill.Freeze(); + FitLineBrush = new SolidColorBrush(Color.FromRgb(30, 144, 255)); + FitLineBrush.Freeze(); + HandleFill = new SolidColorBrush(Color.FromArgb(220, 255, 255, 255)); + HandleFill.Freeze(); + } + + public EdgeLineFitViewModel(IMainViewportService viewportService) + { + _viewportService = viewportService; + FitCommand = new DelegateCommand(ExecuteFit, () => _lineDefined); + ClearAllCommand = new DelegateCommand(ExecuteClearAll); + DrawCaliperCommand = new DelegateCommand(ExecuteDrawCaliper); + } + + // ── 命令 ── + public DelegateCommand FitCommand { get; } + public DelegateCommand ClearAllCommand { get; } + public DelegateCommand DrawCaliperCommand { get; } + + // ── 参数 ── + private int _caliperCount = 20; + public int CaliperCount + { + get => _caliperCount; + set { if (SetProperty(ref _caliperCount, value)) RedrawTemp(); } + } + + private int _displayWidth = 60; + public int DisplayWidth + { + get => _displayWidth; + set + { + if (SetProperty(ref _displayWidth, Math.Max(10, value))) + { + _halfWidth = _displayWidth / 2.0; + RedrawTemp(); + } + } + } + + private string _edgePolarity = "Both"; + public string EdgePolarity + { + get => _edgePolarity; + set { if (SetProperty(ref _edgePolarity, value)) RedrawTemp(); } + } + + private int _edgeThreshold = 20; + public int EdgeThreshold { get => _edgeThreshold; set => SetProperty(ref _edgeThreshold, value); } + + private double _sigma = 1.0; + public double Sigma { get => _sigma; set => SetProperty(ref _sigma, value); } + + private string _fitMethod = "RANSAC"; + public string FitMethod { get => _fitMethod; set => SetProperty(ref _fitMethod, value); } + + private double _ransacThreshold = 2.0; + public double RansacThreshold { get => _ransacThreshold; set => SetProperty(ref _ransacThreshold, value); } + + private string _resultText = "就绪 - 点击「画卡尺」开始"; + public string ResultText { get => _resultText; set => SetProperty(ref _resultText, value); } + + // ── 初始化 ── + public void SetCanvas(PolygonRoiCanvas canvas) + { + _canvas = canvas; + _mainCanvas = FindChild(canvas, "mainCanvas"); + } + + /// 面板关闭时调用:仅清除临时编辑状态,保留已拟合结果 + public void OnPanelClosed() + { + UnregisterAll(); + ClearTempOverlays(); // 清除正在编辑的卡尺手柄 + // _committedOverlays 保留在画布上不清除 + } + + // ══════════════════════════════════════════════════════════════ + // 命令实现 + // ══════════════════════════════════════════════════════════════ + + /// 开始一次新的卡尺绘制(不影响已有结果) + private void ExecuteDrawCaliper() + { + // 清除当前临时编辑 + ClearTempOverlays(); + UnregisterAll(); + _lineDefined = false; + _dragging = DragTarget.None; + FitCommand.RaiseCanExecuteChanged(); + _drawClickCount = 0; + _isDrawingLine = true; + ResultText = "请在图像上点击搜索线起点"; + RegisterInteraction(); + } + + /// 执行拟合,将结果提交为永久显示 + private void ExecuteFit() + { + if (!_lineDefined) return; + + // 清除上一次拟合结果 + ClearCommitted(); + + var imageSource = _viewportService?.CurrentDisplayImage as BitmapSource; + if (imageSource == null) { ResultText = "错误:无可用图像"; return; } + + try + { + BitmapSource source = imageSource; + if (imageSource.Format != PixelFormats.Gray8) + source = new FormatConvertedBitmap(imageSource, PixelFormats.Gray8, null, 0); + + int w = source.PixelWidth, h = source.PixelHeight; + int stride = w; + byte[] px = new byte[h * stride]; + source.CopyPixels(px, stride, 0); + + using var img = new Image(w, h); + for (int y = 0; y < h; y++) + for (int x = 0; x < w; x++) + img.Data[y, x, 0] = px[y * stride + x]; + + var proc = new EdgeLineFitProcessor(); + proc.SetParameter("StartX", (int)_lineStart.X); + proc.SetParameter("StartY", (int)_lineStart.Y); + proc.SetParameter("EndX", (int)_lineEnd.X); + proc.SetParameter("EndY", (int)_lineEnd.Y); + proc.SetParameter("CaliperCount", CaliperCount); + proc.SetParameter("CaliperWidth", (int)(_halfWidth * 2)); + proc.SetParameter("EdgePolarity", EdgePolarity); + proc.SetParameter("EdgeThreshold", EdgeThreshold); + proc.SetParameter("Sigma", Sigma); + proc.SetParameter("FitMethod", FitMethod); + proc.SetParameter("RansacThreshold", RansacThreshold); + + var result = proc.Process(img); + var od = proc.OutputData; + + if (od.ContainsKey("LineFitResult")) + { + var fr = od["LineFitResult"] as LineFitResult; + if (fr != null && fr.Success) + { + _fitCount++; + DrawFitResult(fr); + ResultText = $"[#{_fitCount}] 拟合成功\n角度: {fr.AngleDegrees:F2}°\n" + + $"内点: {fr.Inliers.Count}/{fr.EdgePointCount}\n" + + $"误差: {fr.FitError:F3} px\n\n可继续调整后再次拟合"; + } + else + { + int ec = od.ContainsKey("EdgePointCount") ? (int)od["EdgePointCount"] : 0; + ResultText = $"拟合失败\n边缘点: {ec}\n请调整参数或拖拽手柄"; + } + } + result.Dispose(); + } + catch (Exception ex) { ResultText = $"异常: {ex.Message}"; } + } + + /// 清除所有(包括已拟合的结果) + private void ExecuteClearAll() + { + ClearTempOverlays(); + // 清除所有已提交的结果 + if (_mainCanvas != null) + { + foreach (var el in _committedOverlays) + _mainCanvas.Children.Remove(el); + } + _committedOverlays.Clear(); + _fitCount = 0; + UnregisterAll(); + _lineDefined = false; + _dragging = DragTarget.None; + FitCommand.RaiseCanExecuteChanged(); + ResultText = "已清除所有结果"; + } + + // ══════════════════════════════════════════════════════════════ + // 提交当前卡尺为永久显示 + // ══════════════════════════════════════════════════════════════ + + /// 将当前临时卡尺可视化转为永久(去掉手柄,保留边框和等分线) + private void CommitCurrentCaliper() + { + if (_mainCanvas == null) return; + + // 移除临时元素 + foreach (var el in _tempOverlays) + _mainCanvas.Children.Remove(el); + _tempOverlays.Clear(); + + // 重新绘制卡尺(无手柄,作为永久元素) + double dx = _lineEnd.X - _lineStart.X, dy = _lineEnd.Y - _lineStart.Y; + double len = Math.Sqrt(dx * dx + dy * dy); + if (len < 2) return; + + double ux = dx / len, uy = dy / len; + double px = -uy, py = ux; + double hw = _halfWidth; + + var c1 = new Point(_lineStart.X + px * hw, _lineStart.Y + py * hw); + var c2 = new Point(_lineEnd.X + px * hw, _lineEnd.Y + py * hw); + var c3 = new Point(_lineEnd.X - px * hw, _lineEnd.Y - py * hw); + var c4 = new Point(_lineStart.X - px * hw, _lineStart.Y - py * hw); + + // 矩形边框(半透明,不抢眼) + var border = new Polygon + { + Points = new System.Windows.Media.PointCollection { c1, c2, c3, c4 }, + Stroke = CaliperStroke, StrokeThickness = 1, Opacity = 0.5, + Fill = System.Windows.Media.Brushes.Transparent, IsHitTestVisible = false + }; + _mainCanvas.Children.Add(border); + _committedOverlays.Add(border); + + // 等分线 + int count = CaliperCount; + double step = len / (count + 1); + for (int i = 1; i <= count; i++) + { + double cx = _lineStart.X + ux * step * i, cy = _lineStart.Y + uy * step * i; + var line = new Line + { + X1 = cx + px * hw, Y1 = cy + py * hw, + X2 = cx - px * hw, Y2 = cy - py * hw, + Stroke = CaliperStroke, StrokeThickness = 1, Opacity = 0.3, IsHitTestVisible = false + }; + _mainCanvas.Children.Add(line); + _committedOverlays.Add(line); + } + } + + // ══════════════════════════════════════════════════════════════ + // 绘制拟合结果(永久) + // ══════════════════════════════════════════════════════════════ + + private void DrawFitResult(LineFitResult fr) + { + if (_mainCanvas == null) return; + + // 拟合直线(蓝色) + AddCommitted(new Line + { + X1 = fr.Endpoint1.X, Y1 = fr.Endpoint1.Y, + X2 = fr.Endpoint2.X, Y2 = fr.Endpoint2.Y, + Stroke = FitLineBrush, StrokeThickness = 2, IsHitTestVisible = false + }); + + // 标注 + var lbl = new TextBlock + { + Text = $"∠{fr.AngleDegrees:F2}°", + Foreground = FitLineBrush, FontSize = 11, FontWeight = FontWeights.Bold, IsHitTestVisible = false + }; + Canvas.SetLeft(lbl, (fr.Endpoint1.X + fr.Endpoint2.X) / 2 + 5); + Canvas.SetTop(lbl, (fr.Endpoint1.Y + fr.Endpoint2.Y) / 2 - 18); + AddCommitted(lbl); + } + + private void AddCommitted(UIElement el) + { + _mainCanvas.Children.Add(el); + _committedOverlays.Add(el); + } + + private void ClearCommitted() + { + if (_mainCanvas == null) return; + foreach (var el in _committedOverlays) _mainCanvas.Children.Remove(el); + _committedOverlays.Clear(); + } + + // ══════════════════════════════════════════════════════════════ + // 临时卡尺可视化(编辑中,带手柄) + // ══════════════════════════════════════════════════════════════ + + private void RedrawTemp() + { + if (!_lineDefined || _mainCanvas == null) return; + ClearTempOverlays(); + DrawTempCaliper(); + } + + private void DrawTempCaliper() + { + if (_mainCanvas == null) return; + + double dx = _lineEnd.X - _lineStart.X, dy = _lineEnd.Y - _lineStart.Y; + double len = Math.Sqrt(dx * dx + dy * dy); + if (len < 2) return; + + double ux = dx / len, uy = dy / len; + double px = -uy, py = ux; + double hw = _halfWidth; + + var c1 = new Point(_lineStart.X + px * hw, _lineStart.Y + py * hw); + var c2 = new Point(_lineEnd.X + px * hw, _lineEnd.Y + py * hw); + var c3 = new Point(_lineEnd.X - px * hw, _lineEnd.Y - py * hw); + var c4 = new Point(_lineStart.X - px * hw, _lineStart.Y - py * hw); + + // 矩形 + AddTemp(new Polygon + { + Points = new System.Windows.Media.PointCollection { c1, c2, c3, c4 }, + Stroke = CaliperStroke, StrokeThickness = 1, Fill = CaliperFill, IsHitTestVisible = false + }); + + // 搜索线虚线 + AddTemp(new Line + { + X1 = _lineStart.X, Y1 = _lineStart.Y, X2 = _lineEnd.X, Y2 = _lineEnd.Y, + Stroke = CaliperStroke, StrokeThickness = 1, + StrokeDashArray = new DoubleCollection { 4, 3 }, IsHitTestVisible = false + }); + + // 等分线 + int count = CaliperCount; + double step = len / (count + 1); + for (int i = 1; i <= count; i++) + { + double cx = _lineStart.X + ux * step * i, cy = _lineStart.Y + uy * step * i; + AddTemp(new Line + { + X1 = cx + px * hw, Y1 = cy + py * hw, + X2 = cx - px * hw, Y2 = cy - py * hw, + Stroke = CaliperStroke, StrokeThickness = 1, Opacity = 0.4, IsHitTestVisible = false + }); + } + + // 极性箭头 + DrawPolarityArrow(px, py); + + // 手柄位置 + double midX = (_lineStart.X + _lineEnd.X) / 2, midY = (_lineStart.Y + _lineEnd.Y) / 2; + _handleStartPos = _lineStart; + _handleEndPos = _lineEnd; + _handleTopPos = new Point(midX + px * hw, midY + py * hw); + _handleBottomPos = new Point(midX - px * hw, midY - py * hw); + + // 绘制手柄 + AddTemp(MakeHandleVisual(_handleStartPos)); + AddTemp(MakeHandleVisual(_handleEndPos)); + AddTemp(MakeHandleVisual(_handleTopPos)); + AddTemp(MakeHandleVisual(_handleBottomPos)); + } + + private void DrawPolarityArrow(double px, double py) + { + double midX = (_lineStart.X + _lineEnd.X) / 2, midY = (_lineStart.Y + _lineEnd.Y) / 2; + double arrowLen = Math.Min(_halfWidth * 0.6, 16); + + if (EdgePolarity == "Both") + { + DrawArrow(midX, midY, px, py, arrowLen); + DrawArrow(midX, midY, -px, -py, arrowLen); + } + else if (EdgePolarity == "DarkToBright") + DrawArrow(midX, midY, px, py, arrowLen); + else + DrawArrow(midX, midY, -px, -py, arrowLen); + + string txt = EdgePolarity switch { "BrightToDark" => "B→D", "DarkToBright" => "D→B", _ => "↔" }; + var tb = new TextBlock { Text = txt, Foreground = CaliperStroke, FontSize = 10, IsHitTestVisible = false }; + Canvas.SetLeft(tb, midX + px * (_halfWidth + 12)); + Canvas.SetTop(tb, midY + py * (_halfWidth + 12) - 7); + AddTemp(tb); + } + + private void DrawArrow(double fx, double fy, double dx, double dy, double length) + { + double tx = fx + dx * length, ty = fy + dy * length; + AddTemp(new Line { X1 = fx, Y1 = fy, X2 = tx, Y2 = ty, Stroke = CaliperStroke, StrokeThickness = 1.5, IsHitTestVisible = false }); + double ang = Math.Atan2(dy, dx), hl = 5; + double a1 = ang + 2.5, a2 = ang - 2.5; + AddTemp(new Line { X1 = tx, Y1 = ty, X2 = tx + Math.Cos(a1) * hl, Y2 = ty + Math.Sin(a1) * hl, Stroke = CaliperStroke, StrokeThickness = 1.5, IsHitTestVisible = false }); + AddTemp(new Line { X1 = tx, Y1 = ty, X2 = tx + Math.Cos(a2) * hl, Y2 = ty + Math.Sin(a2) * hl, Stroke = CaliperStroke, StrokeThickness = 1.5, IsHitTestVisible = false }); + } + + private Ellipse MakeHandleVisual(Point pos) + { + var h = new Ellipse + { + Width = HandleSize, Height = HandleSize, + Fill = HandleFill, Stroke = CaliperStroke, StrokeThickness = 2, + IsHitTestVisible = false + }; + Canvas.SetLeft(h, pos.X - HandleSize / 2); + Canvas.SetTop(h, pos.Y - HandleSize / 2); + return h; + } + + private void AddTemp(UIElement el) { _mainCanvas.Children.Add(el); _tempOverlays.Add(el); } + private void ClearTempOverlays() + { + if (_mainCanvas == null) return; + foreach (var el in _tempOverlays) _mainCanvas.Children.Remove(el); + _tempOverlays.Clear(); + } + + // ══════════════════════════════════════════════════════════════ + // 统一鼠标交互 + // ══════════════════════════════════════════════════════════════ + + private bool _interactionRegistered; + + private void RegisterInteraction() + { + if (_canvas == null || _interactionRegistered) return; + _canvas.PreviewMouseLeftButtonDown += OnMouseDown; + _canvas.PreviewMouseMove += OnMouseMove; + _canvas.PreviewMouseLeftButtonUp += OnMouseUp; + _interactionRegistered = true; + } + + private void UnregisterAll() + { + if (_canvas == null || !_interactionRegistered) return; + _canvas.PreviewMouseLeftButtonDown -= OnMouseDown; + _canvas.PreviewMouseMove -= OnMouseMove; + _canvas.PreviewMouseLeftButtonUp -= OnMouseUp; + _interactionRegistered = false; + _isDrawingLine = false; + _dragging = DragTarget.None; + } + + private void OnMouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e) + { + if (_mainCanvas == null) return; + var pos = e.GetPosition(_mainCanvas); + + // 绘制模式 + if (_isDrawingLine) + { + _drawClickCount++; + if (_drawClickCount == 1) + { + _lineStart = pos; + ResultText = "请点击搜索线终点"; + } + else if (_drawClickCount == 2) + { + _lineEnd = pos; + _isDrawingLine = false; + _lineDefined = true; + _halfWidth = DisplayWidth / 2.0; + FitCommand.RaiseCanExecuteChanged(); + RedrawTemp(); + ResultText = $"搜索线已定义 ({Len():F0}px)\n拖拽手柄调整,点击「拟合」执行"; + } + e.Handled = true; + return; + } + + // 拖拽模式 + if (_lineDefined) + { + var target = HitTestHandle(pos); + if (target != DragTarget.None) + { + _dragging = target; + _canvas.CaptureMouse(); + e.Handled = true; + } + } + } + + private void OnMouseMove(object sender, System.Windows.Input.MouseEventArgs e) + { + if (_dragging == DragTarget.None || _mainCanvas == null) return; + var pos = e.GetPosition(_mainCanvas); + + switch (_dragging) + { + case DragTarget.Start: + _lineStart = pos; + break; + case DragTarget.End: + _lineEnd = pos; + break; + case DragTarget.Top: + case DragTarget.Bottom: + double dist = PointToLineDist(pos, _lineStart, _lineEnd); + _halfWidth = Math.Max(5, dist); + SetProperty(ref _displayWidth, (int)(_halfWidth * 2), nameof(DisplayWidth)); + break; + } + + ClearTempOverlays(); + DrawTempCaliper(); + e.Handled = true; + } + + private void OnMouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e) + { + if (_dragging == DragTarget.None) return; + _dragging = DragTarget.None; + _canvas.ReleaseMouseCapture(); + ResultText = $"搜索线: {Len():F0}px, 宽度: {(int)(_halfWidth * 2)}px\n点击「拟合」执行"; + e.Handled = true; + } + + private DragTarget HitTestHandle(Point pos) + { + if (Dist(pos, _handleStartPos) <= HitRadius) return DragTarget.Start; + if (Dist(pos, _handleEndPos) <= HitRadius) return DragTarget.End; + if (Dist(pos, _handleTopPos) <= HitRadius) return DragTarget.Top; + if (Dist(pos, _handleBottomPos) <= HitRadius) return DragTarget.Bottom; + return DragTarget.None; + } + + // ══════════════════════════════════════════════════════════════ + // 辅助 + // ══════════════════════════════════════════════════════════════ + + private double Len() + { + double dx = _lineEnd.X - _lineStart.X, dy = _lineEnd.Y - _lineStart.Y; + return Math.Sqrt(dx * dx + dy * dy); + } + + private static double Dist(Point a, Point b) + { + double dx = a.X - b.X, dy = a.Y - b.Y; + return Math.Sqrt(dx * dx + dy * dy); + } + + private static double PointToLineDist(Point p, Point a, Point b) + { + double abx = b.X - a.X, aby = b.Y - a.Y; + double len2 = abx * abx + aby * aby; + if (len2 < 1e-6) return Dist(p, a); + return Math.Abs(abx * (a.Y - p.Y) - aby * (a.X - p.X)) / Math.Sqrt(len2); + } + + private static T FindChild(DependencyObject parent, string name) where T : FrameworkElement + { + int count = VisualTreeHelper.GetChildrenCount(parent); + for (int i = 0; i < count; i++) + { + var child = VisualTreeHelper.GetChild(parent, i); + if (child is T t && t.Name == name) return t; + var r = FindChild(child, name); + if (r != null) return r; + } + return null; + } + } +} diff --git a/XplorePlane/ViewModels/ImageProcessing/TemplateMatchAssistantViewModel.cs b/XplorePlane/ViewModels/ImageProcessing/TemplateMatchAssistantViewModel.cs new file mode 100644 index 0000000..2cb5579 --- /dev/null +++ b/XplorePlane/ViewModels/ImageProcessing/TemplateMatchAssistantViewModel.cs @@ -0,0 +1,507 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Windows; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using Emgu.CV; +using Emgu.CV.Structure; +using Microsoft.Win32; +using Prism.Commands; +using Prism.Events; +using Prism.Ioc; +using Prism.Mvvm; +using XP.Common.Logging.Interfaces; +using XP.ImageProcessing.Processors; +using XplorePlane.Events; +using XplorePlane.Services.MainViewport; +using XplorePlane.ViewModels; +using Serilog; + +namespace XplorePlane.ViewModels.ImageProcessing; + +/// +/// 旋转模板匹配助手:框选 ROI、从 ROI 训练、参数、加载/保存模型、在当页试匹配;批量测试见 。 +/// +public class TemplateMatchAssistantViewModel : BindableBase, IDisposable +{ + private static readonly Serilog.ILogger Log = Serilog.Log.ForContext(); + + private readonly IEventAggregator _eventAggregator; + private readonly IContainerProvider _containerProvider; + private readonly ILoggerService _logger; + private readonly object _matcherLock = new(); + private readonly TemplateMatchBatchViewModel _batch; + private SubscriptionToken? _roiDrawnToken; + private TemplateMatcherHandle? _matcher; + private bool _disposed; + private Int32Rect _pendingTemplateRoi; + private bool _hasPendingTemplateRoi; + + private string _statusMessage = "可先加载模型,或点击「框选模板 ROI」后在主视图框选,再点「从 ROI 训练模板」。批量测试请切换到「批量测试」选项卡。"; + private double _matchThreshold = 0.75; + private double _toleranceAngle; + private double _maxMatchCount = 5; + private double _maxOverlap = 0.3; + private double _minReduceArea = 256; + private bool _useSimd = true; + private bool _useSubPixel; + private bool _isModelReady; + + public TemplateMatchAssistantViewModel( + IEventAggregator eventAggregator, + IContainerProvider containerProvider, + ILoggerService logger) + { + _eventAggregator = eventAggregator; + _containerProvider = containerProvider; + _logger = logger; + + SelectTemplateRoiCommand = new DelegateCommand(ExecuteSelectTemplateRoi); + LearnFromRoiCommand = new DelegateCommand(ExecuteLearnFromRoi, () => _hasPendingTemplateRoi); + LoadModelCommand = new DelegateCommand(ExecuteLoadModel); + SaveModelCommand = new DelegateCommand(ExecuteSaveModel, () => _isModelReady && _matcher != null); + RunMatchCommand = new DelegateCommand(ExecuteRunMatch, () => _isModelReady && _matcher != null); + + _batch = new TemplateMatchBatchViewModel( + this, + _eventAggregator, + _containerProvider.Resolve()); + + _roiDrawnToken = _eventAggregator.GetEvent() + .Subscribe(OnTemplateRoiDrawn, ThreadOption.UIThread); + } + + /// 批量测试子页(与助手共用同一模型与参数)。 + public TemplateMatchBatchViewModel Batch => _batch; + + public DelegateCommand SelectTemplateRoiCommand { get; } + public DelegateCommand LearnFromRoiCommand { get; } + public DelegateCommand LoadModelCommand { get; } + public DelegateCommand SaveModelCommand { get; } + public DelegateCommand RunMatchCommand { get; } + + public string StatusMessage + { + get => _statusMessage; + set => SetProperty(ref _statusMessage, value); + } + + public double MatchThreshold + { + get => _matchThreshold; + set => SetProperty(ref _matchThreshold, Math.Clamp(value, 0, 1)); + } + + public double ToleranceAngle + { + get => _toleranceAngle; + set => SetProperty(ref _toleranceAngle, Math.Clamp(value, 0, 180)); + } + + /// 最大匹配数(滑块 1~100,运行匹配时取整)。 + public double MaxMatchCount + { + get => _maxMatchCount; + set => SetProperty(ref _maxMatchCount, Math.Clamp(value, 1, 100)); + } + + public double MaxOverlap + { + get => _maxOverlap; + set => SetProperty(ref _maxOverlap, Math.Clamp(value, 0, 1)); + } + + /// 金字塔最小面积(滑块 64~4096,运行匹配时取整)。 + public double MinReduceArea + { + get => _minReduceArea; + set => SetProperty(ref _minReduceArea, Math.Clamp(value, 64, 4096)); + } + + public bool UseSimd + { + get => _useSimd; + set => SetProperty(ref _useSimd, value); + } + + public bool UseSubPixel + { + get => _useSubPixel; + set => SetProperty(ref _useSubPixel, value); + } + + public bool IsModelReady + { + get => _isModelReady; + private set + { + if (SetProperty(ref _isModelReady, value)) + { + SaveModelCommand.RaiseCanExecuteChanged(); + RunMatchCommand.RaiseCanExecuteChanged(); + } + } + } + + /// 是否已有框选完成的模板 ROI(与是否已训练无关)。 + public bool HasPendingTemplateRoi + { + get => _hasPendingTemplateRoi; + private set + { + if (SetProperty(ref _hasPendingTemplateRoi, value)) + LearnFromRoiCommand.RaiseCanExecuteChanged(); + } + } + + /// + /// 清除主视图上框选的模板学习 ROI 叠加,并重置待训练 ROI 状态(运行匹配或开始批量测试前调用)。 + /// + public void ClearTemplateLearningRoiOnViewport() + { + _eventAggregator.GetEvent().Publish(); + HasPendingTemplateRoi = false; + } + + private void ExecuteSelectTemplateRoi() + { + _eventAggregator.GetEvent().Publish(); + StatusMessage = "请在主视图图像上拖拽框选模板区域;框选完成后 ROI 会保留在图上,再点击「从 ROI 训练模板」。"; + } + + private void OnTemplateRoiDrawn(Int32Rect roi) + { + var viewportVm = _containerProvider.Resolve(); + var imageSource = viewportVm?.ImageSource as BitmapSource; + if (imageSource == null) + { + StatusMessage = "当前无主视图图像,无法记录模板 ROI。"; + HasPendingTemplateRoi = false; + return; + } + + int imgW = imageSource.PixelWidth; + int imgH = imageSource.PixelHeight; + int rx = Math.Clamp(roi.X, 0, Math.Max(0, imgW - 1)); + int ry = Math.Clamp(roi.Y, 0, Math.Max(0, imgH - 1)); + int rw = Math.Clamp(roi.Width, 1, Math.Max(1, imgW - rx)); + int rh = Math.Clamp(roi.Height, 1, Math.Max(1, imgH - ry)); + + _pendingTemplateRoi = new Int32Rect(rx, ry, rw, rh); + HasPendingTemplateRoi = true; + StatusMessage = $"已框选模板区域 {rw}×{rh} 像素。请点击「从 ROI 训练模板」。"; + } + + private void ExecuteLearnFromRoi() + { + if (!HasPendingTemplateRoi) return; + + try + { + var viewportVm = _containerProvider.Resolve(); + var imageSource = viewportVm?.ImageSource as BitmapSource; + if (imageSource == null) + { + StatusMessage = "当前无主视图图像,无法学习模板。"; + return; + } + + BitmapSource gray = imageSource.Format == PixelFormats.Gray8 + ? imageSource + : new FormatConvertedBitmap(imageSource, PixelFormats.Gray8, null, 0); + + int imgW = gray.PixelWidth; + int imgH = gray.PixelHeight; + int rx = Math.Clamp(_pendingTemplateRoi.X, 0, imgW - 1); + int ry = Math.Clamp(_pendingTemplateRoi.Y, 0, imgH - 1); + int rw = Math.Clamp(_pendingTemplateRoi.Width, 1, imgW - rx); + int rh = Math.Clamp(_pendingTemplateRoi.Height, 1, imgH - ry); + + var pixels = new byte[rw * rh]; + gray.CopyPixels(new Int32Rect(rx, ry, rw, rh), pixels, rw, 0); + + using var roiImage = new Image(rw, rh); + for (int y = 0; y < rh; y++) + for (int x = 0; x < rw; x++) + roiImage.Data[y, x, 0] = pixels[y * rw + x]; + + lock (_matcherLock) + { + _matcher?.Dispose(); + _matcher = new TemplateMatcherHandle(); + IntPtr p = roiImage.Mat.DataPointer; + int step = (int)roiImage.Mat.Step; + if (!_matcher.LearnPattern(p, rw, rh, step)) + { + _matcher.Dispose(); + _matcher = null; + IsModelReady = false; + StatusMessage = "模板学习失败。"; + _logger.Warn("Template assistant: LearnPattern failed for ROI {0},{1},{2},{3}", rx, ry, rw, rh); + return; + } + } + + IsModelReady = true; + StatusMessage = $"模板已从 ROI 学习成功({rw}×{rh} 像素)。可保存模型或运行匹配。"; + _logger.Info("Template assistant: learned from ROI {0},{1},{2},{3}", rx, ry, rw, rh); + } + catch (Exception ex) + { + Log.Error(ex, "Template assistant ROI learn failed"); + StatusMessage = $"学习失败: {ex.Message}"; + IsModelReady = false; + } + } + + private void ExecuteLoadModel() + { + var dlg = new OpenFileDialog + { + Title = "加载模板模型", + Filter = "模板模型|*.tmmodel;*.tm|所有文件|*.*" + }; + if (dlg.ShowDialog() != true) return; + + try + { + lock (_matcherLock) + { + _matcher?.Dispose(); + _matcher = new TemplateMatcherHandle(); + if (!_matcher.LoadModel(dlg.FileName)) + { + _matcher.Dispose(); + _matcher = null; + IsModelReady = false; + StatusMessage = "模型加载失败。"; + return; + } + } + + HasPendingTemplateRoi = false; + _eventAggregator.GetEvent().Publish(); + + IsModelReady = true; + StatusMessage = $"已加载模型: {Path.GetFileName(dlg.FileName)}"; + } + catch (Exception ex) + { + Log.Error(ex, "LoadModel failed"); + StatusMessage = $"加载失败: {ex.Message}"; + IsModelReady = false; + } + } + + private void ExecuteSaveModel() + { + if (_matcher == null || !IsModelReady) return; + + var dlg = new SaveFileDialog + { + Title = "保存模板模型", + Filter = "模板模型|*.tmmodel|所有文件|*.*", + DefaultExt = ".tmmodel" + }; + if (dlg.ShowDialog() != true) return; + + try + { + bool ok; + lock (_matcherLock) + ok = _matcher != null && _matcher.SaveModel(dlg.FileName); + if (ok) + StatusMessage = $"模型已保存: {dlg.FileName}"; + else + StatusMessage = "模型保存失败。"; + } + catch (Exception ex) + { + Log.Error(ex, "SaveModel failed"); + StatusMessage = $"保存失败: {ex.Message}"; + } + } + + private void ExecuteRunMatch() + { + if (_matcher == null || !IsModelReady) return; + + ClearTemplateLearningRoiOnViewport(); + + try + { + var viewportVm = _containerProvider.Resolve(); + var imageSource = viewportVm?.ImageSource as BitmapSource; + if (imageSource == null) + { + StatusMessage = "当前无主视图图像。"; + return; + } + + using Image? full = BitmapSourceToGrayImage(imageSource); + if (full == null) return; + + bool forcedSubPixelOff = UseSubPixel && + Math.Abs(ToleranceAngle) > TM_Params.SubPixelAngleSafetyLimitDegrees; + int templatePixels = 0; + lock (_matcherLock) + { + if (_matcher != null && _matcher.GetTemplateInfo(out int tw, out int th, out _)) + templatePixels = Math.Max(0, tw) * Math.Max(0, th); + } + + bool bumpedMinReduce = templatePixels >= 512 && + (int)Math.Clamp(Math.Round(MinReduceArea), 64, 4096) < 256; + + if (!TryMatchGrayImage(full, out IReadOnlyList hitsReadonly, out double t, out string? matchErr)) + { + if (matchErr != null && matchErr.Contains("TemplateMatchLib", StringComparison.OrdinalIgnoreCase)) + StatusMessage = "未找到 TemplateMatchLib.dll,请确认已复制到输出目录。"; + else + StatusMessage = string.IsNullOrEmpty(matchErr) ? "匹配失败。" : matchErr; + _eventAggregator.GetEvent().Publish(new TemplateMatchPreviewPayload()); + return; + } + + var hits = new List(hitsReadonly); + + _eventAggregator.GetEvent().Publish( + new TemplateMatchPreviewPayload { Hits = hits, MatchTimeMs = t }); + + StatusMessage = hits.Count == 0 + ? $"未找到匹配(耗时 {t:F1} ms)。可调低阈值或角度范围。" + : $"匹配到 {hits.Count} 个目标,耗时 {t:F1} ms。"; + if (forcedSubPixelOff) + StatusMessage += $" 已自动关闭亚像素(角度容差>{TM_Params.SubPixelAngleSafetyLimitDegrees:F0}° 时匹配库易崩溃)。"; + if (bumpedMinReduce) + StatusMessage += " 已将金字塔最小面积提升至不低于 256(与库默认一致)。"; + } + catch (Exception ex) + { + Log.Error(ex, "RunMatch failed"); + StatusMessage = $"匹配失败: {ex.Message}"; + _eventAggregator.GetEvent().Publish(new TemplateMatchPreviewPayload()); + } + } + + private TM_Params BuildCurrentMatchParams() => new TM_Params + { + Score = MatchThreshold, + ToleranceAngle = ToleranceAngle, + MaxOverlap = MaxOverlap, + MaxCount = (int)Math.Clamp(Math.Round(MaxMatchCount), 1, 100), + MinReduceArea = (int)Math.Clamp(Math.Round(MinReduceArea), 64, 4096), + UseSIMD = UseSimd ? 1 : 0, + UseSubPixel = UseSubPixel ? 1 : 0 + }; + + /// + /// 使用当前助手参数对灰度图做模板匹配(与单张「运行匹配」一致)。供批量测试在后台线程调用,内部已加锁。 + /// + public bool TryMatchGrayImage(Image fullImage, out IReadOnlyList hits, + out double matchTimeMs, out string? errorMessage) + { + hits = Array.Empty(); + matchTimeMs = 0; + errorMessage = null; + if (fullImage == null) + { + errorMessage = "图像为空"; + return false; + } + + lock (_matcherLock) + { + if (_matcher == null || !IsModelReady) + { + errorMessage = "模型未就绪"; + return false; + } + + try + { + var param = BuildCurrentMatchParams(); + var results = _matcher.Match( + fullImage.Mat.DataPointer, + fullImage.Width, + fullImage.Height, + (int)fullImage.Mat.Step, + param); + + matchTimeMs = _matcher.LastMatchTime; + var list = new List(); + foreach (var r in results) + { + list.Add(new TemplateMatchHitDto + { + CenterX = r.CenterX, + CenterY = r.CenterY, + Angle = r.Angle, + Score = r.Score, + LtX = r.LtX, LtY = r.LtY, + RtX = r.RtX, RtY = r.RtY, + RbX = r.RbX, RbY = r.RbY, + LbX = r.LbX, LbY = r.LbY + }); + } + + hits = list; + return true; + } + catch (DllNotFoundException) + { + errorMessage = "TemplateMatchLib.dll 未找到"; + return false; + } + catch (Exception ex) + { + Log.Error(ex, "TryMatchGrayImage failed"); + errorMessage = ex.Message; + return false; + } + } + } + + internal static Image? BitmapSourceToGrayImage(BitmapSource bitmapSource) + { + BitmapSource source = bitmapSource.Format == PixelFormats.Gray8 + ? bitmapSource + : new FormatConvertedBitmap(bitmapSource, PixelFormats.Gray8, null, 0); + + int width = source.PixelWidth; + int height = source.PixelHeight; + if (width < 1 || height < 1) return null; + + int stride = width; + var pixels = new byte[width * height]; + source.CopyPixels(pixels, stride, 0); + + var image = new Image(width, height); + for (int y = 0; y < height; y++) + for (int x = 0; x < width; x++) + image.Data[y, x, 0] = pixels[y * stride + x]; + + return image; + } + + public void Dispose() + { + if (_disposed) return; + _disposed = true; + + if (_roiDrawnToken != null) + { + _eventAggregator.GetEvent().Unsubscribe(_roiDrawnToken); + _roiDrawnToken = null; + } + + _batch.Dispose(); + + lock (_matcherLock) + { + _matcher?.Dispose(); + _matcher = null; + } + } +} diff --git a/XplorePlane/ViewModels/ImageProcessing/TemplateMatchBatchViewModel.cs b/XplorePlane/ViewModels/ImageProcessing/TemplateMatchBatchViewModel.cs new file mode 100644 index 0000000..fa70d43 --- /dev/null +++ b/XplorePlane/ViewModels/ImageProcessing/TemplateMatchBatchViewModel.cs @@ -0,0 +1,340 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; +using Emgu.CV; +using Emgu.CV.CvEnum; +using Emgu.CV.Structure; +using Microsoft.Win32; +using Prism.Commands; +using Prism.Events; +using Prism.Mvvm; +using XplorePlane.Events; +using XplorePlane.Services.MainViewport; + +namespace XplorePlane.ViewModels.ImageProcessing; + +/// 单张批量匹配结果行(绑定到 DataGrid)。 +public sealed class TemplateMatchBatchRow +{ + public string FileName { get; init; } = ""; + public string FullPath { get; init; } = ""; + /// 简要结果:命中 / 未找到 / 失败原因摘要。 + public string Result { get; init; } = ""; + public int MatchCount { get; init; } + public double BestScore { get; init; } + public double TimeMs { get; init; } + public string? ErrorDetail { get; init; } + public IReadOnlyList Hits { get; init; } = Array.Empty(); +} + +/// +/// 模板匹配批量测试:扫描文件夹、逐张匹配(与助手当前参数一致)、在主视口打开选中结果。 +/// +public class TemplateMatchBatchViewModel : BindableBase, IDisposable +{ + private static readonly Serilog.ILogger Log = Serilog.Log.ForContext(); + private static readonly HashSet ImageExtensions = new(StringComparer.OrdinalIgnoreCase) + { + ".bmp", ".png", ".jpg", ".jpeg", ".tif", ".tiff" + }; + + private readonly TemplateMatchAssistantViewModel _assistant; + private readonly IEventAggregator _eventAggregator; + private readonly IMainViewportService _mainViewportService; + private readonly List _imagePaths = new(); + private CancellationTokenSource? _batchCts; + private bool _disposed; + private bool _isRunning; + private string _folderPath = ""; + private string _batchStatusText = "请选择文件夹后点击「开始批量匹配」。"; + private TemplateMatchBatchRow? _selectedRow; + private int _imageFileCount; + + public TemplateMatchBatchViewModel( + TemplateMatchAssistantViewModel assistant, + IEventAggregator eventAggregator, + IMainViewportService mainViewportService) + { + _assistant = assistant ?? throw new ArgumentNullException(nameof(assistant)); + _eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator)); + _mainViewportService = mainViewportService ?? throw new ArgumentNullException(nameof(mainViewportService)); + + _assistant.PropertyChanged += OnAssistantPropertyChanged; + + PickFolderCommand = new DelegateCommand(ExecutePickFolder); + StartBatchCommand = new DelegateCommand(ExecuteStartBatch, CanStartBatch); + StopBatchCommand = new DelegateCommand(ExecuteStopBatch, () => _isRunning); + OpenSelectedInMainViewportCommand = new DelegateCommand(ExecuteOpenSelectedInMainViewport, () => SelectedRow != null); + } + + private void OnAssistantPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(TemplateMatchAssistantViewModel.IsModelReady)) + StartBatchCommand.RaiseCanExecuteChanged(); + } + + public ObservableCollection Rows { get; } = new(); + + public DelegateCommand PickFolderCommand { get; } + public DelegateCommand StartBatchCommand { get; } + public DelegateCommand StopBatchCommand { get; } + public DelegateCommand OpenSelectedInMainViewportCommand { get; } + + public string FolderPath + { + get => _folderPath; + private set => SetProperty(ref _folderPath, value); + } + + public string BatchStatusText + { + get => _batchStatusText; + private set => SetProperty(ref _batchStatusText, value); + } + + public int ImageFileCount + { + get => _imageFileCount; + private set => SetProperty(ref _imageFileCount, value); + } + + public bool IsRunning + { + get => _isRunning; + private set + { + if (SetProperty(ref _isRunning, value)) + { + StartBatchCommand.RaiseCanExecuteChanged(); + StopBatchCommand.RaiseCanExecuteChanged(); + } + } + } + + public TemplateMatchBatchRow? SelectedRow + { + get => _selectedRow; + set + { + if (SetProperty(ref _selectedRow, value)) + OpenSelectedInMainViewportCommand.RaiseCanExecuteChanged(); + } + } + + /// 供宿主窗口 DataGrid 双击调用。 + public void OpenSelectedFromDoubleClick() => OpenSelectedInMainViewportCommand.Execute(); + + private bool CanStartBatch() => + !_isRunning && _imagePaths.Count > 0 && _assistant.IsModelReady; + + private void ExecutePickFolder() + { + var dlg = new OpenFolderDialog + { + Title = "选择待测试图像所在文件夹(仅当前目录,不含子文件夹)", + InitialDirectory = Directory.Exists(_folderPath) ? _folderPath : Environment.GetFolderPath(Environment.SpecialFolder.MyPictures) + }; + + if (dlg.ShowDialog() != true) + return; + + ScanFolder(dlg.FolderName); + } + + private void ScanFolder(string folder) + { + _imagePaths.Clear(); + if (!Directory.Exists(folder)) + { + FolderPath = ""; + ImageFileCount = 0; + BatchStatusText = "文件夹不存在。"; + StartBatchCommand.RaiseCanExecuteChanged(); + return; + } + + foreach (var path in Directory.EnumerateFiles(folder, "*.*", SearchOption.TopDirectoryOnly) + .OrderBy(p => p, StringComparer.OrdinalIgnoreCase)) + { + if (ImageExtensions.Contains(Path.GetExtension(path))) + _imagePaths.Add(path); + } + + FolderPath = folder; + ImageFileCount = _imagePaths.Count; + BatchStatusText = ImageFileCount == 0 + ? "该文件夹下没有支持的图像文件(bmp/png/jpg/tif…)。" + : $"已扫描 {ImageFileCount} 个图像文件,可开始批量匹配。"; + StartBatchCommand.RaiseCanExecuteChanged(); + } + + private async void ExecuteStartBatch() + { + if (!CanStartBatch()) return; + + _assistant.ClearTemplateLearningRoiOnViewport(); + + Rows.Clear(); + IsRunning = true; + _batchCts = new CancellationTokenSource(); + var token = _batchCts.Token; + int total = _imagePaths.Count; + int index = 0; + + try + { + foreach (var path in _imagePaths) + { + token.ThrowIfCancellationRequested(); + index++; + BatchStatusText = $"正在处理 {index}/{total}:{Path.GetFileName(path)}"; + + var row = await Task.Run(() => ProcessOneFile(path), token).ConfigureAwait(true); + Rows.Add(row); + } + + BatchStatusText = $"完成,共处理 {Rows.Count} 张。"; + } + catch (OperationCanceledException) + { + BatchStatusText = $"已停止(已处理 {Rows.Count}/{total} 张)。"; + } + catch (Exception ex) + { + Log.Error(ex, "Batch template match failed"); + BatchStatusText = $"批量过程异常:{ex.Message}"; + } + finally + { + IsRunning = false; + _batchCts?.Dispose(); + _batchCts = null; + } + } + + private void ExecuteStopBatch() => _batchCts?.Cancel(); + + private TemplateMatchBatchRow ProcessOneFile(string path) + { + var fileName = Path.GetFileName(path); + try + { + using var mat = CvInvoke.Imread(path, ImreadModes.Grayscale); + if (mat == null || mat.IsEmpty) + { + return new TemplateMatchBatchRow + { + FileName = fileName, + FullPath = path, + Result = "无法读取", + ErrorDetail = "Imread 为空或失败" + }; + } + + using var gray = mat.ToImage(); + if (!_assistant.TryMatchGrayImage(gray, out var hits, out var t, out var err)) + { + return new TemplateMatchBatchRow + { + FileName = fileName, + FullPath = path, + Result = "失败", + ErrorDetail = err, + TimeMs = t + }; + } + + int c = hits.Count; + double best = c == 0 ? 0 : hits.Max(h => h.Score); + return new TemplateMatchBatchRow + { + FileName = fileName, + FullPath = path, + Result = c > 0 ? "命中" : "未找到", + MatchCount = c, + BestScore = best, + TimeMs = t, + Hits = CloneHits(hits) + }; + } + catch (Exception ex) + { + Log.Warning(ex, "ProcessOneFile: {Path}", path); + return new TemplateMatchBatchRow + { + FileName = fileName, + FullPath = path, + Result = "异常", + ErrorDetail = ex.Message + }; + } + } + + private static List CloneHits(IReadOnlyList src) + { + var list = new List(src.Count); + foreach (var h in src) + { + list.Add(new TemplateMatchHitDto + { + CenterX = h.CenterX, + CenterY = h.CenterY, + Angle = h.Angle, + Score = h.Score, + LtX = h.LtX, LtY = h.LtY, + RtX = h.RtX, RtY = h.RtY, + RbX = h.RbX, RbY = h.RbY, + LbX = h.LbX, LbY = h.LbY + }); + } + + return list; + } + + private void ExecuteOpenSelectedInMainViewport() + { + if (SelectedRow == null || string.IsNullOrWhiteSpace(SelectedRow.FullPath) || !File.Exists(SelectedRow.FullPath)) + return; + + try + { + var bitmap = new BitmapImage(); + bitmap.BeginInit(); + bitmap.UriSource = new Uri(SelectedRow.FullPath, UriKind.Absolute); + bitmap.CacheOption = BitmapCacheOption.OnLoad; + bitmap.EndInit(); + bitmap.Freeze(); + + _mainViewportService.SetManualImage(bitmap, SelectedRow.FullPath); + _eventAggregator.GetEvent() + .Publish(new ManualImageLoadedPayload(bitmap, SelectedRow.FullPath)); + + var hits = CloneHits(SelectedRow.Hits); + _eventAggregator.GetEvent().Publish( + new TemplateMatchPreviewPayload { Hits = hits, MatchTimeMs = SelectedRow.TimeMs }); + + BatchStatusText = $"已在主视图打开:{SelectedRow.FileName}"; + } + catch (Exception ex) + { + Log.Error(ex, "OpenSelectedInMainViewport"); + BatchStatusText = $"打开失败:{ex.Message}"; + } + } + + public void Dispose() + { + if (_disposed) return; + _disposed = true; + _assistant.PropertyChanged -= OnAssistantPropertyChanged; + _batchCts?.Cancel(); + _batchCts?.Dispose(); + _batchCts = null; + } +} diff --git a/XplorePlane/ViewModels/Main/MainViewModel.cs b/XplorePlane/ViewModels/Main/MainViewModel.cs index f3ab7e6..b2648b8 100644 --- a/XplorePlane/ViewModels/Main/MainViewModel.cs +++ b/XplorePlane/ViewModels/Main/MainViewModel.cs @@ -36,6 +36,13 @@ using XP.Common.PdfViewer.Interfaces; using XP.Hardware.MotionControl.Abstractions; using XP.Hardware.MotionControl.Services; using System.Windows.Threading; +using XP.ImageProcessing.Processors; +using XplorePlane.Services.Storage; +using XplorePlane.ViewModels.Cnc; +using XplorePlane.ViewModels.ImageProcessing; +using XplorePlane.Views; +using XplorePlane.Views.Cnc; +using XplorePlane.Views.ImageProcessing; namespace XplorePlane.ViewModels { @@ -180,9 +187,12 @@ namespace XplorePlane.ViewModels public DelegateCommand WhiteBackgroundDetectionCommand { get; } public DelegateCommand BlackBackgroundDetectionCommand { get; } + public DelegateCommand OpenTemplateMatchAssistantCommand { get; } public DelegateCommand GrayscaleCommand { get; } public DelegateCommand SharpenCommand { get; } public DelegateCommand EnhanceCommand { get; } + public DelegateCommand EdgeLineFitCommand { get; } + public DelegateCommand EdgeCircleFitCommand { get; } // 设置命令 public DelegateCommand OpenLanguageSwitcherCommand { get; } @@ -277,6 +287,7 @@ namespace XplorePlane.ViewModels private Window _reportConfigWindow; private Window _inspectionReportViewerWindow; private Window _debugPanelWindow; + private Window _templateMatchAssistantWindow; private object _imagePanelContent; private GridLength _viewportPanelWidth = new(1, GridUnitType.Star); private GridLength _imagePanelWidth = new(320); @@ -332,6 +343,12 @@ namespace XplorePlane.ViewModels _eventAggregator.GetEvent() .Subscribe(OnPipelinePreviewUpdated, ThreadOption.UIThread); + _eventAggregator.GetEvent() + .Subscribe(OnWhiteBackgroundRoiDrawn, ThreadOption.UIThread); + + _eventAggregator.GetEvent() + .Subscribe(OnBlackBackgroundRoiDrawn, ThreadOption.UIThread); + NavigationTree = new ObservableCollection(); NavigateHomeCommand = new DelegateCommand(OnNavigateHome); @@ -394,9 +411,12 @@ namespace XplorePlane.ViewModels // 图像处理命令 WhiteBackgroundDetectionCommand = new DelegateCommand(ExecuteWhiteBackgroundDetection); BlackBackgroundDetectionCommand = new DelegateCommand(ExecuteBlackBackgroundDetection); + OpenTemplateMatchAssistantCommand = new DelegateCommand(ExecuteOpenTemplateMatchAssistant); GrayscaleCommand = new DelegateCommand(ExecuteGrayscale); SharpenCommand = new DelegateCommand(ExecuteSharpen); EnhanceCommand = new DelegateCommand(ExecuteEnhance); + EdgeLineFitCommand = new DelegateCommand(ExecuteEdgeLineFit); + EdgeCircleFitCommand = new DelegateCommand(ExecuteEdgeCircleFit); AxisResetCommand = new DelegateCommand(ExecuteAxisReset); OpenDoorCommand = new DelegateCommand(ExecuteOpenDoor); @@ -1045,6 +1065,8 @@ namespace XplorePlane.ViewModels } private Window _bgaDetectionPanel; + private Window _edgeLineFitPanel; + private Window _edgeCircleFitPanel; private void ExecuteBgaDetection() { @@ -1116,22 +1138,148 @@ namespace XplorePlane.ViewModels private void ExecuteWhiteBackgroundDetection() { if (!CheckImageLoaded()) return; - _logger.Info("White background detection triggered."); - // TODO: 实现白底检测逻辑 + _logger.Info("White background detection: entering ROI draw mode."); + _eventAggregator.GetEvent().Publish(); + StatusMessage = "白底检测:请在图像上拖拽绘制矩形ROI"; } + private void OnWhiteBackgroundRoiDrawn(System.Windows.Int32Rect roi) => + RunBackgroundRoiDetection(roi, BackgroundDefectMode.WhiteBackground); + private void ExecuteBlackBackgroundDetection() { if (!CheckImageLoaded()) return; - _logger.Info("Black background detection triggered."); - // TODO: 实现黑底检测逻辑 + _logger.Info("Black background detection: entering ROI draw mode."); + _eventAggregator.GetEvent().Publish(); + StatusMessage = "黑底检测:请在图像上拖拽绘制矩形ROI"; + } + + private void ExecuteOpenTemplateMatchAssistant() + { + try + { + if (!CheckImageLoaded()) + { + StatusMessage = "请先加载图像再使用模板助手。"; + return; + } + + if (_templateMatchAssistantWindow != null) + { + if (_templateMatchAssistantWindow.IsLoaded) + { + _templateMatchAssistantWindow.Activate(); + return; + } + + _templateMatchAssistantWindow = null; + } + + var vm = _containerProvider.Resolve(); + var w = new TemplateMatchAssistantWindow + { + DataContext = vm, + Owner = Application.Current?.MainWindow + }; + w.Closed += (_, _) => { _templateMatchAssistantWindow = null; }; + _templateMatchAssistantWindow = w; + w.Show(); + _logger.Info("Template match assistant opened."); + StatusMessage = "已打开模板匹配助手"; + } + catch (Exception ex) + { + _logger.Error(ex, "Failed to open template match assistant"); + StatusMessage = $"打开模板助手失败: {ex.Message}"; + } + } + + private void OnBlackBackgroundRoiDrawn(System.Windows.Int32Rect roi) => + RunBackgroundRoiDetection(roi, BackgroundDefectMode.BlackBackground); + + /// + /// 从视口灰度图取 ROI,调用 ,再发布结果事件(全局坐标)。 + /// + private void RunBackgroundRoiDetection(System.Windows.Int32Rect roi, BackgroundDefectMode mode) + { + try + { + var viewportVm = _containerProvider.Resolve(); + var imageSource = viewportVm?.ImageSource as System.Windows.Media.Imaging.BitmapSource; + if (imageSource == null) return; + + System.Windows.Media.Imaging.BitmapSource gray8; + if (imageSource.Format != System.Windows.Media.PixelFormats.Gray8) + gray8 = new System.Windows.Media.Imaging.FormatConvertedBitmap( + imageSource, System.Windows.Media.PixelFormats.Gray8, null, 0); + else + gray8 = imageSource; + + int imgW = gray8.PixelWidth; + int imgH = gray8.PixelHeight; + + int rx = Math.Clamp(roi.X, 0, imgW - 1); + int ry = Math.Clamp(roi.Y, 0, imgH - 1); + int rw = Math.Clamp(roi.Width, 1, imgW - rx); + int rh = Math.Clamp(roi.Height, 1, imgH - ry); + + byte[] roiPixels = new byte[rw * rh]; + gray8.CopyPixels(new System.Windows.Int32Rect(rx, ry, rw, rh), roiPixels, rw, 0); + + using var roiImage = new Image(rw, rh); + for (int y = 0; y < rh; y++) + for (int x = 0; x < rw; x++) + roiImage.Data[y, x, 0] = roiPixels[y * rw + x]; + + const int minArea = 50; + const double mmPerPixel = 0.139; + var blobs = BackgroundDefectAnalyzer.DetectBlobs(roiImage, mode, minArea, mmPerPixel); + + var detections = new System.Collections.Generic.List(blobs.Count); + foreach (var b in blobs) + { + var item = new BackgroundDefectDetectionItem + { + SizeMicrometers = b.MaxChordMicrometers, + ChordP1 = new System.Drawing.Point(b.MaxChordEndAInRoi.X + rx, b.MaxChordEndAInRoi.Y + ry), + ChordP2 = new System.Drawing.Point(b.MaxChordEndBInRoi.X + rx, b.MaxChordEndBInRoi.Y + ry) + }; + foreach (var p in b.ContourInRoi) + item.Contour.Add(new System.Drawing.Point(p.X + rx, p.Y + ry)); + detections.Add(item); + } + + var roiRect = new System.Drawing.Rectangle(rx, ry, rw, rh); + if (mode == BackgroundDefectMode.WhiteBackground) + { + _eventAggregator.GetEvent().Publish( + new WhiteBackgroundResultPayload { RoiRect = roiRect, Detections = detections }); + StatusMessage = $"白底检测完成:检测到 {detections.Count} 个黑色区域"; + _logger.Info("White background detection: found {Count} dark regions in ROI ({X},{Y},{W},{H})", + detections.Count, rx, ry, rw, rh); + } + else + { + _eventAggregator.GetEvent().Publish( + new BlackBackgroundResultPayload { RoiRect = roiRect, Detections = detections }); + StatusMessage = $"黑底检测完成:检测到 {detections.Count} 个亮色区域"; + _logger.Info("Black background detection: found {Count} bright regions in ROI ({X},{Y},{W},{H})", + detections.Count, rx, ry, rw, rh); + } + } + catch (Exception ex) + { + string label = mode == BackgroundDefectMode.WhiteBackground ? "白底" : "黑底"; + _logger.Error(ex, "{Label} background detection failed", label); + StatusMessage = $"{label}检测失败: {ex.Message}"; + } } private void ExecuteGrayscale() { if (!CheckImageLoaded()) return; - _logger.Info("Grayscale conversion triggered."); - // TODO: 实现灰度转换逻辑 + _logger.Info("Line profile toggled."); + _eventAggregator.GetEvent().Publish(); } private void ExecuteSharpen() @@ -1203,6 +1351,44 @@ namespace XplorePlane.ViewModels } } + private void ExecuteEdgeLineFit() + { + if (!CheckImageLoaded()) return; + _logger.Info("边缘查找拟合直线功能已触发"); + + if (_edgeLineFitPanel != null && _edgeLineFitPanel.IsVisible) + { + _edgeLineFitPanel.Activate(); + return; + } + + _edgeLineFitPanel = new Views.ImageProcessing.EdgeLineFitPanel + { + Owner = System.Windows.Application.Current.MainWindow + }; + _edgeLineFitPanel.Closed += (_, _) => { _edgeLineFitPanel = null; }; + _edgeLineFitPanel.Show(); + } + + private void ExecuteEdgeCircleFit() + { + if (!CheckImageLoaded()) return; + _logger.Info("边缘查找拟合圆功能已触发"); + + if (_edgeCircleFitPanel != null && _edgeCircleFitPanel.IsVisible) + { + _edgeCircleFitPanel.Activate(); + return; + } + + _edgeCircleFitPanel = new Views.ImageProcessing.EdgeCircleFitPanel + { + Owner = System.Windows.Application.Current.MainWindow + }; + _edgeCircleFitPanel.Closed += (_, _) => { _edgeCircleFitPanel = null; }; + _edgeCircleFitPanel.Show(); + } + private Image? BitmapSourceToImage(BitmapSource bitmapSource) { // 转换为可用的图像格式 diff --git a/XplorePlane/ViewModels/Main/NavigationPropertyPanelViewModel.cs b/XplorePlane/ViewModels/Main/NavigationPropertyPanelViewModel.cs index 586e99f..1c5d728 100644 --- a/XplorePlane/ViewModels/Main/NavigationPropertyPanelViewModel.cs +++ b/XplorePlane/ViewModels/Main/NavigationPropertyPanelViewModel.cs @@ -55,9 +55,6 @@ namespace XplorePlane.ViewModels StopGrabCommand.RaiseCanExecuteChanged(); ApplyExposureCommand.RaiseCanExecuteChanged(); ApplyGainCommand.RaiseCanExecuteChanged(); - ApplyWidthCommand.RaiseCanExecuteChanged(); - ApplyHeightCommand.RaiseCanExecuteChanged(); - ApplyPixelFormatCommand.RaiseCanExecuteChanged(); RefreshCameraParamsCommand.RaiseCanExecuteChanged(); OpenCameraSettingsCommand.RaiseCanExecuteChanged(); } @@ -79,7 +76,7 @@ namespace XplorePlane.ViewModels } } - private string _cameraStatusText = "未连接"; + private string _cameraStatusText = "Disconnected"; public string CameraStatusText { @@ -152,8 +149,6 @@ namespace XplorePlane.ViewModels set => SetProperty(ref _selectedPixelFormat, value); } - public ObservableCollection PixelFormatOptions { get; } = new() { "Mono8", "BGR8", "BGRA8" }; - #endregion Properties #region Commands @@ -164,9 +159,6 @@ namespace XplorePlane.ViewModels public DelegateCommand StopGrabCommand { get; } public DelegateCommand ApplyExposureCommand { get; } public DelegateCommand ApplyGainCommand { get; } - public DelegateCommand ApplyWidthCommand { get; } - public DelegateCommand ApplyHeightCommand { get; } - public DelegateCommand ApplyPixelFormatCommand { get; } public DelegateCommand RefreshCameraParamsCommand { get; } public DelegateCommand OpenCameraSettingsCommand { get; } @@ -183,9 +175,6 @@ namespace XplorePlane.ViewModels StopGrabCommand = new DelegateCommand(StopGrab, () => IsCameraGrabbing); ApplyExposureCommand = new DelegateCommand(() => ApplyCameraParam(() => _camera.SetExposureTime(ExposureTime)), () => IsCameraConnected); ApplyGainCommand = new DelegateCommand(() => ApplyCameraParam(() => _camera.SetGain(GainValue)), () => IsCameraConnected); - ApplyWidthCommand = new DelegateCommand(() => ApplyCameraParam(() => _camera.SetWidth(ImageWidth)), () => IsCameraConnected); - ApplyHeightCommand = new DelegateCommand(() => ApplyCameraParam(() => _camera.SetHeight(ImageHeight)), () => IsCameraConnected); - ApplyPixelFormatCommand = new DelegateCommand(() => ApplyCameraParam(() => _camera.SetPixelFormat(SelectedPixelFormat)), () => IsCameraConnected); RefreshCameraParamsCommand = new DelegateCommand(RefreshCameraParams, () => IsCameraConnected); OpenCameraSettingsCommand = new DelegateCommand(OpenCameraSettings, () => IsCameraConnected); @@ -193,7 +182,7 @@ namespace XplorePlane.ViewModels _defaultImageSource = new BitmapImage(new Uri("pack://application:,,,/Assets/Icons/NoCamera.png")); _cameraImageSource = _defaultImageSource; - CameraStatusText = "正在检索相机..."; + CameraStatusText = "Searching camera..."; } /// @@ -203,7 +192,7 @@ namespace XplorePlane.ViewModels { if (!_camera.IsConnected) { - CameraStatusText = "未检测到相机"; + CameraStatusText = "No camera detected"; return; } @@ -212,7 +201,7 @@ namespace XplorePlane.ViewModels _camera.ConnectionLost += OnCameraConnectionLost; IsCameraConnected = true; - CameraStatusText = "已连接"; + CameraStatusText = "Connected"; RefreshCameraParams(); SyncCameraStateToAppState(); StartGrab(); @@ -231,7 +220,7 @@ namespace XplorePlane.ViewModels var info = _camera.Open(); IsCameraConnected = true; - CameraStatusText = $"已连接: {info.ModelName} (SN: {info.SerialNumber})"; + CameraStatusText = $"Connected: {info.ModelName} (SN: {info.SerialNumber})"; _logger.Information("Camera connected: {ModelName}", info.ModelName); RefreshCameraParams(); SyncCameraStateToAppState(); @@ -239,7 +228,7 @@ namespace XplorePlane.ViewModels catch (Exception ex) { _logger.Error(ex, "Failed to connect camera"); - CameraStatusText = $"连接失败: {ex.Message}"; + CameraStatusText = $"Connection failed: {ex.Message}"; IsCameraConnected = false; SyncCameraStateToAppState(); } @@ -263,7 +252,7 @@ namespace XplorePlane.ViewModels _camera.ConnectionLost -= OnCameraConnectionLost; IsCameraConnected = false; IsCameraGrabbing = false; - CameraStatusText = "未连接"; + CameraStatusText = "Disconnected"; CameraImageSource = null; SyncCameraStateToAppState(); _logger.Information("Camera disconnected"); @@ -276,7 +265,7 @@ namespace XplorePlane.ViewModels { _camera.StartGrabbing(); IsCameraGrabbing = true; - CameraStatusText = "采集中..."; + CameraStatusText = "Grabbing..."; SyncCameraStateToAppState(); // 如果已勾选实时,自动启动 Live View @@ -288,7 +277,7 @@ namespace XplorePlane.ViewModels catch (Exception ex) { _logger.Error(ex, "Failed to start grabbing"); - CameraStatusText = $"采集失败: {ex.Message}"; + CameraStatusText = $"Grab failed: {ex.Message}"; } } @@ -299,7 +288,7 @@ namespace XplorePlane.ViewModels IsLiveViewEnabled = false; _camera.StopGrabbing(); IsCameraGrabbing = false; - CameraStatusText = "已停止采集"; + CameraStatusText = "Grab stopped"; SyncCameraStateToAppState(); } catch (Exception ex) @@ -313,7 +302,7 @@ namespace XplorePlane.ViewModels if (!IsCameraGrabbing) return; _liveViewRunning = true; - CameraStatusText = "实时采集中..."; + CameraStatusText = "Live..."; try { _camera.ExecuteSoftwareTrigger(); } catch (Exception ex) { _logger.Error(ex, "Live view trigger failed"); } @@ -323,7 +312,7 @@ namespace XplorePlane.ViewModels { _liveViewRunning = false; if (IsCameraGrabbing) - CameraStatusText = "采集中..."; + CameraStatusText = "Grabbing..."; } private void RefreshCameraParams() @@ -334,13 +323,16 @@ namespace XplorePlane.ViewModels GainValue = _camera.GetGain(); ImageWidth = _camera.GetWidth(); ImageHeight = _camera.GetHeight(); - SelectedPixelFormat = _camera.GetPixelFormat(); + + var currentFormat = _camera.GetPixelFormat(); + SelectedPixelFormat = currentFormat; + _logger.Information("Camera parameters refreshed"); } catch (Exception ex) { _logger.Error(ex, "Failed to read camera parameters"); - CameraStatusText = $"读取参数失败: {ex.Message}"; + CameraStatusText = $"Read params failed: {ex.Message}"; } } @@ -354,7 +346,7 @@ namespace XplorePlane.ViewModels catch (Exception ex) { _logger.Error(ex, "Failed to apply camera parameter"); - CameraStatusText = $"设置参数失败: {ex.Message}"; + CameraStatusText = $"Set param failed: {ex.Message}"; } } @@ -385,17 +377,21 @@ namespace XplorePlane.ViewModels if (!_disposed) CameraImageSource = bitmap; }); - - if (_liveViewRunning) - { - _camera.ExecuteSoftwareTrigger(); - } } catch (Exception ex) { if (!_disposed) _logger.Error(ex, "Failed to process camera image"); } + finally + { + // 无论图像处理是否成功,都继续触发下一帧,保持采集链不断 + if (_liveViewRunning && !_disposed) + { + try { _camera.ExecuteSoftwareTrigger(); } + catch { /* 忽略触发失败 */ } + } + } } private void OnCameraGrabError(object? sender, GrabErrorEventArgs e) @@ -407,7 +403,7 @@ namespace XplorePlane.ViewModels app.Dispatcher.BeginInvoke(() => { if (!_disposed) - CameraStatusText = $"采集错误: {e.ErrorDescription}"; + CameraStatusText = $"Grab error: {e.ErrorDescription}"; }); } @@ -422,7 +418,7 @@ namespace XplorePlane.ViewModels if (_disposed) return; IsCameraConnected = false; IsCameraGrabbing = false; - CameraStatusText = "连接已断开"; + CameraStatusText = "Connection lost"; CameraImageSource = null; SyncCameraStateToAppState(); }); diff --git a/XplorePlane/Views/ImageProcessing/EdgeCircleFitPanel.xaml b/XplorePlane/Views/ImageProcessing/EdgeCircleFitPanel.xaml new file mode 100644 index 0000000..c13d41d --- /dev/null +++ b/XplorePlane/Views/ImageProcessing/EdgeCircleFitPanel.xaml @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Both + BrightToDark + DarkToBright + + + + Both + Inward + Outward + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/XplorePlane/Views/ImageProcessing/EdgeCircleFitPanel.xaml.cs b/XplorePlane/Views/ImageProcessing/EdgeCircleFitPanel.xaml.cs new file mode 100644 index 0000000..5f2bb09 --- /dev/null +++ b/XplorePlane/Views/ImageProcessing/EdgeCircleFitPanel.xaml.cs @@ -0,0 +1,52 @@ +using System.Windows; +using Prism.Ioc; +using XP.ImageProcessing.RoiControl.Controls; +using XplorePlane.Services.MainViewport; +using XplorePlane.ViewModels.ImageProcessing; + +namespace XplorePlane.Views.ImageProcessing +{ + public partial class EdgeCircleFitPanel : Window + { + public EdgeCircleFitPanel() + { + InitializeComponent(); + + var viewportService = ContainerLocator.Current?.Resolve(); + DataContext = new EdgeCircleFitViewModel(viewportService); + + Loaded += (s, e) => + { + var mainWin = Owner as MainWindow; + if (mainWin != null) + { + var canvas = FindChild(mainWin); + if (DataContext is EdgeCircleFitViewModel vm) + { + vm.SetCanvas(canvas); + vm.DrawCircleCommand.Execute(); + } + } + }; + + Closed += (s, e) => + { + if (DataContext is EdgeCircleFitViewModel vm) + vm.OnPanelClosed(); + }; + } + + private static T FindChild(DependencyObject parent) where T : DependencyObject + { + int count = System.Windows.Media.VisualTreeHelper.GetChildrenCount(parent); + for (int i = 0; i < count; i++) + { + var child = System.Windows.Media.VisualTreeHelper.GetChild(parent, i); + if (child is T t) return t; + var result = FindChild(child); + if (result != null) return result; + } + return null; + } + } +} diff --git a/XplorePlane/Views/ImageProcessing/EdgeLineFitPanel.xaml b/XplorePlane/Views/ImageProcessing/EdgeLineFitPanel.xaml new file mode 100644 index 0000000..e530710 --- /dev/null +++ b/XplorePlane/Views/ImageProcessing/EdgeLineFitPanel.xaml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Both + BrightToDark + DarkToBright + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/XplorePlane/Views/ImageProcessing/EdgeLineFitPanel.xaml.cs b/XplorePlane/Views/ImageProcessing/EdgeLineFitPanel.xaml.cs new file mode 100644 index 0000000..c2dc50b --- /dev/null +++ b/XplorePlane/Views/ImageProcessing/EdgeLineFitPanel.xaml.cs @@ -0,0 +1,53 @@ +using System.Windows; +using Prism.Ioc; +using XP.ImageProcessing.RoiControl.Controls; +using XplorePlane.Services.MainViewport; +using XplorePlane.ViewModels.ImageProcessing; + +namespace XplorePlane.Views.ImageProcessing +{ + public partial class EdgeLineFitPanel : Window + { + public EdgeLineFitPanel() + { + InitializeComponent(); + + var viewportService = ContainerLocator.Current?.Resolve(); + DataContext = new EdgeLineFitViewModel(viewportService); + + Loaded += (s, e) => + { + var mainWin = Owner as MainWindow; + if (mainWin != null) + { + var canvas = FindChild(mainWin); + if (DataContext is EdgeLineFitViewModel vm) + { + vm.SetCanvas(canvas); + // 自动进入绘制模式 + vm.DrawCaliperCommand.Execute(); + } + } + }; + + Closed += (s, e) => + { + if (DataContext is EdgeLineFitViewModel vm) + vm.OnPanelClosed(); + }; + } + + private static T FindChild(DependencyObject parent) where T : DependencyObject + { + int count = System.Windows.Media.VisualTreeHelper.GetChildrenCount(parent); + for (int i = 0; i < count; i++) + { + var child = System.Windows.Media.VisualTreeHelper.GetChild(parent, i); + if (child is T t) return t; + var result = FindChild(child); + if (result != null) return result; + } + return null; + } + } +} diff --git a/XplorePlane/Views/ImageProcessing/TemplateMatchAssistantWindow.xaml b/XplorePlane/Views/ImageProcessing/TemplateMatchAssistantWindow.xaml new file mode 100644 index 0000000..28b879a --- /dev/null +++ b/XplorePlane/Views/ImageProcessing/TemplateMatchAssistantWindow.xaml @@ -0,0 +1,345 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/XplorePlane/Views/ImageProcessing/TemplateMatchAssistantWindow.xaml.cs b/XplorePlane/Views/ImageProcessing/TemplateMatchAssistantWindow.xaml.cs new file mode 100644 index 0000000..3a84158 --- /dev/null +++ b/XplorePlane/Views/ImageProcessing/TemplateMatchAssistantWindow.xaml.cs @@ -0,0 +1,28 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using XplorePlane.ViewModels.ImageProcessing; + +namespace XplorePlane.Views.ImageProcessing; + +public partial class TemplateMatchAssistantWindow : Window +{ + public TemplateMatchAssistantWindow() + { + InitializeComponent(); + } + + private void BatchResultGrid_OnMouseDoubleClick(object sender, MouseButtonEventArgs e) + { + if (sender is DataGrid g && g.DataContext is TemplateMatchBatchViewModel vm) + vm.OpenSelectedFromDoubleClick(); + } + + protected override void OnClosed(EventArgs e) + { + if (DataContext is IDisposable d) + d.Dispose(); + base.OnClosed(e); + } +} diff --git a/XplorePlane/Views/Main/MainWindow.xaml b/XplorePlane/Views/Main/MainWindow.xaml index b372a76..5b2f49c 100644 --- a/XplorePlane/Views/Main/MainWindow.xaml +++ b/XplorePlane/Views/Main/MainWindow.xaml @@ -166,7 +166,7 @@ Size="Medium" SmallImage="/Assets/Icons/crosshair.png" Text="辅助线" /> - +<<<<<<< HEAD +======= + + + + + + + + + + + + + + + + +>>>>>>> TURBO-615-RecognitionAndPositioning diff --git a/XplorePlane/Views/Main/ViewportPanelView.xaml.cs b/XplorePlane/Views/Main/ViewportPanelView.xaml.cs index b2714a1..0fdb566 100644 --- a/XplorePlane/Views/Main/ViewportPanelView.xaml.cs +++ b/XplorePlane/Views/Main/ViewportPanelView.xaml.cs @@ -1,6 +1,7 @@ using System; using System.ComponentModel; using System.IO; +using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Media; @@ -75,6 +76,7 @@ namespace XplorePlane.Views "FillRate" => "填锡率", "BgaVoid" => "BGA空隙", "BubbleVoid" => "气泡空隙", + "EdgeLineFit" => "直线拟合", _ => "点点距" }; string valueText = args.MeasureType switch @@ -83,9 +85,16 @@ namespace XplorePlane.Views "FillRate" => $"{args.Distance:F1}%", "BgaVoid" => $"{args.Distance:F1}%", "BubbleVoid" => $"{args.Distance:F1}%", + "EdgeLineFit" => "处理中...", _ => $"{args.Distance:F2} px" }; SetStatus($"{typeLabel}: {valueText} | 共 {args.TotalCount} 条测量"); + + // 边缘查找拟合直线:获取搜索线后执行算子 + if (args.MeasureType == "EdgeLineFit") + { + ExecuteEdgeLineFitProcessor(args.P1, args.P2); + } } }; RoiCanvas.MeasureStatusChanged += (s, e) => @@ -114,6 +123,7 @@ namespace XplorePlane.Views MeasurementToolMode.ThroughHoleFillRate => XP.ImageProcessing.RoiControl.Models.MeasureMode.FillRate, MeasurementToolMode.BgaVoid => XP.ImageProcessing.RoiControl.Models.MeasureMode.BgaVoid, MeasurementToolMode.BubbleMeasure => XP.ImageProcessing.RoiControl.Models.MeasureMode.BubbleMeasure, + MeasurementToolMode.EdgeLineFit => XP.ImageProcessing.RoiControl.Models.MeasureMode.EdgeLineFit, _ => XP.ImageProcessing.RoiControl.Models.MeasureMode.None }; }, Prism.Events.ThreadOption.UIThread); @@ -134,8 +144,224 @@ namespace XplorePlane.Views var vm = GetMainVm(); if (vm != null) vm.CursorInfoText = RoiCanvas.CursorInfo; }); + + // 行灰度分布 + try + { + var ea2 = ContainerLocator.Current?.Resolve(); + ea2?.GetEvent().Subscribe(() => + { + ToggleLineProfile(); + }, Prism.Events.ThreadOption.UIThread); + + // 白底检测:进入ROI绘制模式 + ea2?.GetEvent().Subscribe(() => + { + _bgDefectDrawing = false; + _bgDefectRoiMode = BackgroundDefectRoiMode.WhiteBackground; + RegisterBackgroundDefectRoiMouseHandlers(); + }, Prism.Events.ThreadOption.UIThread); + + // 黑底检测:进入ROI绘制模式 + ea2?.GetEvent().Subscribe(() => + { + _bgDefectDrawing = false; + _bgDefectRoiMode = BackgroundDefectRoiMode.BlackBackground; + RegisterBackgroundDefectRoiMouseHandlers(); + }, Prism.Events.ThreadOption.UIThread); + + // 白底检测:渲染结果(红色标识) + ea2?.GetEvent().Subscribe(payload => + { + RenderBackgroundDefectResult(payload.RoiRect, payload.Detections, isBlackBackground: false); + }, Prism.Events.ThreadOption.UIThread); + + // 黑底检测:渲染结果(绿色标识) + ea2?.GetEvent().Subscribe(payload => + { + RenderBackgroundDefectResult(payload.RoiRect, payload.Detections, isBlackBackground: true); + }, Prism.Events.ThreadOption.UIThread); + + ea2?.GetEvent().Subscribe(() => + { + _bgDefectDrawing = false; + _bgDefectRoiMode = BackgroundDefectRoiMode.TemplateAssistant; + RegisterBackgroundDefectRoiMouseHandlers(); + SetStatus("模板助手:请在图像上拖拽框选模板区域"); + }, Prism.Events.ThreadOption.UIThread); + + ea2?.GetEvent().Subscribe(payload => + { + RenderTemplateMatchPreview(payload); + }, Prism.Events.ThreadOption.UIThread); + + ea2?.GetEvent().Subscribe(() => + { + RemoveTemplateAssistantPersistRoi(); + }, Prism.Events.ThreadOption.UIThread); + } + catch { } } + #region 行灰度分布 + + private bool _lineProfileEnabled; + private System.Windows.Shapes.Line _profileRefLine; // 透明命中区域 + private System.Windows.Shapes.Line _profileRefLineVisible; // 1px红线显示 + private System.Windows.Shapes.Polyline _profileCurve; + private double _profileLineY; + private bool _profileDragging; + + private void ToggleLineProfile() + { + _lineProfileEnabled = !_lineProfileEnabled; + var canvas = FindChildByName(RoiCanvas, "mainCanvas"); + if (canvas == null) return; + + if (_lineProfileEnabled) + { + // 参考线默认在图像中间 + _profileLineY = RoiCanvas.CanvasHeight / 2; + + // 创建参考线(红色水平线,可拖动) + // 用透明粗线作为命中区域,叠加1px红线显示 + _profileRefLine = new System.Windows.Shapes.Line + { + X1 = 0, + Y1 = _profileLineY, + X2 = RoiCanvas.CanvasWidth, + Y2 = _profileLineY, + Stroke = System.Windows.Media.Brushes.Transparent, + StrokeThickness = 7, // 上下3px命中区域 + IsHitTestVisible = true, + Cursor = System.Windows.Input.Cursors.SizeNS + }; + _profileRefLineVisible = new System.Windows.Shapes.Line + { + X1 = 0, + Y1 = _profileLineY, + X2 = RoiCanvas.CanvasWidth, + Y2 = _profileLineY, + Stroke = System.Windows.Media.Brushes.Red, + StrokeThickness = 1, + IsHitTestVisible = false + }; + _profileRefLine.MouseLeftButtonDown += ProfileLine_MouseDown; + _profileRefLine.MouseMove += ProfileLine_MouseMove; + _profileRefLine.MouseLeftButtonUp += ProfileLine_MouseUp; + canvas.Children.Add(_profileRefLineVisible); + canvas.Children.Add(_profileRefLine); + + // 创建灰度折线(固定显示在图像中间位置) + _profileCurve = new System.Windows.Shapes.Polyline + { + Stroke = System.Windows.Media.Brushes.Red, + StrokeThickness = 1, + IsHitTestVisible = false + }; + canvas.Children.Add(_profileCurve); + + UpdateLineProfile(); + SetStatus("行灰度分布:拖动红线改变采样行,再次点击按钮关闭"); + } + else + { + if (_profileRefLine != null) + { + canvas.Children.Remove(_profileRefLine); + _profileRefLine = null; + } + if (_profileRefLineVisible != null) + { + canvas.Children.Remove(_profileRefLineVisible); + _profileRefLineVisible = null; + } + if (_profileCurve != null) + { + canvas.Children.Remove(_profileCurve); + _profileCurve = null; + } + SetStatus("行灰度分布已关闭"); + } + } + + private void ProfileLine_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e) + { + _profileDragging = true; + _profileRefLine?.CaptureMouse(); + e.Handled = true; + } + + private void ProfileLine_MouseMove(object sender, System.Windows.Input.MouseEventArgs e) + { + if (!_profileDragging || _profileRefLine == null) return; + + var canvas = FindChildByName(RoiCanvas, "mainCanvas"); + if (canvas == null) return; + + var pos = e.GetPosition(canvas); + _profileLineY = Math.Clamp(pos.Y, 0, RoiCanvas.CanvasHeight - 1); + + _profileRefLine.Y1 = _profileLineY; + _profileRefLine.Y2 = _profileLineY; + _profileRefLineVisible.Y1 = _profileLineY; + _profileRefLineVisible.Y2 = _profileLineY; + + UpdateLineProfile(); + } + + private void ProfileLine_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e) + { + _profileDragging = false; + _profileRefLine?.ReleaseMouseCapture(); + e.Handled = true; + } + + private void UpdateLineProfile() + { + if (_profileCurve == null) return; + + // 从当前显示图像获取像素数据 + var viewportVm = DataContext as ViewportPanelViewModel; + var imageSource = viewportVm?.ImageSource as System.Windows.Media.Imaging.BitmapSource; + if (imageSource == null) return; + + int imgWidth = imageSource.PixelWidth; + int imgHeight = imageSource.PixelHeight; + int row = (int)Math.Clamp(_profileLineY, 0, imgHeight - 1); + + // 转为 Gray8 获取行像素 + System.Windows.Media.Imaging.BitmapSource gray8; + if (imageSource.Format != System.Windows.Media.PixelFormats.Gray8) + gray8 = new System.Windows.Media.Imaging.FormatConvertedBitmap( + imageSource, System.Windows.Media.PixelFormats.Gray8, null, 0); + else + gray8 = imageSource; + + byte[] rowPixels = new byte[imgWidth]; + int stride = imgWidth; + gray8.CopyPixels(new System.Windows.Int32Rect(0, row, imgWidth, 1), rowPixels, stride, 0); + + // 构建折线点集:折线固定显示在图像垂直中间位置 + // 参考线位置决定采样哪一行,折线位置固定在画布中间 + double canvasH = RoiCanvas.CanvasHeight; + double curveCenter = canvasH / 2.0; // 折线基线固定在图像中间 + double displayHeight = canvasH * 0.25; // 折线振幅为画布高度的25% + + var points = new System.Windows.Media.PointCollection(imgWidth); + for (int x = 0; x < imgWidth; x++) + { + double normalizedGray = rowPixels[x] / 255.0; + double y = curveCenter - normalizedGray * displayHeight; + points.Add(new System.Windows.Point(x, y)); + } + _profileCurve.Points = points; + + SetStatus($"行灰度分布 | Y={row} | 均值={rowPixels.Select(b => (double)b).Average():F1} | 最大={rowPixels.Max()} | 最小={rowPixels.Min()}"); + } + + #endregion + private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e) { if (e.OldValue is INotifyPropertyChanged oldVm) @@ -162,7 +388,31 @@ namespace XplorePlane.Views RoiCanvas.SelectedROI = null; if (DataContext is ViewportPanelViewModel vm) vm.ResetMeasurementState(); - SetStatus("已清除所有测量"); + + var canvas = FindChildByName(RoiCanvas, "mainCanvas"); + if (canvas != null) + { + if (_bgDefectPreview != null) + { + canvas.Children.Remove(_bgDefectPreview); + _bgDefectPreview = null; + } + ClearBackgroundDefectOverlays(canvas); + ClearTemplateMatchOverlays(canvas); + RemoveTemplateAssistantPersistRoi(); + } + else + { + _bgDefectOverlays.Clear(); + _tmMatchOverlays.Clear(); + RemoveTemplateAssistantPersistRoi(); + } + + _bgDefectDrawing = false; + _bgDefectRoiMode = BackgroundDefectRoiMode.None; + try { RoiCanvas.ReleaseMouseCapture(); } catch { /* 未捕获则无影响 */ } + + SetStatus("已清除所有测量、白底/黑底检测、模板匹配试跑叠加及模板助手 ROI"); } private void SaveOriginalImage_Click(object sender, RoutedEventArgs e) @@ -215,6 +465,486 @@ namespace XplorePlane.Views #endregion + #region 白底/黑底检测 + + private enum BackgroundDefectRoiMode + { + None, + WhiteBackground, + BlackBackground, + TemplateAssistant + } + + private BackgroundDefectRoiMode _bgDefectRoiMode; + private bool _bgDefectDrawing; + private System.Windows.Point _bgDefectStart; + private System.Windows.Shapes.Rectangle _bgDefectPreview; + private readonly System.Collections.Generic.List _bgDefectOverlays = new(); + private readonly System.Collections.Generic.List _tmMatchOverlays = new(); + private System.Windows.Shapes.Rectangle _templateAssistantRoiPersist; + private bool _bgDefectMouseHandlersRegistered; + + private void RegisterBackgroundDefectRoiMouseHandlers() + { + if (_bgDefectMouseHandlersRegistered) return; + RoiCanvas.PreviewMouseLeftButtonDown += OnMainCanvasPreviewMouseDown; + RoiCanvas.PreviewMouseMove += OnMainCanvasPreviewMouseMove; + RoiCanvas.PreviewMouseLeftButtonUp += OnMainCanvasPreviewMouseUp; + _bgDefectMouseHandlersRegistered = true; + } + + // 需要在 mainCanvas 的 MouseDown/Move/Up 中处理 + // 由于 PolygonRoiCanvas 内部已经处理了鼠标事件,我们通过 PreviewMouse 事件来拦截 + + private void OnMainCanvasPreviewMouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e) + { + if (_bgDefectRoiMode == BackgroundDefectRoiMode.None || e.LeftButton != System.Windows.Input.MouseButtonState.Pressed) return; + + var canvas = FindChildByName(RoiCanvas, "mainCanvas"); + if (canvas == null) return; + + _bgDefectStart = e.GetPosition(canvas); + _bgDefectDrawing = true; + + // 创建预览矩形(不清除之前的检测结果) + _bgDefectPreview = new System.Windows.Shapes.Rectangle + { + Stroke = System.Windows.Media.Brushes.Blue, + StrokeThickness = 1, + StrokeDashArray = new System.Windows.Media.DoubleCollection { 4, 2 } + }; + System.Windows.Controls.Canvas.SetLeft(_bgDefectPreview, _bgDefectStart.X); + System.Windows.Controls.Canvas.SetTop(_bgDefectPreview, _bgDefectStart.Y); + canvas.Children.Add(_bgDefectPreview); + + RoiCanvas.CaptureMouse(); + e.Handled = true; + } + + private void OnMainCanvasPreviewMouseMove(object sender, System.Windows.Input.MouseEventArgs e) + { + if (!_bgDefectDrawing || _bgDefectPreview == null) return; + + var canvas = FindChildByName(RoiCanvas, "mainCanvas"); + if (canvas == null) return; + + var current = e.GetPosition(canvas); + double x = Math.Min(_bgDefectStart.X, current.X); + double y = Math.Min(_bgDefectStart.Y, current.Y); + double w = Math.Abs(current.X - _bgDefectStart.X); + double h = Math.Abs(current.Y - _bgDefectStart.Y); + + System.Windows.Controls.Canvas.SetLeft(_bgDefectPreview, x); + System.Windows.Controls.Canvas.SetTop(_bgDefectPreview, y); + _bgDefectPreview.Width = Math.Max(1, w); + _bgDefectPreview.Height = Math.Max(1, h); + } + + private void OnMainCanvasPreviewMouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e) + { + if (!_bgDefectDrawing) return; + + _bgDefectDrawing = false; + var completedMode = _bgDefectRoiMode; + _bgDefectRoiMode = BackgroundDefectRoiMode.None; + RoiCanvas.ReleaseMouseCapture(); + + var canvas = FindChildByName(RoiCanvas, "mainCanvas"); + if (canvas == null) return; + + var end = e.GetPosition(canvas); + int x = (int)Math.Min(_bgDefectStart.X, end.X); + int y = (int)Math.Min(_bgDefectStart.Y, end.Y); + int w = (int)Math.Abs(end.X - _bgDefectStart.X); + int h = (int)Math.Abs(end.Y - _bgDefectStart.Y); + + // 移除预览矩形 + if (_bgDefectPreview != null) + { + canvas.Children.Remove(_bgDefectPreview); + _bgDefectPreview = null; + } + + if (w < 10 || h < 10) return; // 太小忽略 + + // 模板助手:在画布上保留 ROI 矩形(与试跑匹配叠加分开管理) + if (completedMode == BackgroundDefectRoiMode.TemplateAssistant) + { + RemoveTemplateAssistantPersistRoi(); + _templateAssistantRoiPersist = new System.Windows.Shapes.Rectangle + { + Stroke = System.Windows.Media.Brushes.DeepSkyBlue, + StrokeThickness = 1.5, + StrokeDashArray = new System.Windows.Media.DoubleCollection { 4, 2 }, + Fill = System.Windows.Media.Brushes.Transparent, + Width = Math.Max(1, w), + Height = Math.Max(1, h), + IsHitTestVisible = false + }; + System.Windows.Controls.Canvas.SetLeft(_templateAssistantRoiPersist, x); + System.Windows.Controls.Canvas.SetTop(_templateAssistantRoiPersist, y); + canvas.Children.Add(_templateAssistantRoiPersist); + } + + // 发布ROI绘制完成事件 + try + { + var ea = ContainerLocator.Current?.Resolve(); + var rect = new System.Windows.Int32Rect(x, y, w, h); + if (completedMode == BackgroundDefectRoiMode.WhiteBackground) + ea?.GetEvent().Publish(rect); + else if (completedMode == BackgroundDefectRoiMode.BlackBackground) + ea?.GetEvent().Publish(rect); + else if (completedMode == BackgroundDefectRoiMode.TemplateAssistant) + ea?.GetEvent().Publish(rect); + } + catch { } + + e.Handled = true; + } + + private void ClearTemplateMatchOverlays(System.Windows.Controls.Canvas canvas) + { + if (canvas != null) + { + foreach (var el in _tmMatchOverlays) + canvas.Children.Remove(el); + } + _tmMatchOverlays.Clear(); + } + + private void RemoveTemplateAssistantPersistRoi() + { + if (_templateAssistantRoiPersist == null) return; + var rect = _templateAssistantRoiPersist; + _templateAssistantRoiPersist = null; + if (VisualTreeHelper.GetParent(rect) is Panel p) + p.Children.Remove(rect); + } + + private void RenderTemplateMatchPreview(TemplateMatchPreviewPayload payload) + { + var canvas = FindChildByName(RoiCanvas, "mainCanvas"); + if (canvas == null) return; + + ClearTemplateMatchOverlays(canvas); + if (payload?.Hits == null || payload.Hits.Count == 0) + return; + + var stroke = new SolidColorBrush(Color.FromRgb(255, 140, 0)); + stroke.Freeze(); + const int crossHalf = 8; + + foreach (var h in payload.Hits) + { + var poly = new System.Windows.Shapes.Polygon + { + Stroke = stroke, + StrokeThickness = 2, + Fill = Brushes.Transparent, + IsHitTestVisible = false, + Points = new PointCollection + { + new System.Windows.Point(h.LtX, h.LtY), + new System.Windows.Point(h.RtX, h.RtY), + new System.Windows.Point(h.RbX, h.RbY), + new System.Windows.Point(h.LbX, h.LbY) + } + }; + canvas.Children.Add(poly); + _tmMatchOverlays.Add(poly); + + var cx = h.CenterX; + var cy = h.CenterY; + var hLine = new System.Windows.Shapes.Line + { + X1 = cx - crossHalf, + Y1 = cy, + X2 = cx + crossHalf, + Y2 = cy, + Stroke = stroke, + StrokeThickness = 1.5, + IsHitTestVisible = false + }; + var vLine = new System.Windows.Shapes.Line + { + X1 = cx, + Y1 = cy - crossHalf, + X2 = cx, + Y2 = cy + crossHalf, + Stroke = stroke, + StrokeThickness = 1.5, + IsHitTestVisible = false + }; + canvas.Children.Add(hLine); + canvas.Children.Add(vLine); + _tmMatchOverlays.Add(hLine); + _tmMatchOverlays.Add(vLine); + + var tb = new System.Windows.Controls.TextBlock + { + Text = $"{h.Score:F2}", + Foreground = stroke, + FontSize = 10, + IsHitTestVisible = false + }; + System.Windows.Controls.Canvas.SetLeft(tb, cx + crossHalf + 2); + System.Windows.Controls.Canvas.SetTop(tb, cy - 8); + canvas.Children.Add(tb); + _tmMatchOverlays.Add(tb); + } + } + + private void RenderBackgroundDefectResult( + System.Drawing.Rectangle roiRect, + System.Collections.Generic.IReadOnlyList detections, + bool isBlackBackground) + { + var canvas = FindChildByName(RoiCanvas, "mainCanvas"); + if (canvas == null || detections == null) return; + + // 绘制ROI矩形(蓝色实线,两种模式一致) + var roiShape = new System.Windows.Shapes.Rectangle + { + Stroke = System.Windows.Media.Brushes.Blue, + StrokeThickness = 1, + Width = roiRect.Width, + Height = roiRect.Height, + IsHitTestVisible = false + }; + System.Windows.Controls.Canvas.SetLeft(roiShape, roiRect.X); + System.Windows.Controls.Canvas.SetTop(roiShape, roiRect.Y); + canvas.Children.Add(roiShape); + _bgDefectOverlays.Add(roiShape); + + var defectBrush = isBlackBackground + ? System.Windows.Media.Brushes.LimeGreen + : System.Windows.Media.Brushes.Red; + + const int labelPadRightOfRoi = 4; + const double labelLineHeight = 15; + int validCount = detections.Count(d => d.Contour != null && d.Contour.Count >= 2); + double roiMidY = roiRect.Y + roiRect.Height * 0.5; + double labelLeft = roiRect.X + roiRect.Width + labelPadRightOfRoi; + double labelStartY = roiMidY - validCount * labelLineHeight * 0.5; + int labelRow = 0; + + foreach (var d in detections) + { + if (d.Contour == null || d.Contour.Count < 2) continue; + + var fig = new PathFigure + { + StartPoint = new System.Windows.Point(d.Contour[0].X, d.Contour[0].Y), + IsClosed = true + }; + if (d.Contour.Count > 1) + { + fig.Segments.Add(new PolyLineSegment( + d.Contour.Skip(1).Select(p => new System.Windows.Point(p.X, p.Y)), true)); + } + + var geom = new PathGeometry(); + geom.Figures.Add(fig); + var contourPath = new System.Windows.Shapes.Path + { + Data = geom, + Stroke = defectBrush, + StrokeThickness = 1, + Fill = System.Windows.Media.Brushes.Transparent, + IsHitTestVisible = false + }; + canvas.Children.Add(contourPath); + _bgDefectOverlays.Add(contourPath); + + var chordLine = new System.Windows.Shapes.Line + { + X1 = d.ChordP1.X, + Y1 = d.ChordP1.Y, + X2 = d.ChordP2.X, + Y2 = d.ChordP2.Y, + Stroke = defectBrush, + StrokeThickness = 1.5, + IsHitTestVisible = false + }; + canvas.Children.Add(chordLine); + _bgDefectOverlays.Add(chordLine); + + double um = d.SizeMicrometers; + string label = um >= 1000 ? $"{um / 1000:F2} mm" : $"{um:F0} μm"; + + var text = new System.Windows.Controls.TextBlock + { + Text = label, + Foreground = defectBrush, + FontSize = 11, + IsHitTestVisible = false + }; + System.Windows.Controls.Canvas.SetLeft(text, labelLeft); + System.Windows.Controls.Canvas.SetTop(text, labelStartY + labelRow * labelLineHeight); + canvas.Children.Add(text); + _bgDefectOverlays.Add(text); + labelRow++; + } + } + + private void ClearBackgroundDefectOverlays(System.Windows.Controls.Canvas canvas) + { + foreach (var el in _bgDefectOverlays) + canvas.Children.Remove(el); + _bgDefectOverlays.Clear(); + } + + #endregion + + #region 边缘查找拟合直线 + + private void ExecuteEdgeLineFitProcessor(Point startPoint, Point endPoint) + { + try + { + var vm = GetMainVm(); + if (vm == null) return; + + // 获取当前图像 + var viewportVm = ContainerLocator.Current?.Resolve(); + var imageSource = viewportVm?.ImageSource as BitmapSource; + if (imageSource == null) + { + SetStatus("直线拟合失败:无可用图像"); + return; + } + + // 转换为 Emgu.CV Image + BitmapSource source = imageSource; + if (imageSource.Format != System.Windows.Media.PixelFormats.Gray8) + source = new FormatConvertedBitmap(imageSource, System.Windows.Media.PixelFormats.Gray8, null, 0); + + int width = source.PixelWidth; + int height = source.PixelHeight; + int stride = width; + byte[] pixels = new byte[height * stride]; + source.CopyPixels(pixels, stride, 0); + + using var inputImage = new Emgu.CV.Image(width, height); + for (int y = 0; y < height; y++) + for (int x = 0; x < width; x++) + inputImage.Data[y, x, 0] = pixels[y * stride + x]; + + // 创建并配置算子 + var processor = new XP.ImageProcessing.Processors.EdgeLineFitProcessor(); + processor.SetParameter("StartX", (int)startPoint.X); + processor.SetParameter("StartY", (int)startPoint.Y); + processor.SetParameter("EndX", (int)endPoint.X); + processor.SetParameter("EndY", (int)endPoint.Y); + + // 执行处理 + var result = processor.Process(inputImage); + + // 获取输出数据并在画布上绘制结果 + var outputData = processor.OutputData; + if (outputData.ContainsKey("LineFitResult")) + { + var fitResult = outputData["LineFitResult"] as XP.ImageProcessing.Processors.LineFitResult; + if (fitResult != null && fitResult.Success) + { + DrawEdgeLineFitResult(fitResult, outputData); + SetStatus($"直线拟合完成: 角度={fitResult.AngleDegrees:F2}°, 内点={fitResult.Inliers.Count}/{fitResult.EdgePointCount}, 误差={fitResult.FitError:F3}px"); + } + else + { + SetStatus("直线拟合失败:未找到足够的边缘点"); + } + } + + result.Dispose(); + } + catch (Exception ex) + { + SetStatus($"直线拟合异常: {ex.Message}"); + } + } + + private readonly System.Collections.Generic.List _elfOverlays = new(); + + private void DrawEdgeLineFitResult( + XP.ImageProcessing.Processors.LineFitResult fitResult, + System.Collections.Generic.Dictionary outputData) + { + var canvas = FindChildByName(RoiCanvas, "mainCanvas"); + if (canvas == null) return; + + // 清除之前的拟合结果 + foreach (var el in _elfOverlays) + canvas.Children.Remove(el); + _elfOverlays.Clear(); + + // 绘制拟合直线(绿色) + var fitLine = new System.Windows.Shapes.Line + { + X1 = fitResult.Endpoint1.X, + Y1 = fitResult.Endpoint1.Y, + X2 = fitResult.Endpoint2.X, + Y2 = fitResult.Endpoint2.Y, + Stroke = System.Windows.Media.Brushes.Lime, + StrokeThickness = 2, + IsHitTestVisible = false + }; + canvas.Children.Add(fitLine); + _elfOverlays.Add(fitLine); + + // 绘制内点(绿色小圆点) + foreach (var pt in fitResult.Inliers) + { + var dot = new System.Windows.Shapes.Ellipse + { + Width = 6, + Height = 6, + Fill = System.Windows.Media.Brushes.Lime, + IsHitTestVisible = false + }; + System.Windows.Controls.Canvas.SetLeft(dot, pt.X - 3); + System.Windows.Controls.Canvas.SetTop(dot, pt.Y - 3); + canvas.Children.Add(dot); + _elfOverlays.Add(dot); + } + + // 绘制外点(红色小圆点) + foreach (var pt in fitResult.Outliers) + { + var dot = new System.Windows.Shapes.Ellipse + { + Width = 6, + Height = 6, + Fill = System.Windows.Media.Brushes.Red, + IsHitTestVisible = false + }; + System.Windows.Controls.Canvas.SetLeft(dot, pt.X - 3); + System.Windows.Controls.Canvas.SetTop(dot, pt.Y - 3); + canvas.Children.Add(dot); + _elfOverlays.Add(dot); + } + + // 绘制角度标注 + var labelText = $"∠{fitResult.AngleDegrees:F2}° | Err:{fitResult.FitError:F2}px"; + var label = new System.Windows.Controls.TextBlock + { + Text = labelText, + Foreground = System.Windows.Media.Brushes.Yellow, + FontSize = 12, + FontWeight = FontWeights.Bold, + IsHitTestVisible = false + }; + double labelX = (fitResult.Endpoint1.X + fitResult.Endpoint2.X) / 2 + 5; + double labelY = (fitResult.Endpoint1.Y + fitResult.Endpoint2.Y) / 2 - 20; + System.Windows.Controls.Canvas.SetLeft(label, labelX); + System.Windows.Controls.Canvas.SetTop(label, labelY); + canvas.Children.Add(label); + _elfOverlays.Add(label); + } + + #endregion + private static T FindChildByName(DependencyObject parent, string name) where T : FrameworkElement { int count = VisualTreeHelper.GetChildrenCount(parent); diff --git a/XplorePlane/Views/Setting/CameraSettingsWindow.xaml b/XplorePlane/Views/Setting/CameraSettingsWindow.xaml index a0e9ddc..b4e0414 100644 --- a/XplorePlane/Views/Setting/CameraSettingsWindow.xaml +++ b/XplorePlane/Views/Setting/CameraSettingsWindow.xaml @@ -59,21 +59,11 @@ - - - - + - - - - + @@ -81,9 +71,9 @@ - + diff --git a/XplorePlane/Views/Setting/CameraSettingsWindow.xaml.cs b/XplorePlane/Views/Setting/CameraSettingsWindow.xaml.cs index 338c524..b2f6537 100644 --- a/XplorePlane/Views/Setting/CameraSettingsWindow.xaml.cs +++ b/XplorePlane/Views/Setting/CameraSettingsWindow.xaml.cs @@ -18,9 +18,6 @@ namespace XplorePlane.Views var type = dc.GetType(); ExecuteCommand(type, dc, "ApplyExposureCommand"); ExecuteCommand(type, dc, "ApplyGainCommand"); - ExecuteCommand(type, dc, "ApplyWidthCommand"); - ExecuteCommand(type, dc, "ApplyHeightCommand"); - ExecuteCommand(type, dc, "ApplyPixelFormatCommand"); } private static void ExecuteCommand(System.Type type, object dc, string cmdName) diff --git a/XplorePlane/XplorePlane.csproj b/XplorePlane/XplorePlane.csproj index 198686c..1c3d7a5 100644 --- a/XplorePlane/XplorePlane.csproj +++ b/XplorePlane/XplorePlane.csproj @@ -55,6 +55,12 @@ Libs\Native\BR.AN.PviServices.dll True + + + + ..\ExternalLibraries\MvCameraControl.Net.dll + true +