C#数字图像处理时注意图像的未用区域

图1. 被锁定图像像素数组基本布局
如图1所示,数组的宽度并不一定等于图像像素数组的宽度,还有一部分未用区域。这是为了提高效率,系统要确定每行的字节数必须为4的倍数。例如一幅24位、宽为17个像素的图像,它需要每行占有的空间为51(3
* 17)个字节,但51不是4的倍数,因此还需要扩充1个字节,从而使每行的字节数扩展为52(4 * 13,即Stride=52),这样就满足了每行字节数是4的倍数的条件。需要扩展多少个字节不仅是由图像的宽度决定,而且还由图像像素的格式决定。
如果处理的是任意宽度的图像,那么在进行下一行扫描的时候,需要把不含图像数据、仅起对齐作用的扩展字节去掉。此时对图像像素数组的遍历的形式如下:
(1).灰度图像:
- Rectangle rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
- BitmapData bmpData = bitmap.LockBits(rect, ImageLockMode.ReadWrite, bitmap.PixelFormat);
- IntPtr ptr = bmpData.Scan0; // 首地址
- int bytes = bmpData.Stride * bmpData.Height; // 像素个数,包括未用空间
- byte[] grayValues = new byte[bytes];
- System.Runtime.InteropServices.Marshal.Copy(ptr, grayValues, 0, bytes);
- for (int i = 0; i < bmpData.Height; i++)
- {
- // 仅处理每行中为图像像素的数据,舍弃未用空间
- for (int j = 0; j < bmpData.Width; j++)
- {
- // use of grayValues[i * bmpData.Stride + j];
- }
- }
- System.Runtime.InteropServices.Marshal.Copy(grayValues, 0, ptr, bytes);
- bitmap.UnlockBits(bmpData);
或者:
- // 获取图像参数
- int stride = bmpData.Stride; // 扫描线的宽度
- int offset = stride - width; // 显示宽度与扫描线宽度的间隙
- IntPtr iptr = bmpData.Scan0; // 获取bmpData的内存起始位置
- int scanBytes = stride * height; // 用stride宽度,表示这是内存区域的大小
- // 位置指针,指向源数组
- int posScan = 0;
- byte[] grayValues = new byte[scanBytes]; // <span style="font-family: Arial; ">为目标数组分配内存</span>
- for (int x = 0; x < height; x++)
- {
- // 下面的循环节是模拟行扫描
- for (int y = 0; y < width; y++)
- {
- // grayValues[posScan++]
- }
- posScan += offset; // 行扫描结束,跳过未用空间字节
- }
(2).24位RGB图像:
- Rectangle rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
- BitmapData bmpData = bitmap.LockBits(rect, ImageLockMode.ReadWrite, bitmap.PixelFormat);
- IntPtr ptr = bmpData.Scan0; // 首地址
- int bytes = bmpData.Stride * bmpData.Height; // 像素个数,包括未用空间
- byte[] rgbValues = new byte[bytes];
- System.Runtime.InteropServices.Marshal.Copy(ptr, rbgValues, 0, bytes);
- for (int i = 0; i < bmpData.Height; i++)
- {
- // 仅处理每行中为图像像素的数据,舍弃未用空间
- for (int j = 0; j < bmpData.Width * 3; j += 3)
- {
- // R: rgbValues[i * bmpData.Stride + j + 2]
- // G: rgbValues[i * bmpData.Stride + j + 1]
- // B: rgbValues[i * bmpData.Stride + j]
- }
- }
- System.Runtime.InteropServices.Marshal.Copy(rgbValues, 0, ptr, bytes);
- bitmap.UnlockBits(bmpData);
以上代码将图像的整个像素数组都复制到数组grayValue或者rgbValue中,包括像素和仅用于对齐的未用空间,然后再在数组grayValue或者rgbValue中只对像素数据进行处理,跳过每一行的未用空间(跳过末尾几列)。
以下实验中,对任意尺度(每行字节数不是4字节的整数倍)的图像进行灰度直方图绘制,如果不考虑未用空间的话会导致错误结果(本实验中错误程度较小)。

图2.未考虑图像中未用空间的错误结果
用Matlab进行灰度直方图计算会发现真实结果中最大灰度的像素个数不是52811,而是53195。
- f = imread('GrayTest.bmp');
- h = imhist(f)

错误的原因是,因为图像的字节宽度不是4字节的整数倍,图像的像素数组包括了未用空间(每一行中都包括了未用空间)程序只从规模为bmpData.Stride * bmpData.Height字节的像素数组中复制了curBitmap.Width * curBitmap.Height规模的字节(灰度像素,1字节),这些字节中既包含了像素也包含了未用空间,如图4所示:

图4.出错时对像素数组复制的字节
最后在代码中考虑未用空间能得到正确结果:

图5.正确结果
C#数字图像处理时注意图像的未用区域的更多相关文章
- Win8 Metro(C#)数字图像处理--3.2图像方差计算
原文:Win8 Metro(C#)数字图像处理--3.2图像方差计算 /// <summary> /// /// </summary>Variance computing. / ...
- Win8 Metro(C#)数字图像处理--3.3图像直方图计算
原文:Win8 Metro(C#)数字图像处理--3.3图像直方图计算 /// <summary> /// Get the array of histrgram. /// </sum ...
- Win8 Metro(C#)数字图像处理--3.4图像信息熵计算
原文:Win8 Metro(C#)数字图像处理--3.4图像信息熵计算 [函数代码] /// <summary> /// Entropy of one image. /// </su ...
- Win8 Metro(C#)数字图像处理--3.5图像形心计算
原文:Win8 Metro(C#)数字图像处理--3.5图像形心计算 /// <summary> /// Get the center of the object in an image. ...
- Win8 Metro(C#)数字图像处理--3.1图像均值计算
原文:Win8 Metro(C#)数字图像处理--3.1图像均值计算 /// <summary> /// Mean value computing. /// </summary> ...
- Win8 Metro(C#)数字图像处理--2.74图像凸包计算
原文:Win8 Metro(C#)数字图像处理--2.74图像凸包计算 /// <summary> /// Convex Hull compute. /// </summary> ...
- Win8 Metro(C#)数字图像处理--2.68图像最小值滤波器
原文:Win8 Metro(C#)数字图像处理--2.68图像最小值滤波器 /// <summary> /// Min value filter. /// </summary> ...
- Win8 Metro(C#)数字图像处理--2.52图像K均值聚类
原文:Win8 Metro(C#)数字图像处理--2.52图像K均值聚类 [函数名称] 图像KMeans聚类 KMeansCluster(WriteableBitmap src,i ...
- Win8 Metro(C#)数字图像处理--2.45图像雾化效果算法
原文:Win8 Metro(C#)数字图像处理--2.45图像雾化效果算法 [函数名称] 图像雾化 AtomizationProcess(WriteableBitmap src,i ...
随机推荐
- js如何使用正则表达式验证电话号码(可选区号)和邮箱?(分步骤)
js如何使用正则表达式验证电话号码(可选区号)和邮箱?(分步骤) 一.总结 js进阶正则表达式16电话号码和邮箱正则(分类解决邮箱验证)(分组加?解决电话号码区号选择问题)([\w\.-]中括号解决邮 ...
- 第二篇:_UICascadingTextStorage attributesAtIndex:effectiveRange:]: Range or index out of bounds
注意下文标红字段: #pragma mark- 输入改变时 - (void) textFieldDidChanged:(UITextField *) TextField{ //搜索关键字一旦改变,将重 ...
- Ajax基础与Json应用(一)
一.Ajax概念 Ajax是异步的javacript和xml 发音: Ajax [ˈeɪˌdʒæks] 二.同步与异步 传统方式(同步):一个请求对应一个回应,他们是同步的,回应不完成,没办法对这个页 ...
- Go语言学习(十)bytes包处理字节切片
bytes包提供了对字节切片进行读写操作的一系列函数 字节切片处理的函数比較多,分为基本处理函数,比較函数,后缀检查函数,索引函数,切割函数, 大写和小写处理函数和子切片处理函数等. 1.字节切片基本 ...
- JS null问题
在学习getElementById()方法的过程中出现了这样一个问题,便想记录下来. 分析问题之前,我们最好还是先来认识一下getElementById()方法.getElementById()方法, ...
- js 进阶 10 js选择器大全
js 进阶 10 js选择器大全 一.总结 一句话总结:和css选择器很像 二.JQuery选择器 原生javaScript中,只能使用getELementById().getElementByNam ...
- Java: Map里面的键和值可以为空吗?
在Java中,Map里面的键和值可以为空吗?我们先来看一个例子: private static void TestHashMap() { // TODO Auto-generated method s ...
- UIPasteboard粘贴板:UITableView复制粘贴功能(二)
这篇咱写写一写在UITableView上实用剪贴板的两种方法: 一:在自定义cell里面使用 其实,在cell的使用跟在label里面的用法基本一样 1.放方法: - (BOOL)canBecomeF ...
- 从华为产品学到什么是devops
根据Gartner 2015 I&O Automation 报告,DevOps处于技术发展的最高点,实践受到高度关注,到底devops魔力在哪里? 从devops实践看主要是打破开发人员和运营 ...
- Linux SVNserver建立
1. Ubuntu PC一个.最好是最新的Ubuntu稳定的版本号 2. 运行以下命令来安装subversion: sudo apt-get update sudo apt-get install s ...