在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中 ...
随机推荐
- 防抖节流的含义使用场景及js实现原理
1.防抖:n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时.代码实现重在清零 clearTimeout. 应用:登录,提交,浏览器窗口的resizes事件,文本编辑保存 <script ...
- Mybatis学习四(分页助手pagehelper)
Mybatis学习过程中有一个很重要的插件分页助手(pagehelper) 能够运用这个插件也非常简单 1.导入jar包 [jsqlparser-2.0.jar包] [点击下载https://gith ...
- 九、DataArts Studio
功能总览: 基本概念: 主题设计:通过分层架构表达对数据的分类和定义,帮助理清数据资产,明确业务领域和业务对象的关联关系. 主题域分组:基于业务场景对主题域分组. 主题域:互不重叠数据的高层面的数据分 ...
- ide构建SpringMVC框架
框架原理图如下: 1. 创建如图项目 2. 在lib中所需导入jar包 3. 配置变量 (1) (2)add library (3)选择web app libraries 4. 配置web.xml文件 ...
- 为什么需要学习ITSM/ITIL
假如你需要管理一个超过20人的IT服务组织,一般会面临以下问题: 人多事杂活重,每个人都很累,工作却还是一团糟糕, 用户方怨声载道,领导也颇有微词,同事间也经常互相甩锅埋坑, 工作只是救火或者混日子, ...
- 解决:Failed to get D-Bus connection: Operation not permitted
docker中安装完httpd服务后,使用命令systemctl start httpd.service,发现报错,错误信息:Failed to get D-Bus connection: Opera ...
- 02.go-admin IDE配置配置命令启动方式讲解笔记
目录 go-admin版本 视频地址 一.代码地址 二.在线文档 三.首次配置需要初始化数据库资源信息(已初始化过数据库的,跳过此步) 配置数据库迁移 五.配置启动项目,用goland IDE进行启动 ...
- Golang 开发常用代码片段
Struct to JsonString type BaseRequest struct { httpMethod string domain string path string params ma ...
- 使用.NET查询日出日落时间
在WPF中,通过资源文件实现主题切换是个常见的功能,有不少文章介绍了如何实现手动切换主题.那如何实现自动切换主题呢?通常有两种机制:一是跟随系统明暗主题切换,二是像手机操作系统那样根据日出日落时间自动 ...
- python 源
清华:https://pypi.tuna.tsinghua.edu.cn/simple 阿里云:http://mirrors.aliyun.com/pypi/simple/ 中国科技大学 https: ...