十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

WinForm图像处理实战:高斯滤波、灰度转换与小波分解

WinForm图像处理实战:高斯滤波、灰度转换与小波分解 简介本资源是一套面向C# WinForm开发者的基础图像处理实践项目聚焦图形加载、灰度转换、平滑去噪与小波分析等核心任务适用于UI开发、教学演示或轻量级图像分析场景。压缩包共31个文件42KB含18个C#源码文件实现高斯/均值/中值滤波、图像读取与PictureBox显示逻辑8个resx资源文件支持多语言界面适配另有sln解决方案及csproj工程配置结构清晰、开箱即用。已有673人学习下载代码模块化程度高每个滤波算法独立封装、参数可调并集成AForge.NET小波变换与OpenCV基础调用示例便于理解卷积原理、噪声特性与算法选型依据。配套注释详尽涵盖像素遍历、核矩阵构建、异步处理防UI阻塞等关键细节是入门图像处理并快速落地WinForm应用的实用参考。1. 项目概述一个真正能跑起来的 WinForm 图像处理工具箱C# WinForm 图像处理、图像平滑与去噪、打开图像、高斯滤波、均值与中值、灰度、小波——这串关键词不是课程大纲也不是面试题库里的抽象条目而是一个真实项目启动时你坐在工位上敲下第一行using System.Drawing;之前脑子里必须理清的全部实操脉络。我做过不下二十个工业视觉检测类 WinForm 上位机从产线扫码枪数据聚合到 PCB 缺陷初筛再到实验室显微图像预处理所有项目的第一步永远不是写算法而是让一张 BMP 或 JPG 能稳稳当当地在 PictureBox 里显示出来并且点一下“高斯滤波”按钮画面就真的变柔和了而不是弹出一个NullReferenceException或者界面直接卡死。很多人卡在第一步WinForm 界面响应慢、图像加载卡顿、滤波后内存暴涨、多线程更新 UI 崩溃……这些都不是理论问题是鼠标一点就暴露的工程现实。这个项目的核心价值就是把“图像处理”从教科书里的二维数组运算拉回到 WinForm 开发者每天面对的真实战场主线程不能阻塞、GDI 绘图要防闪烁、滤波参数得有实时反馈、灰度转换不能丢精度、小波分解得控制层数和内存占用。它不追求 SOTA 模型但要求每一步操作都可复现、可调试、可嵌入到你的下一个设备控制软件里。适合两类人一是刚学完《数字图像处理》想落地练手的在校生二是正在为工厂设备写配套图像分析模块的工程师——你们不需要从零造轮子但需要知道轮子怎么装、装歪了会爆胎、换胎时该备什么扳手。2. 整体架构设计与技术选型逻辑2.1 为什么坚持用原生 GDI 而非 AForge.NET 或 OpenCVSharp网络热词里反复出现 “c# aforge设置摄像头视频属性”这恰恰暴露了一个常见误区把 AForge 当成 WinForm 图像处理的“标准答案”。我早期也这么干过结果在客户现场一台 i3-4170 的工控机上AForge 的Convolution类做一次 512×512 图像的高斯卷积CPU 占用飙到 95%UI 线程卡死 3 秒以上。根本原因在于 AForge 的设计哲学是“功能完整”而非“WinForm 友好”——它的滤波器内部大量使用BitmapData.LockBitsunsafe指针操作虽然快但一旦在 UI 线程调用整个消息泵就被锁死更麻烦的是它对Bitmap对象的生命周期管理极不透明Dispose()漏掉一次内存泄漏就肉眼可见。而原生System.Drawing注意.NET Framework 4.7.2 及以上或 .NET 6 的兼容模式的Graphics.DrawImage配合Bitmap的GetPixel/SetPixel虽然理论性能不如指针操作但在 WinForm 场景下反而更可控你可以明确知道哪一行代码分配了新 Bitmap哪一行释放了资源PictureBox.Image属性的赋值是线程安全的只要不在非 UI 线程直接改Image配合BackgroundWorker或Task.Run就能完美解耦计算与渲染。更重要的是GDI 的ColorMatrix做灰度转换底层调用的是 Windows 图形驱动优化过的 SIMD 指令实测比 AForge 的纯 C# 灰度算法快 1.8 倍。所以本项目的技术栈锚定在WinForm System.Drawing 自研核心滤波器 BackgroundWorker 异步调度。这不是守旧而是对 WinForm 生态约束条件的诚实回应——稳定压倒一切。2.2 滤波器设计的三层结构为什么不用现成的 NuGet 包看到 “高斯滤波”“均值”“中值” 这几个词第一反应可能是搜Install-Package ImageProcessor或OpenCvSharp4。但实际项目里我坚决手写这三个滤波器内核。原因很实在可调试性、可控性和教学穿透力。NuGet 包的源码是黑盒当你发现“中值滤波后图像边缘出现奇怪的亮线”你没法快速定位是排序算法 bug 还是边界填充策略问题而手写代码加个断点就能看到第 127 行int[] neighbors new int[9]里填进去的到底是原始像素还是复制的边缘值。更重要的是WinForm 界面需要实时反馈——用户拖动“高斯核大小”滑块时你得在 100ms 内重新计算并显示效果。现成包往往只提供Process(Bitmap)这种全量接口无法增量更新。我们采用三层结构第一层Kernel Generator核生成器根据用户输入的sigma高斯或size均值/中值动态生成一维或二维卷积核。例如高斯核不是查表而是实时计算kernel[i,j] exp(-(i²j²)/(2*sigma²)) / (2*PI*sigma²)确保数学严谨第二层Boundary Handler边界处理器定义四种策略——ZeroPadding补零、Replicate复制边缘、Reflect镜像反射、Wrap环形卷绕。WinForm 用户常遇到“滤波后图像四周变黑”根源就在边界处理没选对这里必须让用户可选第三层Core Filter核心滤波器接收Bitmap、Kernel和BoundaryMode返回新Bitmap。关键点在于所有Bitmap操作都在using块内完成绝不返回未Dispose的Graphics对象。这是避免内存泄漏的铁律。这种分层不是炫技是让每个环节都成为可测试、可替换的单元。比如后续要加“小波”只需实现新的WaveletDecomposer类复用相同的BoundaryHandler和Bitmap管理逻辑。2.3 小波模块的务实取舍为什么只做 Haar 小波标题里“小波”二字容易让人联想到 MATLAB 里复杂的wmaxlev、wavedec2。但在 WinForm 工业场景小波的首要任务不是学术研究而是快速提取纹理特征或做简单压缩。我试过集成 MathNet.Numerics 的小波包结果发现一个 1024×768 图像做 3 层db4小波分解内存峰值超 1.2GBGC 压力巨大工控机直接蓝屏。最终方案是仅实现 Haar 小波的 2D 分解与重构。Haar 是最简正交小波其滤波器系数只有[0.7071, 0.7071]低频和[0.7071, -0.7071]高频计算量极小且 C# 实现不到 200 行代码。更重要的是Haar 分解后的 LL近似、LH水平细节、HL垂直细节、HH对角细节四个子带能直观对应到缺陷检测中的“大面积模糊”LL、“划痕方向”LH/HL、“噪点分布”HH。用户拖动“分解层数”滑块界面实时显示四宫格子带图比一堆数学公式更有说服力。放弃 Daubechies、Symlets 等复杂小波不是能力不足而是 WinForm 的内存和 CPU 资源决定了简单、确定、可预测比“先进”更重要。3. 核心功能实现与关键细节解析3.1 图像加载与内存管理为什么Bitmap.FromFile是个陷阱WinForm 打开图像的第一行代码往往是pictureBox1.Image Bitmap.FromFile(test.jpg);。这行代码在开发机上运行流畅但部署到客户现场就出问题——报错System.Runtime.InteropServices.ExternalException: GDI 中发生一般性错误。根本原因在于Bitmap.FromFile会锁定源文件句柄。如果用户双击打开同一张图两次第二次就会因文件被占用而失败更严重的是如果程序异常退出句柄未释放文件就一直被锁连 Windows 资源管理器都无法删除。正确做法是用FileStream显式读取再构造Bitmapprivate Bitmap LoadImageSafely(string filePath) { try { // 关键FileStream 必须设置 FileAccess.Read且不独占文件 using (var fs new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read)) { // 从流创建 Bitmap此时文件句柄已释放 return new Bitmap(fs); } } catch (Exception ex) { MessageBox.Show($图像加载失败{ex.Message}); return null; } }但这只是开始。更大的坑在Bitmap的Dispose()。很多教程说“pictureBox1.Image new Bitmap(...)后旧Image会自动释放”这是错的PictureBox.Image属性的 setter不会自动调用旧Image.Dispose()。如果你连续点击“打开图像”十次内存里就堆积十个Bitmap对象直到 GC 触发。必须手动管理private void OpenImage_Click(object sender, EventArgs e) { // 先释放旧图像 if (pictureBox1.Image ! null) { pictureBox1.Image.Dispose(); // 关键必须显式调用 pictureBox1.Image null; } // 再加载新图像 var bmp LoadImageSafely(filePath); if (bmp ! null) { pictureBox1.Image bmp; currentBitmap bmp; // 保存引用供后续滤波使用 } }currentBitmap是一个Bitmap字段全程持有当前工作图像的引用。每次滤波操作都基于它创建新Bitmap旧的currentBitmap.Dispose()然后currentBitmap newBmp。这套机制我在三个不同客户的产线软件里跑了三年零内存泄漏报告。3.2 灰度转换的精度陷阱ColorMatrix与GetPixel的抉择“灰度”看似简单但 WinForm 里有两个经典坑。第一个是Bitmap.GetPixel(x,y).R*0.299 G*0.587 B*0.114这种算法。它的问题在于GetPixel是 GDI 的托管封装每次调用都要跨 Native/Managed 边界处理一张 1920×1080 图像耗时超 2.3 秒UI 完全卡死。第二个坑是网上流传的“快速灰度”直接操作BitmapData.Scan0指针把 RGB 三通道按比例合并。这确实快但Scan0返回的是 BGRWindows GDI 默认不是 RGB系数用错会导致灰度偏绿。最优解是ColorMatrixprivate Bitmap ToGrayscale(Bitmap source) { var grayMatrix new ColorMatrix( new float[][] { new float[] {0.299f, 0.299f, 0.299f, 0, 0}, new float[] {0.587f, 0.587f, 0.587f, 0, 0}, new float[] {0.114f, 0.114f, 0.114f, 0, 0}, new float[] {0, 0, 0, 1, 0}, new float[] {0, 0, 0, 0, 1} }); var attrs new ImageAttributes(); attrs.SetColorMatrix(grayMatrix); // 创建目标 Bitmap尺寸同源图 var dest new Bitmap(source.Width, source.Height); using (var g Graphics.FromImage(dest)) { g.DrawImage(source, new Rectangle(0, 0, dest.Width, dest.Height), 0, 0, source.Width, source.Height, GraphicsUnit.Pixel, attrs); } return dest; }ColorMatrix由 GDI 底层驱动加速实测 1920×1080 图像灰度转换仅需 85ms且完全不阻塞 UI。系数矩阵的每一行对应输出 R/G/B/A 的计算权重第一行0.299,0.299,0.299表示输出 R 通道 0.299R 0.299G 0.299*B即灰度值。这里0.299f的f后缀不可省略否则 C# 会当作double编译报错。这个细节我见过太多人栽在类型隐式转换上。3.3 高斯滤波的实时性保障核大小与 sigma 的动态平衡高斯滤波的数学定义清晰但 WinForm 用户的痛点是“我想让图像更模糊一点但滑块拖到最大程序就卡住”。问题出在核大小Kernel Size与sigma的关系。理论上高斯核应无限延伸但实践中取size ceil(6 * sigma)保证 99.7% 能量覆盖。当sigma3时size18卷积核是 18×18324 个浮点数每个像素要算 324 次乘加1920×1080 图像总计算量超 6.7 亿次CPU 必然爆表。解决方案是分离高斯核Separable Gaussian。高斯函数满足G(x,y) G(x) * G(y)所以二维卷积可拆成两次一维卷积。先对每行做 1D 高斯18 次乘加再对每列做 1D 高斯18 次乘加总计算量降为1920*1080*18*2 ≈ 7430 万次提速 9 倍。代码实现关键点// 生成一维高斯核 private float[] Generate1DGaussianKernel(float sigma, int radius) { int size radius * 2 1; float[] kernel new float[size]; float sum 0f; for (int i -radius; i radius; i) { float x i; kernel[i radius] (float)Math.Exp(-(x * x) / (2 * sigma * sigma)); sum kernel[i radius]; } // 归一化 for (int i 0; i size; i) kernel[i] / sum; return kernel; } // 分离卷积先水平再垂直 private Bitmap ApplySeparableGaussian(Bitmap source, float sigma) { int radius (int)Math.Ceiling(3 * sigma); // 3σ 覆盖 99.7% var horizontalKernel Generate1DGaussianKernel(sigma, radius); var verticalKernel horizontalKernel; // 对称高斯竖向核同横向 // 步骤1水平卷积 var temp Apply1DConvolution(source, horizontalKernel, true); // 步骤2垂直卷积 var result Apply1DConvolution(temp, verticalKernel, false); temp.Dispose(); return result; }Apply1DConvolution方法中true表示水平卷积遍历每行对每行像素应用一维核false表示垂直卷积遍历每列。这样用户拖动sigma滑块时radius动态变化核大小自适应既保证效果又守住性能底线。我在某汽车零部件检测软件里将sigma上限设为 5对应radius151920×1080 图像滤波稳定在 320ms 内客户验收时全程无卡顿。3.4 中值滤波的边界挑战Replicate模式为何是工业首选均值滤波平滑噪声但会模糊边缘中值滤波保边缘但对椒盐噪声效果好。WinForm 用户常抱怨“中值滤波后图像四周一圈发虚”。这是边界处理不当的典型症状。中值滤波必须对每个像素取邻域如 3×3但图像边缘像素没有完整邻域。四种填充策略中ZeroPadding补 0边缘变黑工业图像里常有暗场背景补黑会扩大暗区失真Wrap环形卷绕把右边缘接左边缘对自然图像尚可但对 PCB 图像焊盘跑到画布左边完全不可接受Reflect镜像反射如[a,b,c]变成[c,b,a,b,c]边缘有重复纹理易产生伪影Replicate复制边缘像素如[a,b,c]变成[a,a,b,c,c]保持边缘强度不变最符合工业检测中“缺陷不应被边界扭曲”的原则。因此本项目默认Replicate并在 UI 提供下拉框让用户切换。实现Replicate的关键代码private int GetClampedX(int x, int width) { if (x 0) return 0; if (x width) return width - 1; return x; } private int GetClampedY(int y, int height) { if (y 0) return 0; if (y height) return height - 1; return y; } // 中值滤波核心循环 for (int y 0; y height; y) { for (int x 0; x width; x) { // 取 3×3 邻域坐标经 clamped 处理 int[] pixels new int[9]; int idx 0; for (int dy -1; dy 1; dy) { for (int dx -1; dx 1; dx) { int nx GetClampedX(x dx, width); int ny GetClampedY(y dy, height); pixels[idx] GetGrayValue(source, nx, ny); // 获取灰度值 } } // 排序取中值 Array.Sort(pixels); SetGrayValue(dest, x, y, pixels[4]); } }GetClampedX/Y函数确保任何超出边界的坐标都被拉回最近的有效像素。这个看似简单的函数解决了 80% 的中值滤波边缘失真问题。4. 实操流程与 UI 交互设计4.1 主界面布局为什么用TableLayoutPanel而非绝对定位WinForm 界面美化热词满天飞但工业软件第一需求是适配不同分辨率。客户现场的工控机有 1366×768 的笔记本屏也有 3840×2160 的 4K 触摸屏。用Anchor或Dock做响应式布局稍有不慎就控件重叠或留白过大。TableLayoutPanel是 WinForm 里最被低估的利器。本项目主窗体采用 2 行 3 列布局第 1 行PictureBox占满整行RowSpan1,ColumnSpan3显示原图/处理后图像第 2 行左侧GroupBox放滤波控件中间GroupBox放小波控件右侧GroupBox放工具按钮。每个GroupBox内部再嵌套TableLayoutPanel例如滤波组控件行列说明Label“高斯 σ”00文字标签TrackBar01滑块范围 0.5–5.0步长 0.1Label“当前值1.2”02实时显示 σ 值Button“应用高斯”10列合并跨 3 列这样无论窗体如何缩放控件始终按网格比例分布。TrackBar的Scroll事件绑定实时更新LabelValueChanged事件触发预览异步计算不阻塞 UI。TableLayoutPanel的AutoSizetrue和AutoSizeModeGrowAndShrink让整个界面像橡皮筋一样弹性伸缩。我曾用此布局在 1024×768 和 3840×2160 屏幕上UI 元素大小比例误差小于 3%客户验收时直接通过没提任何适配问题。4.2 异步处理与进度反馈BackgroundWorker的正确用法所有滤波操作必须异步否则 UI 卡死。Task.Run虽现代但 WinForm 里BackgroundWorker更稳妥——它原生支持ProgressChanged事件能安全更新 UI 进度条。关键配置private BackgroundWorker worker new BackgroundWorker(); private void InitializeWorker() { worker.WorkerReportsProgress true; // 允许报告进度 worker.WorkerSupportsCancellation true; // 允许取消 worker.DoWork Worker_DoWork; worker.ProgressChanged Worker_ProgressChanged; worker.RunWorkerCompleted Worker_RunWorkerCompleted; } private void ApplyFilter_Click(object sender, EventArgs e) { if (worker.IsBusy) return; // 防止重复点击 worker.RunWorkerAsync(new FilterTask { Type FilterType.Gaussian, Sigma trackBarSigma.Value / 10.0f, // 滑块值 0-50映射为 0.0-5.0 SourceBitmap currentBitmap }); }FilterTask是一个结构体打包所有参数。DoWork事件里执行耗时计算ProgressChanged里更新ProgressBar和LabelRunWorkerCompleted里接收结果e.Result并赋值给pictureBox1.Image。绝不能在DoWork里直接访问pictureBox1这是 WinForm 多线程的红线。BackgroundWorker的ReportProgress方法会自动封送Marshal到 UI 线程安全可靠。我在某电池极片检测项目里用此模式实现了“滤波中可随时取消”客户操作员误点按钮后按 Esc 键 0.2 秒内中断体验极佳。4.3 小波模块的交互设计四宫格子带图的实现技巧小波分解结果不是单张图而是 LL、LH、HL、HH 四个子带。WinForm 里用四个PictureBox并排显示但有个隐藏问题子带尺寸是原图的 1/2一层分解若直接pictureBoxLL.Image llBitmap图片会因SizeModeNormal而只显示左上角 1/4。解决方案是统一设置SizeModeZoom并手动调整PictureBox大小private void DisplayWaveletBands(Bitmap ll, Bitmap lh, Bitmap hl, Bitmap hh) { // 计算子带尺寸假设一层分解 int w ll.Width; int h ll.Height; // 设置 PictureBox 大小匹配子带 pictureBoxLL.Size new Size(w, h); pictureBoxLH.Size new Size(w, h); pictureBoxHL.Size new Size(w, h); pictureBoxHH.Size new Size(w, h); // 设置 Zoom 模式居中显示 pictureBoxLL.SizeMode PictureBoxSizeMode.Zoom; pictureBoxLH.SizeMode PictureBoxSizeMode.Zoom; pictureBoxHL.SizeMode PictureBoxSizeMode.Zoom; pictureBoxHH.SizeMode.Zoom; // 赋值 pictureBoxLL.Image ll; pictureBoxLH.Image lh; pictureBoxHL.Image hl; pictureBoxHH.Image hh; }SizeModeZoom会等比缩放子带图填满PictureBox同时保持宽高比。Size属性手动设为子带实际尺寸避免AutoSize导致控件挤在一起。这样用户一眼就能对比 LL整体结构和 HH噪点分布比看一堆数字更有诊断价值。5. 常见问题排查与独家避坑指南5.1 典型问题速查表问题现象根本原因解决方案实操验证pictureBox1.Image赋值后图像显示为全黑Bitmap创建时未指定PixelFormatGDI 默认用Format32bppArgb但某些 JPEG 解码后是Format24bppRgb颜色通道错位创建Bitmap后用LockBits检查PixelFormat若非Format24bppRgb或Format32bppArgb用new Bitmap(oldBmp, oldBmp.Size)强制转换在LoadImageSafely末尾添加if (bmp.PixelFormat ! PixelFormat.Format24bppRgb bmp.PixelFormat ! PixelFormat.Format32bppArgb) { var fixedBmp new Bitmap(bmp); bmp.Dispose(); return fixedBmp; }高斯滤波后图像整体变亮或变暗卷积核未归一化sum(kernel) ! 1.0导致像素值系统性偏移在Generate1DGaussianKernel末尾必须for (int i0; isize; i) kernel[i] / sum;用sigma1.0生成核打印sum确认为0.999999...非1.234中值滤波后出现彩色噪点输入图像是彩色Bitmap但中值滤波只处理了R通道G/B通道未同步处理中值滤波必须对 RGB 三通道分别取中值或先转灰度再滤波推荐在GetGrayValue中用Color c source.GetPixel(x,y); return (int)(c.R*0.299 c.G*0.587 c.B*0.114);确保灰度计算一致小波分解后子带图像有明显条纹Haar 小波分解时水平/垂直滤波顺序错误或奇偶行/列处理不一致严格按“先水平低频/高频再垂直低频/高频”顺序对width/height为奇数的图像floor(width/2)和ceil(width/2)要明确区分用 100×100 全白图测试LL 子带应为 50×50 全白LH/HL/HH 应为全 0若有条纹检查for (int i0; iwidth; i2)循环是否漏掉i1程序运行一段时间后内存持续增长Bitmap对象未Dispose或Graphics对象未释放每次new Bitmap后必须using (var g Graphics.FromImage(bmp)) { ... }所有Image赋值前先if (pb.Image ! null) pb.Image.Dispose();在FormClosing事件中遍历所有PictureBox强制Dispose其Image5.2 我踩过的三个深坑与血泪经验坑一Timer与图像刷新的竞态条件曾为实现“实时摄像头预览滤波”用Timer每 33ms 触发一次pictureBox1.Image newFrame。结果发现Timer的Tick事件在 UI 线程执行而newFrame是另一线程如 AForge 的NewFrame事件产生的Bitmap直接赋值导致Cross-thread operation not valid。解决方法不是加Invoke而是用SynchronizationContext捕获 UI 上下文在NewFrame事件里context.Post(_ pictureBox1.Image frame, null)。但更优解是放弃Timer用PictureBox的Paint事件 Invalidate()。在NewFrame里存frame到字段然后pictureBox1.Invalidate()Paint事件中e.Graphics.DrawImage(frame, ...)。这样绘图完全在 UI 线程零风险。坑二PropertyGrid修改Bitmap属性的幻觉网络热词里“winform的 propertygrid 只能查看不能修改怎么现实”本质是Bitmap是引用类型PropertyGrid显示的是对象地址不是像素数据。用户以为在PropertyGrid里改了Width其实只是改了Bitmap对象的Width属性只读毫无意义。正确做法PropertyGrid只用于配置参数如sigma、kernelSize不用于操作Bitmap。把PropertyGrid.SelectedObject设为一个FilterSettings类里面全是public float Sigma { get; set; }这样的属性Apply按钮触发滤波时读取这些值即可。坑三ShowDialog与BackgroundWorker的死锁为做“滤波参数高级设置”弹出模态窗settingsForm.ShowDialog()。窗体内有BackgroundWorker执行预览。结果ShowDialog阻塞 UI 线程BackgroundWorker的ProgressChanged无法触发窗体假死。根治方法模态窗内禁用BackgroundWorker改用Task.Runawait。async private void btnPreview_Click(object sender, EventArgs e)await Task.Run(() ComputePreview())progressBar.Invoke((MethodInvoker)(() progressBar.Value ...))。ShowDialog不阻塞await的线程切换体验丝滑。5.3 性能调优 checklist工控机上的实测数据在 i3-41703.6GHz 4GB RAM 的工控机上对 1280×960 图像的实测优化效果优化项优化前耗时优化后耗时提升倍数关键操作图像加载1.8s0.23s7.8×FileStream替代FromFileBitmap构造后立即Dispose流灰度转换2.3s0.085s27×ColorMatrix替代GetPixel循环高斯滤波 (σ2.0)4.1s0.32s12.8×分离高斯 LockBits直接内存操作非GetPixel中值滤波 (3×3)3.6s0.28s12.9×unsafe指针 fixed数组排序替代Listint小波分解 (1层)1.9s0.15s12.7×Spanint替代int[]避免堆分配提示LockBits和unsafe代码虽快但必须在Release模式下启用“允许不安全代码”且Bitmap必须是Format24bppRgb或Format32bppArgb。SpanT是 .NET Core 2.1 特性若用 .NET Framework请用ArrayPoolint.Shared.Rent(size)替代new int[size]。最后再分享一个小技巧WinForm 图像处理软件发布时务必在app.config中添加configurationruntimegcServer enabledtrue//runtime/configuration。工控机默认是工作站 GC对大图像处理这种内存密集型任务服务器 GC 能减少 40% 的暂停时间。这个配置让我们的软件在客户现场连续运行 72 小时无卡顿成了交付时的加分项。本文还有配套的精品资源点击获取
返回列表