2021SC@SDUSC
文章目录
一、Bitmap
1.1 简介
Bitmap可以理解为通过一个bit数组来存储特定数据的一种数据结构;由于bit是数据的最小单位,所以这种数据结构往往是非常节省存储空间。举个例子:
《编程珠玑》中描述了一个这样的问题:只能使用1MB左右的内存空间,如何对
1
0
7
10^{7}
107 个正整数进行排序(每一个正整数都<n)?
最容易想到的应该就是八大排序算法,但是很显然占用空间过大。作者介绍了Bitmap算法。其基本思想是用1个bit来表示[0–n]中数是否存在,如果存在这个bit置为1,否则置0。第一步:我们需要创建一个
1
0
7
10^{7}
107位的字符串,占用内存
1
0
7
10^{7}
107/(8*
2
20
2^{20}
220)MB≈1MB,并将每位设成0。第二步:读取正整数,对于每个i,将bit[i]置为1。第三步:按位顺序遍历字符串。
伪代码如下:
for i = [0,n)
bit[i] = 0
for each i in the input file
bit[i] = 1
for i = [0,n)
if bit[i] == 1
write i on the output file
1.2 优缺点
- 优点:由于采用了bit为单位来存储数据并建立映射关系来查找位置,因此可以大大减少存储空间,加快在大量数据中查询的时间。(有点哈希表的意思,但哈希中的value值数据类型可以丰富多样,而Bitmap最终查到的value只能表示简单的几种状态。)
- 缺点:Bitmap中的查询结果(value)能表达的状态有限,且所有的数据不能重复。即不可对重复的数据进行排序和查找。
1.3 bitmap与二维码关系
我们在Zxing中所述的bitmap与1.1中所述的bitmap有所不同(这里是后来学习时觉得越来越不对劲才发现的TAT)。上述的bitmap是用1bit位来表示某种状态,适用于大规模的数据,通常判断数据存在不存在。而我们这一系列的博客中所说的bitmap是用特定大小的内存空间来表示单个像素的色值。
从数学角度上,面是由无数个点构成的,但是从生理角度,人类的肉眼无法区分很小的点。所以在实际应用中,我们没有必要用无数个点来表示一张图片,甚至都没有必要使用足够多的点,只需要让点的个数和大小在人眼能区分的极限之上一点点就好了。
BMP(Bitmap-File)图形文件是Windows采用的图形文件格式,在Windows环境下运行的所有图像处理软件都支持BMP图像文件格式。
二维码是以图片的形式被我们使用的,所以在用电脑处理的时候自然会用到bitmap,并不是在整理1.1时以为的bitmap算法
二、LuminanceSource
2.1 LuminanceSource中的方法
方法 | 作用 |
---|---|
LuminanceSource(int width, int height) | 在创建LuminanceSource对象时,规定传入width和height参数,并赋值给类属性 |
getRow(int y, byte[] row) | 从底层平台的位图中获取一行亮度数据。值的范围为0(黑色)到255(白色)。因为Java没有无符号字节类型,所以调用者必须对每个值使用0xff按位与1。此方法的实现最好只获取此行,而不是整个图像(因为可能不会安装2D读取器,也可能永远不会调用getMatrix())。参数:y∈[0,getHeight()),要获取的行;row预选的数组 |
getMatrix() | 获取bitmap的亮度数据,返回值为byte[] |
getWidth() | 获取bitmap的宽度,返回值为int |
getHeight() | 获取bitmap的高度,返回值为int |
isCropSupported() | 此子类是否支持裁剪,返回值为boolean |
crop(int left, int top, int width, int height) | 返回带有裁剪图像数据的新对象。实现可能保留对原始数据的引用,而不是副本。仅当isCropSupported()为true时才可调用,否则抛出UnsupportedOperationException异常“不支持裁剪”。返回值为LuminanceSource。参数:left∈[0,getWidth()),左坐标;top∈[0,getHeight()),顶坐标;width裁剪的矩形宽度;height裁剪的矩形高度; |
isRotateSupported() | 此子类是否支持逆时针旋转,返回值为布尔类型 |
invert() | 返回值是InvertedLuminanceSource类型 |
rotateCounterClockwise() | 返回图像数据逆时针旋转90度的新对象。仅当isRotateSupported() 为true时才可调用,否则抛出UnsupportedOperationException异常“不支持旋转90度”。返回值为LuminanceSource |
rotateCounterClockwise45() | 返回逆时针旋转45度图像数据的新对象。仅当isRotateSupported() 为true时才可调用,否则抛出UnsupportedOperationException异常“不支持旋转45度”。返回值为LuminanceSource |
toString() | 打印图片信息,值在[0,64)用’#'表示;[64,128)用’+‘表示;[128,192)用‘.’表示,其余的用空格表示。返回值是String类型 |
2.2 LuminanceSource子类简介
- RGBLuminanceSource :传入的流数据格式为RGB类型(从文件中的ARGB2像素阵列中获取),主要应用场景是解析本地文件。不支持旋转。
- PlanarYUVLuminanceSource :传入的流数据格式为YUV类型,主要应用场景是解析摄像头返回的数据。可以选择裁剪,这可以用来排除周围多余的像素并加快解码速度。大部分二维码的识别都是基于二值化的方法,在色域的处理上,YUV的二值化效果要优于RGB,并且RGB图像在处理中不支持旋转。
- BufferedImageLuminanceSource :传入的流数据格式为BufferedImage类型,主要应用在javase中。
- InvertedLuminanceSource : 将传入的数据进行旋转,外部一般不会调用。它return的结果会将亮度进行反转,黑色变为白色,反之亦然,每个值变为(255-value)
上述4个子类都被声明为final,即表明这个类不能被继承。final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法。类被声明为final有以下几种情况:①类的本身方法之间有复杂的调用关系。假如随意创建这些类的子类,子类可能会错误的修改父类的实现细节②出于安全原因,类的实现细节不允许有任何改动③在创建对象模型的时候,确信这个类不会再被扩展
2.3 子类
2.3.1 RGBLuminanceSource
方法 | 作用 |
---|---|
构造方法1:RGBLuminanceSource(int width, int height, int[] pixels) | 将图片转为灰度图像。外部创建RGBLuminanceSource对象时使用 |
构造方法2:RGBLuminanceSource(byte[] pixels,int dataWidth,int dataHeight,int left,int top,int width,int height) | 声明为private,仅供内部使用。 |
getRow(int y, byte[] row) | 重写方法。 |
getMatrix() | 重写方法。返回灰度值矩阵 |
isCropSupported() | 重写方法。返回值为true,即类型为RGBLuminanceSource的对象允许裁剪 |
crop(int left, int top, int width, int height) | 重写方法。返回带有裁剪图像数据的新对象(使用构造方法2)。 |
- 构造方法1详解
调用构造方法1:输入RGB图像,将其转为灰度图像
算法如下(以0x00FF00为例):
00FF00转为二级制为:00000000 11111111 00000000
右移16位变成 00000000;与0xff &,变成r=00000000
右移7位变成 00000000 11111111 0;与0x1fe &,变成g2=11111111 0
不移与0xff &,变成b=00000000
((r + g2 + b) / 4)=1111111,十进制为127,用‘+‘表示
打印转换后的结果:
这里的灰度值计算权衡了R、G、B三个通道但是更侧重于G通道。当然,除此之外RGB转换成灰度的方法还有很多种 - 构造方法2和crop详解:
以crop(1, 1, 1, 1)为例,截取最中间的绿色像素块,然后返回仅供内部使用的构造方法2。构造方法2的参数含义依次为:灰度图像,原数据宽度(3),原数据高度(3),原数据左坐标(0)+开始截取的左坐标(1),原数据顶坐标(0)+开始截取的顶坐标(1),要截取的宽度,要截取的高度。在构造方法2中,首先进行判断,如果截取的矩形超过了原图片的范围,如图:
将会抛出非法参数异常。若合法,则更新所有的属性值。 - getRow详解
以getRow(2, new byte[3])为例,表示想要获取第3行的数据:
如果行数y>数据的hight,则会报参数不合法异常,即请求的行位于图像外部;如果传入的row为空或者是比width小,会给row重新赋一个大小为width的数组。
如果参数都合法,把对应行的灰度值copy到row的对应位置中 - 测试代码和相应解释(关于@Test3):
public final class RGBLuminanceSourceTestCase extends Assert {
//用一位数组的形式传入3*3的RGB图像,对应上面的彩色9宫格
private static final RGBLuminanceSource SOURCE = new RGBLuminanceSource(3, 3, new int[] {
0x000000, 0x7F7F7F, 0xFFFFFF,
0xFF0000, 0x00FF00, 0x0000FF,
0x0000FF, 0x00FF00, 0xFF0000});
//测试Crop()
@Test
public void testCrop() {
//表达式的值为true,则测试通过
assertTrue(SOURCE.isCropSupported());
//调用方法crop,截取最中间的绿色像素块
LuminanceSource cropped = SOURCE.crop(1, 1, 1, 1);
//比较前后两个参数是否相等,相等就测试通过
assertEquals(1, cropped.getHeight());
assertEquals(1, cropped.getWidth());
assertArrayEquals(new byte[] { 0x7F }, cropped.getRow(0, null));
}
//测试Matrix()
@Test
public void testMatrix() {
//比较前后两个参数是否相等,相等就测试通过
assertArrayEquals(new byte[] { 0x00, 0x7F, (byte) 0xFF, 0x3F, 0x7F, 0x3F, 0x3F, 0x7F, 0x3F },
SOURCE.getMatrix());
//调用方法crop,截取后两行数据
LuminanceSource croppedFullWidth = SOURCE.crop(0, 1, 3, 2);
//比较前后两个参数是否相等,相等就测试通过
assertArrayEquals(new byte[] { 0x3F, 0x7F, 0x3F, 0x3F, 0x7F, 0x3F },
croppedFullWidth.getMatrix());
//调用方法crop,截取右下角的4个像素块
LuminanceSource croppedCorner = SOURCE.crop(1, 1, 2, 2);
//比较前后两个参数是否相等,相等就测试通过
assertArrayEquals(new byte[] { 0x7F, 0x3F, 0x7F, 0x3F },
croppedCorner.getMatrix());
}
//测试GetRow()
@Test
public void testGetRow() {
//比较前后两个参数是否相等,相等就测试通过
assertArrayEquals(new byte[] { 0x3F, 0x7F, 0x3F }, SOURCE.getRow(2, new byte[3]));
}
//测试ToString()
@Test
public void testToString() {
//比较前后两个参数是否相等,相等就测试通过
assertEquals("#+ \n#+#\n#+#\n", SOURCE.toString());
}
}
2.3.2 PlanarYUVLuminanceSource
方法 | 作用 |
---|---|
构造方法:PlanarYUVLuminanceSource(byte[] yuvData,int dataWidth,int dataHeight,int left,int top,int width,int height, boolean reverseHorizontal) | (无特殊说明) |
getRow(int y, byte[] row) | 重写方法。 |
getMatrix() | 重写方法。返回灰度值矩阵 |
isCropSupported() | 重写方法。返回值为true,即类型为PlanarYUVLuminanceSource的对象允许裁剪 |
crop(int left, int top, int width, int height) | 重写方法。返回带有裁剪图像数据的新对象(使用构造方法)。 |
renderThumbnail() | 返回int数组,渲染缩略图 |
reverseHorizontal(int width, int height) | 旋转图片 |
YUV 是一种彩色编码系统,主要用在视频、图形处理流水线中(pipeline)。相对于 RGB 颜色空间,设计 YUV 的目的就是为了编码、传输的方便,减少带宽占用和信息出错。Y(明亮度)U(蓝色投影B)V(红色投影R),U 通道数值越高,颜色就越接近蓝色,V 通道数值越高,颜色就越接近红色,Y 通道数值越高,图片则越亮。
PlanarYUVLuminanceSource主要用于安卓,对象是从多从相机设备中返回的YUV数据数组转换得到,可以选择性的将YUV的完整数据剪切其中一部分用于解析(具体参数可以查看其构造函数)。这样可以用于取出边界外多余的像素用于加快解析速度。
- getRow、getMatrix等方法的使用几乎等同与RGBLuminanceSource同名方法。构造方法和crop多的一个参数reverseHorizontal(是否需要旋转图片)默认为false,yuvData表示传入的帧数据。
2.3.3 BufferedImageLuminanceSource
方法 | 作用 |
---|---|
构造方法1:BufferedImageLuminanceSource(BufferedImage image) | 传入的对象是图片时使用 |
构造方法2:BufferedImageLuminanceSource(BufferedImage image, int left, int top, int width, int height) | 传入的图片需要裁剪时使用 |
getRow(int y, byte[] row) | 重写方法。 |
getMatrix() | 重写方法。返回灰度值矩阵 |
isCropSupported() | 重写方法。返回值为true,即类型为BufferedImageLuminanceSource的对象允许裁剪 |
crop(int left, int top, int width, int height) | 重写方法。返回带有裁剪图像数据的新对象(使用构造方法2)。 |
isRotateSupported() | 重写方法。返回值为true,即类型为BufferedImageLuminanceSource的对象允许逆时针旋转 |
rotateCounterClockwise() | 重写方法。返回图像数据逆时针旋转90度的新对象。 |
rotateCounterClockwise45() | 重写方法。返回逆时针旋转45度图像数据的新对象。 |
- 构造方法2详解
首先判断图片是否为灰度图像,如果是,this.image = image,this.left = left,this.top = top,构造完成。
如果不是,判断是否可以裁剪,不可以则抛出异常。然后因为我们传入的image是BufferedImage类型的,所以我们可以利用BufferedImage的构造器,直接将BufferedImageLuminanceSource属性中的image设置为灰度图像。后面又将其获取到raster中。接着对参数image进行操作,先获取image的RGB属性。如果像素点是纯黑色,设置值为11111111,不然的话设置值为公式0.299R + 0.587G + 0.114B四舍五入后的结果。将计算后的像素矩阵赋值给属性中的image。
构造方法1是为了简化创建对象时传递参数的操作,内部实际上还是使用了构造方法2
- rotateCounterClockwise()详解
用到了AffineTransform类进行翻转和Graphics2D进行绘制,翻转45度的方法是在翻转90度的基础上实现的。
2.3.4 InvertedLuminanceSource
方法 | 作用 |
---|---|
构造方法:InvertedLuminanceSource(LuminanceSource delegate)) | 传入的对象是图片时使用 |
getRow(int y, byte[] row) | 重写方法。 |
getMatrix() | 重写方法。返回灰度值矩阵 |
isCropSupported() | 重写方法。返回值为属性delegate的isCropSupported()的值 |
crop(int left, int top, int width, int height) | 重写方法。返回带有裁剪图像数据的新对象(使用构造方法2)。 |
isRotateSupported() | 重写方法。返回值为属性delegate的isRotateSupported()的值 |
rotateCounterClockwise() | 重写方法。返回图像数据逆时针旋转90度的新对象。 |
rotateCounterClockwise45() | 重写方法。返回逆时针旋转45度图像数据的新对象。 |
这个类主要是为了颜色翻转(黑色变白色,白色变黑色)实现都很简单,
特点是在getRow和getMatrix方法中都有类似
row[i] = (byte) (255 - (row[i] & 0xFF));
的翻转语句
- 测试代码
public final class InvertedLuminanceSourceTestCase extends Assert {
@Test
public void testInverted() {
//创建一个宽2高1的RGB图像
BufferedImage image = new BufferedImage(2, 1, BufferedImage.TYPE_INT_RGB);
//设置RGB图像的RGB值,(0,0)点的是白色
image.setRGB(0, 0, 0xFFFFFF);
LuminanceSource source = new BufferedImageLuminanceSource(image);
//开始两个像素是 白、黑
assertArrayEquals(new byte[] { (byte) 0xFF, 0 }, source.getRow(0, null));
//翻转后变成 黑、白
LuminanceSource inverted = new InvertedLuminanceSource(source);
assertArrayEquals(new byte[] { 0, (byte) 0xFF }, inverted.getRow(0, null));
}
}
欢迎提出宝贵意见,感谢观看!
参考:
bitmap位图详解
ZxingAPI
BufferedImage文档
https://iluhcm.com/2016/01/08/scan-qr-code-and-recognize-it-from-picture-fastly-using-zxing/?utm_source=tuicool
RGB是一种色彩模式,通过对红 (R)、绿 (G)、蓝 (B)三个颜色通道的变化以及它们相互之间的叠加来得到各式各样的颜色。这个标准几乎包括了人类视力所能感知的所有颜色,是运用最广的颜色系统之一。而ARGB则是在RGB的基础上增加了一个A(alpha)通道,表示透明度。使用时通常以“#”字符开头,以8位16进制数表示颜色,A\R\G\B各用两位表示,如#7fff0000表示透明度为50%的红色。透明度分为256个等级,即 0 - 255,0就是透明,255就是不透明,对应着16进制 (透明)00 –> FF(不透明)在线颜色转换连接 ↩︎
使用该注解标注的public void方法会表示为一个测试方法。让测试极为方便 ↩︎