Python does not use third-party libraries to implement bmp image processing

Station B: Follow-up! ! ! _bilibili
CSDN: Python does not use third-party libraries to implement bmp image processing_Closed after a hundred years - CSDN blog
Github: closed
Public account: closed after a hundred years

1. Background

There are many ways to load bmp format images. For python, we have a lot of choices. For example, using the powerful functions provided by the following third-party libraries, we can easily implement functions such as loading, processing, and saving images, for example:

  • Pillow
  • opencv-python
  • skimage

The first two libraries are very commonly used third-party libraries for image processing, but sometimes we are required not to use third-party libraries for processing. At this time, processing bmp images requires us to have a certain understanding of the storage format of bmp images, so we only Images can be read as binary files. Here I mainly refer to some articles of this great guy . I will follow the steps below to explain how to use the built-in functions of python to realize the bmp image reading, resize, rotate and save functions:

2. Implementation of specific functions

2.1 Read bmp image

Reading bmp images is mainly divided into two steps. One is to read the information header of the image, which is generally composed of 54 bytes, and then read the real image data behind: The simplest method is as follows:

  with open(filename, "rb") as file:
  	   # 读取图像的信息头
       header = file.read(54)
       # 读取图像数据
       image_data = file.read()
       # 转化为十进制数字像素列表
       image = list(image_data)

In order to flexibly complete the conversion of int and bytes, we first define the following auxiliary functions, int2byte -> i2b and byte2int -> b2i:

 def i2b(number, length, byteorder='little'):
    return number.to_bytes(length, byteorder)


def b2i(mbytes, byteorder='little'):
    return int.from_bytes(mbytes, byteorder)

Reading in this way will be very simple. If you don't resize, the header does not need to be changed; but when your image length and width and other information change, when saving the bmp, you need to modify the header information inside to make it normal. Save, so we need to parse the header in detail, as follows:

with open(filename, "rb") as file:   
	 # BmpFileHeader
	 bfType = file.read(2)
	 bfSize = file.read(4)
	 bfReserved1 = file.read(2)
	 bfReserved2 = file.read(2)
	 bfOffBits = file.read(4)
	 # BmpStructHeader
	 biSize = file.read(4)
	 biWidth = file.read(4)
	 biHeight = file.read(4)
	 biPlanes = file.read(2)
	 biBitCount = file.read(2)
	 # pixel size
	 biCompression = file.read(4)
	 biSizeImage = file.read(4)
	 biXPelsPerMeter = file.read(4)
	 biYPelsPerMeter = file.read(4)
	 biClrUsed = file.read(4)
	 biClrImportant = file.read(4)
	
	 print("bfType: ", b2i(bfType))
	 print("bfSize: ", b2i(bfSize))
	 print("biSize: ", b2i(biSize))
	 print("biWidth: ", b2i(biWidth))
	 print("biHeight: ", b2i(biHeight))
	 print("biBitCount: ", b2i(biBitCount))
	
	 # 读取图像数据
	 image_data = file.read()
	 # 转化为列表
	 image = list(image_data)
bfType:  19778
bfSize:  5248854
biSize:  40
biWidth:  1620
biHeight:  1080
biBitCount:  24

For a detailed analysis of the bmp image information header, you can read this boss's blog . In fact, the bmp header mainly contains two types of data: file information header, image structure information and palette information. What we mainly care about is the width, height and channel information of the image, which are biWidth, biHeight and biBitCount.

2.2 resize function

The function of resize is generally to complete the color correspondence by mapping the pixels of the new image to the original image. However, since there are non-integer pixel positions in the conversion process, if you want to achieve better results, you can use some images Interpolation methods, such as nearest neighbor interpolation, bilinear interpolation and bicubic interpolation, make the size of the image after resize more reasonable. Here we just implement the simplest mapping relationship, the code is as follows:

def resize_image(self, new_w, new_h):
    width, height = b2i(self.biWidth), b2i(self.biHeight)
    new_width, new_height = new_w, new_h
    scale_w, scale_h = new_width / width, new_height / height

    # 创建新的图像数据列表
    new_image = []
    # 遍历每一行
    for h in range(new_height):
        # 计算原始图像中对应的行
        original_row = int(h / scale_h)
        # 遍历每一列
        for w in range(new_width):
            # 计算原始图像中对应的列
            original_col = int(w / scale_w)
            # 获取对应的像素值
            pixel = self.img[
                    (original_row * width + original_col) * 3: (original_row * width + original_col) * 3 + 3]
            # 添加到新的图像数据列表中
            new_image += pixel
    self.img = new_image
    # 修改图像的header
    self.bfSize = i2b(new_height * new_width * b2i(self.biBitCount) // 8 + 54, 4)
    self.biSizeImage = i2b(len(new_image), 4)
    self.biWidth = i2b(new_width, 4)
    self.biHeight = i2b(new_height, 4)
    return new_image

2.3 rotate function

Rotate is actually very simple. It is just a fun function. You can implement some functions such as local scale of the image, morphological operations, sharpening, etc. by yourself. Here, rotate implements the function of rotating the angle clockwise with the center point of the image as the center. You can write a vector correspondence between the points before and after the rotation to derive the formula. I will not go into details here. The code is as follows:

def rotate_image(self, angle):
    width, height = b2i(self.biWidth), b2i(self.biHeight)
    # 创建新的图像数据列表
    new_image = [0] * (width * height * 3)
    # 遍历每一行
    for h in range(height):
        # 遍历每一列
        for w in range(width):
            # 计算旋转后的行和列
            new_row = int((h - height / 2) * math.cos(angle) - (w - width / 2) * math.sin(angle) + height / 2)
            new_col = int((h - height / 2) * math.sin(angle) + (w - width / 2) * math.cos(angle) + width / 2)
            # 判断是否在新的图像范围内
            if new_row >= 0 and new_row < height and new_col >= 0 and new_col < width:
                # 获取对应的像素值
                pixel = self.img[(h * width + w) * 3: (h * width + w) * 3 + 3]
                # 添加到新的图像数据列表中
                new_image[(new_row * width + new_col) * 3: (new_row * width + new_col) * 3 + 3] = pixel
    self.img = new_image

2.4 Save bmp image

With the changed image, of course we need to save it for easy viewing. If you change the structure or palette information of the image, then you need to modify the specific information in the header read in 2.1. For example, if you resize the image, then you need to change the following information, don't forget it.

 # 修改图像的header
 self.bfSize = i2b(new_height * new_width * b2i(self.biBitCount) // 8 + 54, 4)
 self.biSizeImage = i2b(len(new_image), 4)
 self.biWidth = i2b(new_width, 4)
 self.biHeight = i2b(new_height, 4)

Other contents in the header information of the original image do not need to be changed. We open a new file, write the header in binary format, and then write the new image:

with open(filename, 'wb') as file:
    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
    file.write(bytes(self.img))
    file.close()

3. Complete code

Here is the code that can be run directly:

import math
import sys

def i2b(number, length, byteorder='little'):
    return number.to_bytes(length, byteorder)


def b2i(mbytes, byteorder='little'):
    return int.from_bytes(mbytes, byteorder)


class BMPReader:

    def __init__(self):
        self.img = None

    def read_bmp(self, filename):
        try:
            with open(filename, "rb") as file:
                # header = file.read(54)
                # 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.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)

                print("bfType: ", b2i(self.bfType))
                print("bfSize: ", b2i(self.bfSize))
                print("biSize: ", b2i(self.biSize))
                print("biWidth: ", b2i(self.biWidth))
                print("biHeight: ", b2i(self.biHeight))
                print("biBitCount: ", b2i(self.biBitCount))

                # 读取图像数据
                image_data = file.read()
                # 转化为列表
                image = list(image_data)

                self.img = image
                return 0
        except  Exception as e:
            print(e)
            return -1

    def save_bmp(self, filename):
        # 读取文件头
        with open(filename, 'wb') as file:
            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
            file.write(bytes(self.img))
            file.close()

    def resize_image(self, new_w, new_h):
        width, height = b2i(self.biWidth), b2i(self.biHeight)
        new_width, new_height = new_w, new_h
        scale_w, scale_h = new_width / width, new_height / height

        # 创建新的图像数据列表
        new_image = []
        # 遍历每一行
        for h in range(new_height):
            # 计算原始图像中对应的行
            original_row = int(h / scale_h)
            # 遍历每一列
            for w in range(new_width):
                # 计算原始图像中对应的列
                original_col = int(w / scale_w)
                # 获取对应的像素值
                pixel = self.img[
                        (original_row * width + original_col) * 3: (original_row * width + original_col) * 3 + 3]
                # 添加到新的图像数据列表中
                new_image += pixel
        self.img = new_image
        self.bfSize = i2b(new_height * new_width * b2i(self.biBitCount) // 8 + 54, 4)
        self.biSizeImage = i2b(len(new_image), 4)
        self.biWidth = i2b(new_width, 4)
        self.biHeight = i2b(new_height, 4)
        return new_image

    def rotate_image(self, angle):
        width, height = b2i(self.biWidth), b2i(self.biHeight)
        # 创建新的图像数据列表
        new_image = [0] * (width * height * 3)
        # 遍历每一行
        for h in range(height):
            # 遍历每一列
            for w in range(width):
                # 计算旋转后的行和列
                new_row = int((h - height / 2) * math.cos(angle) - (w - width / 2) * math.sin(angle) + height / 2)
                new_col = int((h - height / 2) * math.sin(angle) + (w - width / 2) * math.cos(angle) + width / 2)
                # 判断是否在新的图像范围内
                if new_row >= 0 and new_row < height and new_col >= 0 and new_col < width:
                    # 获取对应的像素值
                    pixel = self.img[(h * width + w) * 3: (h * width + w) * 3 + 3]
                    # 添加到新的图像数据列表中
                    new_image[(new_row * width + new_col) * 3: (new_row * width + new_col) * 3 + 3] = pixel
        self.img = new_image



if __name__ == '__main__':
    br = BMPReader()
    br.read_bmp('ipc104.bmp')
    br.resize_image(new_w=800, new_h=600)
    br.rotate_image(90)
    br.save_bmp('save.bmp')

Guess you like

Origin blog.csdn.net/qq_36306288/article/details/128692110
Recommended