简单bmp图片处理工具——python实现

预备实现功能:

  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类至少应该有三个信息:

  1. 文件信息头
  2. 位图信息头
  3. 位图数据

在拥有了上述信息后,下面的事情就变得非常简单,只需要去读取文件,并按照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

作为入门级小白,我起初对图像处理是一无所知,所以根本不知道应该如何去放大,缩小一张图片,经过查阅百度后,发现常用的算法有两种

  1. 最近邻内插值
  2. 双线性插值法

上述两个算法博客中都有相应思路,这里只简单提一下

最近邻是最简单的算法,基本思路也就是成比例放大后,将源图像坐标映射到目标图像,所以我选择用这种算法作为我的处理算法。

双线性法的处理效果比最近邻要好,但是相对复杂,所以我暂时没有实现该算法。

既然选择了最近邻算法,接下来要说明一些我们要获取的参数

第一步:我们首先得明确一个概念,每一行(指的是图片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 灰度化效果

第四步,对图片进行旋转

参考博文:

  1. http://blog.csdn.net/liyuan02/article/details/6750828
  2. 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

时间: 2024-11-05 20:39:29

简单bmp图片处理工具——python实现的相关文章

最简单的基于FFmpeg的libswscale的示例附件:测试图片生成工具

本文记录一个自己写的简单的测试图片生成工具:simplest_pic_gen.该工具可以生成视频测试时候常用的RGB/YUV格式的测试图片.下面简单介绍一下这些测试图片的生成函数.这里有一点需要注意:查看生成的图片需要使用RGB/YUV播放器. 灰阶测试图 亮度取值为16-235的灰阶测试图下面这张图是一张灰阶测试图的示例.这张图的分辨率是1280x720,像素格式是YUV420P,亮度的取值范围是16-235,一共包含了10级的灰度.最左边的灰度竖条的YUV取值为(16,128,128),最右

Python开发程序:简单主机批量管理工具

题目:简单主机批量管理工具 需求: 主机分组 登录后显示主机分组,选择分组后查看主机列表 可批量执行命令.发送文件,结果实时返回 主机用户名密码可以不同 流程图: 说明: ### 作者介绍: * author:lzl ### 博客地址: * http://www.cnblogs.com/lianzhilei/p/5881434.html ### 功能实现 题目:简单主机批量管理工具 需求: 主机分组 登录后显示主机分组,选择分组后查看主机列表 可批量执行命令.发送文件,结果实时返回 主机用户名密

Caffe图片特征提取(Python/C++)

Caffe图片特征提取(Python/C++) 1.Caffe特征提取(C++实现) Caffe框架提供了相应的Tools(build/tools/extract_features.bin)工具extract features,官方教程,使用方法如下: extract_features.bin xxx.caffemodel xxxx.prototxt layer-name output-path mini-batches db-style xxx.caffemodel:已训练好的模型参数 xxx

提取bmp图片的颜色信息,可直接framebuffer显示

稍微了解了下linux的framebuffer,这是一种很简单的显示接口,直接写入像素信息即可 配置好的内核,会有/dev/fbn 的接口,于是想能否提前生成一个文件,比如logo.fb,里面仅包含像素信息,从而可以直接送入framebuffer显示 搜索了一下,有不少文章介绍,如何解析bmp图片并送给framebuffer显示,但没有找到预处理工具,都是直接处理完就送入framebuffer 于是参考了一篇文章,改动了下代码,将直接送入framebuffer变成写到一个文件中. 原代码地址为

简单主机批量管理工具

题目:简单主机批量管理工具 需求: 主机分组 登录后显示主机分组,选择分组后查看主机列表 可批量执行命令.发送文件,结果实时返回 主机用户名密码可以不同 流程图: 说明: ### 作者介绍: * author:lzl ### 博客地址: * http://www.cnblogs.com/lianzhilei/p/5881434.html ### 功能实现 题目:简单主机批量管理工具 需求: 主机分组 登录后显示主机分组,选择分组后查看主机列表 可批量执行命令.发送文件,结果实时返回 主机用户名密

Qt项目实战2:简单的图片查看器(1)

在博文http://www.cnblogs.com/hancq/p/5817108.html中介绍了使用空的Qt项目创建带有菜单栏.工具栏的界面. 这里,使用一个简单的图片查看器项目,来熟悉一下Qt的图片显示和基本操作. 该项目分为两部分: (1)实现图片的打开.关闭.居中显示.上一张/下一张切换 (2)实现图片的放大.缩小.左旋.右旋.另存为等操作 需要用的Qt类: QFileDialog QImage QPixmap QFileInfo 使用空的Qt项目创建带有菜单栏和工具栏的界面的操作参考

第一次编写简单的中间件测试工具(1) - 记一次新员工训练营

去年11月,我加入了N记,紧接着进入新员工训练营. 开始一次简单的中间件测试工具编写任务. 这次训练营体验给我的感觉就是:大公司不愧是大公司,这回我终于可以安心学点核心技术了. 任务: 这个训练营有两个任务,一是熟悉这边的敏捷开发流程:二是在训练营里做一定的编码,用python编写一个测试工具(桩,stub). 我们要做的这个工具,是用来测试我们一种通信设备(B)上运行的程序(某种中间件),这个工具模拟另一种通信设备(A),发送一些按特定协议编码的消息给另一种通信设备B,并能反编码设备B返回的消

图片捕获工具driftnet

driftnet是一款简单而使用的图片捕获工具,可以很方便的在网络数据包中抓取图片.该工具可以实时和离线捕获指定数据包中是图片,当然在kali里是有的. 在我之前的一篇博文<kali下搭建WiFi钓鱼热点>中用到了一下,现在做一个简单的小结,算是备忘靶. 语法: driftnet   [options]   [filter code] 主要参数: -b               捕获到新的图片时发出嘟嘟声 -i  interface     选择监听接口 -f  file   读取一个指定p

常用科学作图与图片处理工具

在平时的学习与科研工作中,少不了画画示意图.处理一下实验和仿真数据并绘制曲线.也曾用过不少的软件,大体分为设计绘图.示意图绘制.数学绘图.转换工具四大类.将其总结在下面,以便今后专注于几个特定的工具,深入挖掘.熟能生巧.其中,若以制作高精度和矢量图作为第一要务,再辅之以方便易用.可以加入LaTeX符号,则符合这些条件的软件名以红色标出,加删除线者弃之不用. 设计绘图 GIMP对于普通用户来说,用于替代Photoshop完全没有问题. Inkscape用于绘制SVG矢量图,当然也可以将其导出成其它