Fix EmguCV针对图像宽不是4的倍数的改变bug修复

This commit is contained in:
TianSong
2026-04-21 15:30:07 +08:00
parent 33fef1b6eb
commit 8189e76492
7 changed files with 132 additions and 120 deletions
+110 -110
View File
@@ -1,110 +1,110 @@
## XplorePlane 平面CT软件 ## XplorePlane 平面CT软件
### 系统目标 ### 系统目标
XplorePlane 系统用于控制平面 CT 设备的各个子系统(射线源、探测器、运动控制、相机)并完成采集图像的处理与分析,为研发与调试提供统一的软件平台。 XplorePlane 系统用于控制平面 CT 设备的各个子系统(射线源、探测器、运动控制、相机)并完成采集图像的处理与分析,为研发与调试提供统一的软件平台。
### 总体架构 ### 总体架构
- 客户端框架: WPF + Prism MVVM(目标框架 net8.0-windows - 客户端框架: WPF + Prism MVVM(目标框架 net8.0-windows
- 图像处理内核: ImageProcessing.Core(算子基类)+ ImageProcessing.Processors(具体算子),基于 EmguCV - 图像处理内核: ImageProcessing.Core(算子基类)+ ImageProcessing.Processors(具体算子),基于 EmguCV
- 相机控制: XP.CameraBasler pylon SDK 封装,支持软件触发、参数读写) - 相机控制: XP.CameraBasler pylon SDK 封装,支持软件触发、参数读写)
- 硬件抽象: XP.Common + XP.Hardware.RaySource(射线源控制) - 硬件抽象: XP.Common + XP.Hardware.RaySource(射线源控制)
- 日志: Serilog - 日志: Serilog
- UI 组件: Telerik RadRibbonView、Fluent.Ribbon - UI 组件: Telerik RadRibbonView、Fluent.Ribbon
### 解决方案结构 ### 解决方案结构
``` ```
XplorePlane.sln XplorePlane.sln
├── XplorePlane/ # 主应用程序(WPF) ├── XplorePlane/ # 主应用程序(WPF)
├── XP.Camera/ # 相机控制库(Basler ├── XP.Camera/ # 相机控制库(Basler
├── ImageProcessing/ # 独立图像处理应用 ├── ImageProcessing/ # 独立图像处理应用
├── ImageProcessing.Core/ # 图像处理算子基类 ├── ImageProcessing.Core/ # 图像处理算子基类
├── ImageProcessing.Processors/ # 具体算子实现 ├── ImageProcessing.Processors/ # 具体算子实现
├── ImageProcessing.Controls/ # 图像显示控件(ImageCanvasControl ├── ImageProcessing.Controls/ # 图像显示控件(ImageCanvasControl
├── ImageROIControl/ # ROI 绘制控件 ├── ImageROIControl/ # ROI 绘制控件
├── XplorePlane.Tests/ # 单元测试 ├── XplorePlane.Tests/ # 单元测试
└── ExternalLibraries/ # 外部 DLL 和 ONNX 模型 └── ExternalLibraries/ # 外部 DLL 和 ONNX 模型
``` ```
### XplorePlane 主项目结构 ### XplorePlane 主项目结构
``` ```
XplorePlane/ XplorePlane/
├── App.xaml / App.xaml.cs # 应用入口 + DI 容器配置(AppBootstrapper ├── App.xaml / App.xaml.cs # 应用入口 + DI 容器配置(AppBootstrapper
├── Views/ ├── Views/
│ ├── Main/ │ ├── Main/
│ │ ├── MainWindow.xaml # 主窗口(Telerik Ribbon + 三栏布局) │ │ ├── MainWindow.xaml # 主窗口(Telerik Ribbon + 三栏布局)
│ │ ├── NavigationPropertyPanelView.xaml # 相机实时预览面板 │ │ ├── NavigationPropertyPanelView.xaml # 相机实时预览面板
│ │ └── MotionControlPanelView.xaml # 运动控制面板 │ │ └── MotionControlPanelView.xaml # 运动控制面板
│ ├── Cnc/ # CNC 编辑器 / 矩阵编排视图 │ ├── Cnc/ # CNC 编辑器 / 矩阵编排视图
│ ├── ImageProcessing/ # 图像处理面板视图 │ ├── ImageProcessing/ # 图像处理面板视图
│ └── CameraSettingsWindow.xaml # 相机参数设置对话框 │ └── CameraSettingsWindow.xaml # 相机参数设置对话框
├── ViewModels/ ├── ViewModels/
│ ├── Main/ │ ├── Main/
│ │ ├── MainViewModel.cs # 主窗口 ViewModel │ │ ├── MainViewModel.cs # 主窗口 ViewModel
│ │ └── NavigationPropertyPanelViewModel.cs # 相机预览 ViewModel │ │ └── NavigationPropertyPanelViewModel.cs # 相机预览 ViewModel
│ ├── Cnc/ # CNC / 矩阵 ViewModel │ ├── Cnc/ # CNC / 矩阵 ViewModel
│ └── ImageProcessing/ # 图像处理 / 流水线 ViewModel │ └── ImageProcessing/ # 图像处理 / 流水线 ViewModel
├── Services/ ├── Services/
│ ├── AppState/ # 全局状态管理(线程安全) │ ├── AppState/ # 全局状态管理(线程安全)
│ ├── Camera/ # 相机服务 │ ├── Camera/ # 相机服务
│ ├── Cnc/ # CNC 程序服务 │ ├── Cnc/ # CNC 程序服务
│ ├── Matrix/ # 矩阵编排服务 │ ├── Matrix/ # 矩阵编排服务
│ ├── Measurement/ # 测量数据服务 │ ├── Measurement/ # 测量数据服务
│ ├── Pipeline/ # 流水线执行 / 持久化 │ ├── Pipeline/ # 流水线执行 / 持久化
│ └── Recipe/ # 检测配方服务 │ └── Recipe/ # 检测配方服务
├── Models/ # 数据模型(State、CNC、Matrix、Pipeline 等) ├── Models/ # 数据模型(State、CNC、Matrix、Pipeline 等)
├── Events/ # Prism 事件 ├── Events/ # Prism 事件
├── Libs/ ├── Libs/
│ ├── Hardware/ # 硬件库 DLLXP.Common、XP.Hardware.RaySource │ ├── Hardware/ # 硬件库 DLLXP.Common、XP.Hardware.RaySource
│ └── Native/ # 原生依赖库 │ └── Native/ # 原生依赖库
└── Assets/Icons/ # 工具栏图标 └── Assets/Icons/ # 工具栏图标
``` ```
### 相机集成 ### 相机集成
相机实时影像集成在主窗口左下角的 NavigationPropertyPanelView 中: 相机实时影像集成在主窗口左下角的 NavigationPropertyPanelView 中:
- 连接/断开相机(Basler,通过 ICameraController - 连接/断开相机(Basler,通过 ICameraController
- 开始/停止采集(软件触发模式) - 开始/停止采集(软件触发模式)
- 实时预览(Live View,勾选"实时"复选框) - 实时预览(Live View,勾选"实时"复选框)
- 鼠标悬停显示像素坐标 - 鼠标悬停显示像素坐标
- 相机参数设置对话框(曝光时间、增益、分辨率、像素格式) - 相机参数设置对话框(曝光时间、增益、分辨率、像素格式)
- 主界面 Ribbon 硬件栏提供"相机设置"快捷按钮 - 主界面 Ribbon 硬件栏提供"相机设置"快捷按钮
相机控制逻辑移植自 ImageProcessing 项目,使用 XP.Camera.PixelConverter 进行像素数据转换,通过 Application.Dispatcher.Invoke 保证 UI 线程安全。 相机控制逻辑移植自 ImageProcessing 项目,使用 XP.Camera.PixelConverter 进行像素数据转换,通过 Application.Dispatcher.Invoke 保证 UI 线程安全。
### 依赖注入(DI ### 依赖注入(DI
使用 Prism + DryIoc,在 AppBootstrapper.RegisterTypes() 中统一注册: 使用 Prism + DryIoc,在 AppBootstrapper.RegisterTypes() 中统一注册:
- ICameraFactory / ICameraController / ICameraService(单例) - ICameraFactory / ICameraController / ICameraService(单例)
- IRaySourceService / IRaySourceFactory(单例) - IRaySourceService / IRaySourceFactory(单例)
- IAppStateService(单例,线程安全状态管理) - IAppStateService(单例,线程安全状态管理)
- NavigationPropertyPanelViewModel(单例,相机预览共享实例) - NavigationPropertyPanelViewModel(单例,相机预览共享实例)
- 各 Service 和 ViewModel(按需注册) - 各 Service 和 ViewModel(按需注册)
### 构建 ### 构建
```bash ```bash
# Debug # Debug
dotnet build XplorePlane.sln -c Debug dotnet build XplorePlane.sln -c Debug
# Release # Release
dotnet build XplorePlane.sln -c Release dotnet build XplorePlane.sln -c Release
``` ```
### TO-DO List ### TO-DO List
- [x] 软件基于 WPF + Prism 基础的框架 - [x] 软件基于 WPF + Prism 基础的框架
- [x] 日志库的引用(通过 XP.Common.dll - [x] 日志库的引用(通过 XP.Common.dll
- [x] 按推荐的 DLL 目录结构进行修改 - [x] 按推荐的 DLL 目录结构进行修改
- [x] 通过库依赖的方式调用日志功能 - [x] 通过库依赖的方式调用日志功能
- [x] 界面的布局 - [x] 界面的布局
- [x] 相机实时影像集成(连接、采集、Live View、像素坐标显示) - [x] 相机实时影像集成(连接、采集、Live View、像素坐标显示)
- [x] 相机参数设置对话框(曝光、增益、分辨率、像素格式) - [x] 相机参数设置对话框(曝光、增益、分辨率、像素格式)
- [x] 主界面硬件栏相机设置按钮 - [x] 主界面硬件栏相机设置按钮
- [ ] 打通与硬件层的调用流程 - [ ] 打通与硬件层的调用流程
- [ ] 打通与图像层的调用流程 - [ ] 打通与图像层的调用流程
+1 -1
View File
@@ -1,4 +1,4 @@
using System.Configuration; using System.Configuration;
using XP.Common.Configs; using XP.Common.Configs;
using XP.Common.Dump.Configs; using XP.Common.Dump.Configs;
@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
+1 -1
View File
@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
+1 -1
View File
@@ -1,4 +1,4 @@
using Prism.Mvvm; using Prism.Mvvm;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -1,4 +1,4 @@
using Prism.Ioc; using Prism.Ioc;
using Prism.Modularity; using Prism.Modularity;
using System.Resources; using System.Resources;
using XP.Common.Localization; using XP.Common.Localization;
@@ -28,11 +28,11 @@ namespace XplorePlane.Services
var formatted = new FormatConvertedBitmap(bitmapSource, PixelFormats.Gray8, null, 0); var formatted = new FormatConvertedBitmap(bitmapSource, PixelFormats.Gray8, null, 0);
int width = formatted.PixelWidth; int width = formatted.PixelWidth;
int height = formatted.PixelHeight; int height = formatted.PixelHeight;
int stride = width;
byte[] pixels = new byte[height * stride];
formatted.CopyPixels(pixels, stride, 0);
var image = new Image<Gray, byte>(width, height); var image = new Image<Gray, byte>(width, height);
int stride = image.Bytes.Length / height;
var pixels = new byte[height * stride];
formatted.CopyPixels(pixels, stride, 0);
image.Bytes = pixels; image.Bytes = pixels;
return image; return image;
} }
@@ -40,7 +40,19 @@ namespace XplorePlane.Services
public static Image<Gray, byte> ToEmguCVFromPixels(byte[] pixels, int width, int height) public static Image<Gray, byte> ToEmguCVFromPixels(byte[] pixels, int width, int height)
{ {
var image = new Image<Gray, byte>(width, height); var image = new Image<Gray, byte>(width, height);
image.Bytes = pixels; int required = image.Bytes.Length;
if (pixels.Length == required)
{
image.Bytes = pixels;
}
else
{
int stride = required / height;
var padded = new byte[required];
for (int row = 0; row < height; row++)
Buffer.BlockCopy(pixels, row * width, padded, row * stride, width);
image.Bytes = padded;
}
return image; return image;
} }
@@ -50,8 +62,8 @@ namespace XplorePlane.Services
int width = emguImage.Width; int width = emguImage.Width;
int height = emguImage.Height; int height = emguImage.Height;
int stride = width;
byte[] pixels = emguImage.Bytes; byte[] pixels = emguImage.Bytes;
int stride = pixels.Length / height;
return BitmapSource.Create(width, height, 96, 96, PixelFormats.Gray8, null, pixels, stride); return BitmapSource.Create(width, height, 96, 96, PixelFormats.Gray8, null, pixels, stride);
} }