简单bmp图片处理工具——python实现
发现问题,目前正在调试:
灰度化图片时,如果图片的高度比宽高,会提示超出数组边界,没想通,正在看。
是因为之前初始化像素个数的时候出了些问题,现在已经修正。github正在重新提交
预备实现功能:
1、读取bmp文件
2、保存bmp文件
3、对bmp图片进行放大、缩小
4、对bmp图片进行灰度化
5、对bmp图片进行旋转
bmp文件格式非常简单,对于我这种初学者来说减少了不少不必要麻烦,故选择写一个处理bmp格式的工具。因为之前自学python一直没有动手,所以语言选择python。
第一步、熟悉bmp文件格式,完成bmp文件的解析、生成
参考了如下博客
1、http://blog.csdn.net/lanbing510/article/details/8176231
2、http://blog.csdn.net/jemenchen/article/details/52658476
根据上面的博客,了解了基本的bmp文件格式,bmp文件可以简单的分为:
1、文件信息头
2、位图信息头
3、调色板
4、像素信息
因为现在的bmp图片一般都没有调色板信息(因为24位),所以忽略第三个。所以bmp文件一般的头部信息(包括文件信息头和位图信息图)总共占用54个字节
使用16进制查看器打开一张bmp格式图片,我们可以看到如下信息
图1 bmp文件
图中蓝色部分就是bmp文件的头部信息,后面的FF为像素数据。可以看到,头部信息总共有54个字节(不包括调色板),具体这些数据由什么组成,可以查看上述的参考博文。
所以现在我们的思路就非常清晰,读取文件的头部信息后,在读取文件的位图数据,即可完成对bmp文件的解析。
所以我们构造了如下的类
文件信息头类
- class BmpFileHeader:
- def __init__(self):
- self.bfType = i_to_bytes(0, 2) # 0x4d42 对应BM
- self.bfSize = i_to_bytes(0, 4) # file size
- self.bfReserved1 = i_to_bytes(0, 2)
- self.bfReserved2 = i_to_bytes(0, 2)
- self.bfOffBits = i_to_bytes(0, 4) # header info offset
位图信息头
- class BmpStructHeader:
- def __init__(self):
- self.biSize = i_to_bytes(0, 4) # bmpheader size
- self.biWidth = i_to_bytes(0, 4)
- self.biHeight = i_to_bytes(0, 4)
- self.biPlanes = i_to_bytes(0, 2) # default 1
- self.biBitCount = i_to_bytes(0, 2) # one pixel occupy how many bits
- self.biCompression = i_to_bytes(0, 4)
- self.biSizeImage = i_to_bytes(0, 4)
- self.biXPelsPerMeter = i_to_bytes(0, 4)
- self.biYPelsPerMeter = i_to_bytes(0, 4)
- self.biClrUsed = i_to_bytes(0, 4)
- self.biClrImportant = i_to_bytes(0, 4)
bmp类至少应该有三个信息:
- 文件信息头
- 位图信息头
- 位图数据
在拥有了上述信息后,下面的事情就变得非常简单,只需要去读取文件,并按照bmp的格式构造出头部信息,位图数据即可完成bmp的解析
解析过程中,我们要注意数据的大小端问题
我们读取、写入数据要以小段模式(这个有点容易混淆,我可能解释的也有点不清楚)
bmp类
- class Bmp(BmpFileHeader, BmpStructHeader):
- def __init__(self):
- BmpFileHeader.__init__(self)
- BmpStructHeader.__init__(self)
- self.__bitSize = 0 # pixels size
- self.bits = [] # pixel array
- @property
- def width(self):
- return bytes_to_i(self.biWidth)
- @property
- def height(self):
- return bytes_to_i(self.biHeight)
- # unit is byte
- @property
- def bit_count(self):
- return bytes_to_i(self.biBitCount) // 8
- @property
- def width_step(self):
- return self.bit_count * self.width
bmp类中解析方法:
- # resolve a bmp file
- def parse(self, file_name):
- file = open(file_name, 'rb')
- # BmpFileHeader
- self.bfType = file.read(2)
- self.bfSize = file.read(4)
- self.bfReserved1 = file.read(2)
- self.bfReserved2 = file.read(2)
- self.bfOffBits = file.read(4)
- # BmpStructHeader
- self.biSize = file.read(4)
- self.biWidth = file.read(4)
- self.biHeight = file.read(4)
- self.biPlanes = file.read(2)
- self.biBitCount = file.read(2)
- # pixel size
- self.__bitSize = (int.from_bytes(self.bfSize,
- 'little') -
- int.from_bytes(self.bfOffBits, 'little')) \
- // (int.from_bytes(self.biBitCount, 'little') // 8)
- self.biCompression = file.read(4)
- self.biSizeImage = file.read(4)
- self.biXPelsPerMeter = file.read(4)
- self.biYPelsPerMeter = file.read(4)
- self.biClrUsed = file.read(4)
- self.biClrImportant = file.read(4)
- # load pixel info
- count = 0
- while count < self.__bitSize:
- bit_count = 0
- while bit_count < (int.from_bytes(self.biBitCount, 'little') // 8):
- self.bits.append(file.read(1))
- bit_count += 1
- count += 1
- file.close()
有了上述信息,我们再重新生成bmp文件就很简单了,直接将数据再重新写回去就可以了,如果有额外要求,可以自己构建头部信息,然后再重新写回
bmp类中生成方法
- def generate(self, file_name):
- file = open(file_name, 'wb+')
- # reconstruct File Header
- file.write(self.bfType)
- file.write(self.bfSize)
- file.write(self.bfReserved1)
- file.write(self.bfReserved2)
- file.write(self.bfOffBits)
- # reconstruct bmp header
- file.write(self.biSize)
- file.write(self.biWidth)
- file.write(self.biHeight)
- file.write(self.biPlanes)
- file.write(self.biBitCount)
- file.write(self.biCompression)
- file.write(self.biSizeImage)
- file.write(self.biXPelsPerMeter)
- file.write(self.biYPelsPerMeter)
- file.write(self.biClrUsed)
- file.write(self.biClrImportant)
- # reconstruct pixels
- for bit in self.bits:
- file.write(bit)
- file.close()
至此,我们就已经完成了bmp图片的解析和生成。
随后,我们就可以根据我们读取出来的位图数据,来进行我们需要的操作,从而达到处理图像的功能。
第二步,实现图片的放大,缩小
参考博客:http://blog.csdn.net/zhangla1220/article/details/41014541
作为入门级小白,我起初对图像处理是一无所知,所以根本不知道应该如何去放大,缩小一张图片,经过查阅百度后,发现常用的算法有两种
- 最近邻内插值
- 双线性插值法
上述两个算法博客中都有相应思路,这里只简单提一下
最近邻是最简单的算法,基本思路也就是成比例放大后,将源图像坐标映射到目标图像,所以我选择用这种算法作为我的处理算法。
双线性法的处理效果比最近邻要好,但是相对复杂,所以我暂时没有实现该算法。
既然选择了最近邻算法,接下来要说明一些我们要获取的参数
第一步:我们首先得明确一个概念,每一行(指的是图片width * 每一个像素占的字节)的元素必须是4的倍数,否则计算机会无法识别该文件(这是我后面遇到的坑)
第二步:我们应该知道 源坐标和目的坐标的转换方式,我们假设 w是源图像宽,h是源图像高,W是目标图像宽,H是目标图像高,设(x,y)是源图像坐标,(X,Y)是目标图像坐标
所以我们可以得到下列公式
x = h / H * X;
y = w / W * Y
第三步:我再解析bmp图片时,是将图片位图数据存储到了一个一维数组里,所以我们要进行对应的x,y转换到我们的一维数组上(应该就是个行优先,列优先问题),要注意的是,一个像素是由多个字节组成的。
遇到问题:
因为我们计算机中图像是以左上角为原点,所以在进行源图像和目标图像映射的时候,我们要进行转换,否则就会不正常,这个是我解决了很久的问题,最后是查看了一个博文提到了这个问题,我才知道(博文地址找不到了。。。)
nni代码
- def resize(self, width, height):
- self.__nni(width, height)
- # nearest_neighbor Interpolation
- def __nni(self, width, height):
- # width must be Multiple of four
- if width % 4 != 0:
- width -= width % 4
- w_ratio = (self.height / height)
- h_ratio = (self.width / height)
- # new pixels array
- new_bits = [b''] * height * width * self.bit_count
- for row in range(0, height):
- for col in range(0, width):
- for channel in range(0, self.bit_count):
- old_r = round((row + 0.5) * w_ratio - 0.5) # 这里的 +0.5 -0.5 就是对坐标进行转换
- old_c = round((col + 0.5) * h_ratio - 0.5)
- new_index = row * width * self.bit_count + col * self.bit_count + channel
- old_index = old_r * self.width_step + old_c * self.bit_count + channel
- new_bits[new_index] = self.bits[old_index]
- self.bits = new_bits
- # reset header info
- self.bfSize = i_to_bytes(height * width * self.bit_count + 54, 4)
- self.biSizeImage = i_to_bytes(len(new_bits), 4)
- self.biWidth = i_to_bytes(width, 4)
- self.biHeight = i_to_bytes(height, 4)
实现的效果如下,将一个988*423的bmp文件缩小为8*8的
图2 源图像
图3 8*8
第三步,对图片进行灰度化
灰度化就很简单了,最简单的做法就是求R,G,B的平均值
这里采用了常见的灰度化公式,这是整数算法,减少了浮点计算,在一定程度上提高了速度
Gray = (R*299 + G*587 + B*114 + 500) / 1000
灰度化代码
- # put bmp graying
- def graying(self):
- new_bits = [b''] * self.width * self.height * self.bit_count
- for i in range(0, self.height):
- for j in range(0, self.width):
- s_index = i * self.width_step + j * self.bit_count
- target_index = i * self.width_step + j * self.bit_count
- r = int.from_bytes(self.bits[s_index + 2], 'little')
- g = int.from_bytes(self.bits[s_index + 1], 'little')
- b = int.from_bytes(self.bits[s_index], 'little')
- gray = (r * 30 + g * 59 + b * 11) / 100
- new_bits[target_index] = int(gray).to_bytes(1, 'little')
- new_bits[target_index + 1] = int(gray).to_bytes(1, 'little')
- new_bits[target_index + 2] = int(gray).to_bytes(1, 'little')
- self.bits = new_bits
实现效果:
图4 灰度化效果
第四步,对图片进行旋转
参考博文:
- http://blog.csdn.net/liyuan02/article/details/6750828
- http://blog.csdn.net/lkj345/article/details/50555870
第一篇博文完整分析了图片旋转的公式原理,说的非常清晰,有兴趣可以手写一遍,挺简单,而且清晰。
原理我就不说了,不在这献丑了~
来说说我遇到的问题
1、我不清楚如何要获得我们旋转后的宽和高,参考博文2中给出了公式,当时我并没有看懂,后面找到了一张图后,就明白了,这这里分享出这张图。
图5 旋转原理图
说明: w为原宽,h为原高 h'为旋转后的高 w'为旋转后的宽
从而可以看出
h' = h * cos + w * sin
w' = h * sin + w * cos
从而我们就得到了旋转后的宽和高,旋转后的图片所在的矩形区域是图中绿色部分。
我使用了上述博文中提到的反映射方法和最近邻插值法
代码如下:
- def rotate(self):
- self.__rotate(90)
- """
- reference: http://blog.csdn.net/liyuan02/article/details/6750828
- attention: in the loop, the x in real bmp is represent y, the y same too.
- """
- def __rotate(self, degree):
- cos_degree = math.cos(math.radians(degree))
- sin_degree = math.sin(math.radians(degree))
- h = math.ceil(self.height * cos_degree
- + self.width * sin_degree)
- w = math.ceil(self.height * sin_degree
- + self.width * cos_degree)
- h = abs(h)
- w = abs(w)
- if w % 4 != 0:
- w -= w % 4
- dx = -0.5 * w * cos_degree - 0.5 * h * sin_degree + 0.5 * self.width
- dy = 0.5 * w * sin_degree - 0.5 * h * cos_degree + 0.5 * self.height
- new_bits = [b''] * w * h * 3
- for x in range(0, h):
- for y in range(0, w):
- x0 = y * cos_degree + x * sin_degree + dx
- y0 = -y * sin_degree + x * cos_degree + dy
- src_index = round(y0) * self.width_step + round(x0) * self.bit_count
- dst_index = x * w * self.bit_count + y * self.bit_count
- if len(self.bits) - self.bit_count > src_index >= 0:
- new_bits[dst_index + 2] = self.bits[src_index + 2]
- new_bits[dst_index + 1] = self.bits[src_index + 1]
- new_bits[dst_index] = self.bits[src_index]
- else:
- new_bits[dst_index + 2] = i_to_bytes(255, 1)
- new_bits[dst_index + 1] = i_to_bytes(255, 1)
- new_bits[dst_index] = i_to_bytes(255, 1)
- self.bits = new_bits
- self.biWidth = i_to_bytes(w, 4)
- self.biHeight = i_to_bytes(h, 4)
要注意的问题是:
在for循环中的 x 是实际图像的 高
在for循环中的 y是实际图像中的宽
之前没注意该问题,直接套公式,结果一直有问题。
我目前只是初版,选择90°效果还行,其他度数的话,代码可能要进行改动。
我这里是默认逆时针旋转90°
效果如下:
图6 逆时针旋转90°
到此,预期实现功能结束。
因为我所在地区比较偏远,github访问不便,等会上传成功后,给出源码链接
github:
https://github.com/zyp461476492/SimpleBmpResolver.git
简单bmp图片处理工具——python实现的更多相关文章
- 简单的mongo小工具 python
#!/bin/python #coding=utf-8 ### eg : mgotool.py -i 127.0.0.1 -p 10001 -a xxxxx -u root -rc #import s ...
- Python中通过Image的open之后,去show结果打不开bmp图片,无法正常显示图片
在windows的cmd命令行下,使用Python的PIL库打开并显示一个jpg图片: ? 1 2 3 openedImg = Image.open(saveToFile); print " ...
- 提取bmp图片的颜色信息,可直接framebuffer显示(c版本与python版本)
稍微了解了下linux的framebuffer,这是一种很简单的显示接口,直接写入像素信息即可 配置好的内核,会有/dev/fbn 的接口,于是想能否提前生成一个文件,比如logo.fb,里面仅包含像 ...
- 最简单的基于FFmpeg的libswscale的示例附件:测试图片生成工具
===================================================== 最简单的基于FFmpeg的libswscale的示例系列文章列表: 最简单的基于FFmpeg ...
- Python开发程序:简单主机批量管理工具
题目:简单主机批量管理工具 需求: 主机分组 登录后显示主机分组,选择分组后查看主机列表 可批量执行命令.发送文件,结果实时返回 主机用户名密码可以不同 流程图: 说明: ### 作者介绍: * au ...
- Python简单主机批量管理工具
一.程序介绍 需求: 简单主机批量管理工具 需求: 1.主机分组 2.主机信息使用配置文件 3.可批量执行命令.发送文件,结果实时返回 4.主机用户名密码.端口可以不同 5.执行远程命令使用param ...
- UEditor之实现配置简单的图片上传示例
UEditor之实现配置简单的图片上传示例 原创 2016年06月11日 18:27:31 开心一笑 下班后,阿华到楼下小超市买毛巾,刚买完出来,就遇到同一办公楼里另一家公司的阿菲,之前与她远远的有过 ...
- Ubuntu下几个命令行方式使用的图片浏览工具
想找几个Ubuntu下可以以命令行方式使用的图片浏览工具. Google了一些资料,找到下面几个web: 1.pho:轻巧的命令行图片查看器 其中介绍了工具pho,其功能特点,见下面的转帖内容: ph ...
- MFC对话框显示BMP图片
1.MFC对话框显示BMP图片我们先从简单的开始吧.先分一个类: (一) 非动态显示图片(即图片先通过资源管理器载入,有一个固定ID) (二) 动态载入图片(即只需要在程序中指定图片的路径即可载入) ...
随机推荐
- 关于在网页拼接时出现:提示Uncaught SyntaxError: missing ) after argument list;错误的原因分析
1:网页拼接不完善,可能哪里漏了:),},</XX>...等 2:如果有动态数据写入的话,请注意转义动态数据,如图(是转义后的内容,不会报错): 在写方法时:onclick中,注意单双引号 ...
- ubuntu12.04.5安装openssh-server所引发的血案
刚安装好的ubuntu12.04.5在安装openssh-server之后,安装其他软件都安装不了,如下: root@ubuntu:/home/lancer/software/ssh# apt-get ...
- 细谈UITabBarController
1.简述 UITabBarController和UINavigationController类似,UITabBarController也可以轻松地管理多个控制器,轻松完成控制器之间的切换,UITabB ...
- Linux轻松使用vim
VIM命令---Vi IMproved, a programmers text editor文本编辑 1>gedit 图形文本编辑工具 2>vim 字符界面的编辑工具 写脚本 ...
- T_SQL编程赋值、分支语句、循环
咱们在C#中会常用到赋值.循环.分支语句什么的 今天咱们来看下当初在C#用到的一点东西放到SQL中是怎么使用的 创建变量 在C#中创建一个值类型变量很简单 int a:这就可以了 SQL: decla ...
- java源码剖析: 对象内存布局、JVM锁以及优化
一.目录 1.启蒙知识预热:CAS原理+JVM对象头内存存储结构 2.JVM中锁优化:锁粗化.锁消除.偏向锁.轻量级锁.自旋锁. 3.总结:偏向锁.轻量级锁,重量级锁的优缺点. 二.启蒙知识预热 开启 ...
- 安卓Native和H5页面进行交互
安卓Native和H5页面进行交互 1.H5页面调用安卓Native界面 1)通过给webView添加JsInterface,安卓提供接口,让H5来进行调用 a)安卓写一个类,里面的方法需要用通 ...
- Python中使用Mysql(安装篇)
准备工作 import MySQLdb Linux系统自带了Python,但并不是都有这个包,至少我每次拿到一台全新的服务器时候,都发现没有装这个包. 这个东西的下载地址是 http://source ...
- java中使用fastjson、jackson、json-lib解析JSON-------------------妈妈再也不用担心JSON解析
1.fastjson引入包<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjso ...
- 【复制粘贴】silverlight网格选择日期和时间
今天有个功能需要在网格中输入时间,本来是用文本框的,但是客户说不知道格式,要求用选择的形式,好像silverlight又没有能选择时间的控件,谷歌下得到一个曲线救国的答案,记录下 <sdk:Da ...