在WPF中使用WriteableBitmap对接工业相机及常用操作
写作背景
写这篇文章主要是因为工业相机(海康、大恒等)提供的.NET开发文档和示例程序都是用WinForm项目来说明举例的,而在WPF项目中对图像的使用和处理与在WinForm项目中有很大不同。在WinForm中用System.Drawing.Bitmap来处理图像,而在WPF中是用System.Windows.Media.Imaging.WriteableBitmap来处理图像的。本文的主要内容也是对WriteableBitmap类使用的介绍。
从相机中接收图像
首先当然要创建一个WriteableBitmap,这里以PixelFormats.Bgr24像素格式举例说明
PropertyInfo dpiXProperty = typeof(SystemParameters).GetProperty("DpiX", BindingFlags.NonPublic | BindingFlags.Static);
PropertyInfo dpiYProperty = typeof(SystemParameters).GetProperty("Dpi", BindingFlags.NonPublic | BindingFlags.Static);
int dpiX = (int)dpiXProperty.GetValue(null);
int dpiY = (int)dpiYProperty.GetValue(null);
WriteableBitmap WBitmap = new WriteableBitmap(PhotoWidth, PhotoHeight, dpiX, dpiY, PixelFormats.Bgr24, BitmapPalettes.Halftone256);
接收相机中的照片数据得使用相机SDK提供的方法,一般都是向方法提供一个IntPtr变量,然后相机SDK会将图像数据复制一份到这个内存地址中。
WriteableBitmap对象表示像素数据的地址是WBitmap.BackBuffer。
而在WinForm中的Bitmap则有两种方式接收图像。
一种是创建指定大小和像素格式的Bitmap后使用LockBits获得BitmapData,BitmapData的scan0表示像素数据地址然后和前面的方式一样。
另一种是在创建Bitmap时使用Bitmap(int width, int height, int stride, PixelFormat format, IntPtr scan0)构造函数,使用代表像素数据的IntPtr传给scan0参数即可。
图像的显示
WriteableBitmap使用两个缓冲区,一个后端缓冲区和一个前端缓冲区,所以一个WriteableBitmap对象存着图像的两份数据。前面我们接收图像是把图像存入后端缓冲区中,而界面上Image控件
显示图像用的是前端缓冲区中的图像。所以现在我们需要把后端缓冲区中的数据更新到前端缓冲区中去,然后传给Image的Source属性即可。
WBitmap.Lock();
WBitmap.AddDirtyRect(new Int32Rect(0, 0, PhotoWidth, PhotoHeight));
WBitmap.Unlock();
MyImage.Source = WBitmap;
Lock锁定后端缓冲区,AddDirtyRect将后端缓冲区数据跟新到前端缓冲区,Unlock解锁后端缓冲区。AddDirtyRect的使用模式是固定的,都是先Lock然后Unlock。
像素操作
System.Drawing.Bitmap对象有GetPixel和SetPixel方法,读取、修改某点的像素值很方便。在WriteableBitmap中则需要用指针区操作。在前面【接收图像】中提到用一个指针地址去接受图像,
所以图像的所有像素数据都保存在这个起始地址的内存中,也就是后端缓冲区中。WBitmap.BackBuffer指向的就是坐标(0,0)点的像素数据,以读取(100,200)坐标点的像素数据为例。
先介绍要用到的两个属性:WBitmap.BackBufferStride表示一行图像数据的字节数,WBitmap.Format.BitsPerPixel表示一个像素的位数。
首先计算(100,200)处的偏移量应该是WBitmap.BackBufferStride*200 + WBitmap.Format.BitsPerPixel / 8*100,那么BackBuffer加上偏移量就是(100,200)处的地址 ,所以完整的读取像素值的代码如下:
int offset = WBitmap.BackBufferStride * 200 + PixelFormats.Bgr24.BitsPerPixel / 8 * 100;
unsafe {
byte* pb = (byte*)WBitmap.BackBuffer.ToPointer();
byte cB = pb[offset];
byte cG = pb[offset + 1];
byte cR = pb[offset + 2];
}
或者使用System.Runtime.InteropServices.Marshal.ReadByte,不需要unsafe模式
byte cB = Marshal.ReadByte(WBitmap.BackBuffer, offset);
byte cG = Marshal.ReadByte(WBitmap.BackBuffer, offset+1);
byte cR = Marshal.ReadByte(WBitmap.BackBuffer, offset+2);
像素修改也是同样的方法,把读取变成赋值即可,或者用Marshal.WriteByte写值。
图像的保存
与Bitmap使用Save不同,WriteableBitmap需要使用Encoder编码后才能保存成文件。
using FileStream stream = new FileStream(@"C:\newu8.bmp", FileMode.Create);
BmpBitmapEncoder encoder = new BmpBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(WBitmap));
encoder.Save(stream);
这里使用BmpBitmapEncoder编码器来保存bmp图像,要保存成其他格式则使用对应的编码器即可,如JpegBitmapEncoder等。
注意事项
1:像素格式问题,相机SDK提供转化成你需要的格式的方法,在接收图像时要确保两边像素格式一致。相机SDK中提供的像素格式、Bitmap的System.Drawing.Imaging.PixelFormat和WriteableBitmap的System.Windows.Media.PixelFormats对同一像素格式的命名是不同的。比如本文中的PixelFormats.Bgr24对应的是Bitmap中的PixelFormat.Format24bppRgb。可以通过解析同一张图像来确定两者之间的对应关系。
2:使用工业相机采图的方式一般都是使用回调函数的形式,所以在回调函数的多线程环境中执行显示图像的代码要注意控件的跨线程访问问题。
3:图像保存用的是后端缓冲区中的数据(再次证明前端缓冲区只是用来在界面上展示的),意味着只需要在界面上展示图像的时才调用AddDirtyRect。
4:修改部分像素点值后需要在界面上展示的,调用AddDirtyRect方法时Int32Rect参数应该是包含你修改位置的最小面积矩形区域,出于性能考虑不建议使用整个图像区域。
在WPF中使用WriteableBitmap对接工业相机及常用操作的更多相关文章
- js中对Object对象的一些常用操作总结
前言我前面的文章,写过js中“类”与继承的一些文章.ES5我们可以通过 构造函数 或者 Object.create()等方式来模拟出js中的“类”,当然,对象呢是类的实例化,我们可以通过如下方式创建对 ...
- python中列表,字典,字符串常用操作
1. 列表操作 分类 关键字 / 函数 / 方法 说明 增加 列表.append(值) 在末尾追加值 列表.insert(索引, 值) 在指定位置插入值, 超过索引会追加值 列表.extend ...
- MSDN 杂志:UI 前沿技术 - WPF 中的多点触控操作事件
原文 MSDN 杂志:UI 前沿技术 - WPF 中的多点触控操作事件 UI 前沿技术 WPF 中的多点触控操作事件 Charles Petzold 下载代码示例 就在过去几年,多点触控还只是科幻电 ...
- 在WPF中合并两个ObservableCollection
WPF中的ObservableCollection是一个非常常用的集合对象,我们可以通过将它绑定到ListBox之类的集合控件上时,当集合发生变更时,会同步更新到界面上.但是,有的时候我们需要合并两个 ...
- WPF中的常用布局 栈的实现 一个关于素数的神奇性质 C# defualt关键字默认值用法 接口通俗理解 C# Json序列化和反序列化 ASP.NET CORE系列【五】webapi整理以及RESTful风格化
WPF中的常用布局 一 写在开头1.1 写在开头微软是一家伟大的公司.评价一门技术的好坏得看具体的需求,没有哪门技术是面面俱到地好,应该抛弃对微软和微软的技术的偏见. 1.2 本文内容本文主要内容 ...
- 在WPF中使用依赖注入的方式创建视图
在WPF中使用依赖注入的方式创建视图 0x00 问题的产生 互联网时代桌面开发真是越来越少了,很多应用都转到了浏览器端和移动智能终端,相应的软件开发上的新技术应用到桌面开发的文章也很少.我之前主要做W ...
- MVVM模式解析和在WPF中的实现(六) 用依赖注入的方式配置ViewModel并注册消息
MVVM模式解析和在WPF中的实现(六) 用依赖注入的方式配置ViewModel并注册消息 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二 ...
- MVVM模式解析和在WPF中的实现(五)View和ViewModel的通信
MVVM模式解析和在WPF中的实现(五) View和ViewModel的通信 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 M ...
- MVVM设计模式和WPF中的实现(四)事件绑定
MVVM设计模式和在WPF中的实现(四) 事件绑定 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在WPF中 ...
- MVVM模式解析和在WPF中的实现(三)命令绑定
MVVM模式解析和在WPF中的实现(三) 命令绑定 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在WPF中 ...
随机推荐
- S/4 HANA 中的 Email Template
电子邮件是非常常见的业务需求. SAP 了解这一点,并在 S/4 HANA(cloud和on premise)中引入了非常有趣的功能--Email Template.它将CDS视图和HTML模板结合了 ...
- C/C++如何写调试宏
1. 调试宏以及测试 在写代码时,不可避免需要打印提示.警告.错误等信息,且要灵活控制打印信息的级别.另外,还有可能需要使用宏来控制代码段(主要是调试代码段)是否执行.为此,本文提供一种调试宏定义方案 ...
- 【人脸识别】OpenCV获取自己的图像
思路:先获取10000张自己的图像,然后通过CNN神经网络进行学习. 第一步:先获取自己的脸的数据.如何做? 代码如下: import cv2 import os import sys import ...
- iceoryx源码阅读(六)——共享内存创建
目录 1 共享内存的组织 2 共享内存创建 2.1 IceOryxRouDiMemoryManager::createAndAnnounceMemory 2.2 RouDiMemoryManager: ...
- 海康威视web插件安装后,谷歌浏览器还是不能看视频问题
首先要根据弹出的信息提示,下载并安装视频播放插件, 安装完成后重新打开谷歌浏览器,重新登录系统,如果还是不能看视频,请按下面的方法设置: 步骤1:谷歌浏览器,地址栏中输入:chrome://flags ...
- Python语言:散修笔记
文章目录 前言 转义字符的使用 原字符 变量的定义 类型转换 注释 接收用户信息 运算规则 整除运算 幂运算 比较运算符 布尔运算 运算优先级 对象的布尔值 if else elif分支结构 条件表达 ...
- 国产系统UOS安装体验
原文链接 https://www.giantliu.cn/2020/09/04/200904InstallUOS/ UOS简介 统信桌面操作系统(Uniontech OS)个人正式版是统信软件基于Li ...
- RocketMQ的单节点环境搭建
1 介绍RocketMQ作为一款纯java.分布式.队列模型的开源消息中间件,支持事务消息.顺序消息.批量消息.定时消息.消息回溯等. 1.1 RocketMQ 特点支持发布/订阅(Pub/Sub)和 ...
- Swoole 源码分析之 TCP Server 模块
首发原文链接:https://mp.weixin.qq.com/s/KxgxseLEz84wxUPjzSUd3w 大家好,我是码农先森. 今天我们来分析 TCP Server 模块 的实现原理,下面这 ...
- HTML——标签语法
<标签名 属性1="属性值1" 属性2="属性值2"-->内容部分</标签名> <标签名 属性1="属性值1" ...