本文目录导读:

我来介绍BMP无损格式图像的读写方法,包括Python和C++两种实现。
BMP文件格式概述
BMP文件结构:
[BMP文件头] [DIB头] [调色板(可选)] [像素数据]
Python实现
使用PIL/Pillow(推荐)
from PIL import Image
import numpy as np
# 读取BMP
img = Image.open('image.bmp')
print(f"格式: {img.format}")
print(f"大小: {img.size}")
print(f"模式: {img.mode}") # RGB, RGBA, L等
# 转换为numpy数组
pixels = np.array(img)
print(f"像素数组形状: {pixels.shape}")
# 写入BMP
img.save('output.bmp', 'BMP')
# 处理像素
def process_bmp(input_path, output_path):
# 读取
img = Image.open(input_path)
pixels = np.array(img)
# 示例:反转颜色
processed = 255 - pixels
# 保存
result = Image.fromarray(processed.astype('uint8'))
result.save(output_path, 'BMP')
# 使用
process_bmp('input.bmp', 'output.bmp')
不使用库的纯Python实现
import struct
class BMPReader:
def __init__(self):
self.file_header = {}
self.info_header = {}
self.pixels = None
self.width = 0
self.height = 0
self.padding = 0
def read(self, filename):
with open(filename, 'rb') as f:
# 读取文件头 (14字节)
self.file_header['bfType'] = f.read(2)
self.file_header['bfSize'] = struct.unpack('<I', f.read(4))[0]
self.file_header['bfReserved1'] = struct.unpack('<H', f.read(2))[0]
self.file_header['bfReserved2'] = struct.unpack('<H', f.read(2))[0]
self.file_header['bfOffBits'] = struct.unpack('<I', f.read(4))[0]
# 读取信息头
self.info_header['biSize'] = struct.unpack('<I', f.read(4))[0]
self.width = struct.unpack('<i', f.read(4))[0]
self.height = struct.unpack('<i', f.read(4))[0]
self.info_header['biPlanes'] = struct.unpack('<H', f.read(2))[0]
self.info_header['biBitCount'] = struct.unpack('<H', f.read(2))[0]
self.info_header['biCompression'] = struct.unpack('<I', f.read(4))[0]
self.info_header['biSizeImage'] = struct.unpack('<I', f.read(4))[0]
self.info_header['biXPelsPerMeter'] = struct.unpack('<i', f.read(4))[0]
self.info_header['biYPelsPerMeter'] = struct.unpack('<i', f.read(4))[0]
self.info_header['biClrUsed'] = struct.unpack('<I', f.read(4))[0]
self.info_header['biClrImportant'] = struct.unpack('<I', f.read(4))[0]
# 计算每行字节数和填充
bit_count = self.info_header['biBitCount']
row_size = (self.width * bit_count + 31) // 32 * 4
self.padding = row_size - (self.width * bit_count // 8)
# 移动到像素数据
f.seek(self.file_header['bfOffBits'])
# 读取像素数据 (假设24位BMP)
if bit_count == 24:
self.pixels = []
for y in range(abs(self.height)):
row = []
for x in range(self.width):
# BMP存储顺序是BGR
b = struct.unpack('B', f.read(1))[0]
g = struct.unpack('B', f.read(1))[0]
r = struct.unpack('B', f.read(1))[0]
row.append((r, g, b))
# 跳过填充字节
if self.padding > 0:
f.read(self.padding)
self.pixels.append(row)
# 如果高度为正,图像是倒置的
if self.height > 0:
self.pixels.reverse()
def write(self, filename):
with open(filename, 'wb') as f:
# 计算文件大小
height = abs(self.height)
row_size = (self.width * 24 + 31) // 32 * 4
pixel_data_size = row_size * height
file_size = 54 + pixel_data_size # 14 + 40 = 54
# 写入文件头
f.write(b'BM')
f.write(struct.pack('<I', file_size))
f.write(struct.pack('<H', 0)) # 保留
f.write(struct.pack('<H', 0)) # 保留
f.write(struct.pack('<I', 54)) # 数据偏移
# 写入信息头
f.write(struct.pack('<I', 40)) # 信息头大小
f.write(struct.pack('<i', self.width))
f.write(struct.pack('<i', self.height))
f.write(struct.pack('<H', 1)) # 颜色平面数
f.write(struct.pack('<H', 24)) # 每像素位数
f.write(struct.pack('<I', 0)) # 压缩方式
f.write(struct.pack('<I', pixel_data_size))
f.write(struct.pack('<i', 2835)) # 水平分辨率
f.write(struct.pack('<i', 2835)) # 垂直分辨率
f.write(struct.pack('<I', 0)) # 颜色数
f.write(struct.pack('<I', 0)) # 重要颜色数
# 写入像素数据
pixels = self.pixels.copy()
if self.height > 0:
pixels.reverse() # BMP存储是倒置的
for row in pixels:
for r, g, b in row:
f.write(struct.pack('B', b))
f.write(struct.pack('B', g))
f.write(struct.pack('B', r))
# 填充字节
for _ in range(self.padding):
f.write(struct.pack('B', 0))
# 使用示例
reader = BMPReader()
reader.read('input.bmp')
print(f"宽度: {reader.width}, 高度: {reader.height}")
# 修改像素(示例:添加红色滤镜)
for y in range(len(reader.pixels)):
for x in range(len(reader.pixels[y])):
r, g, b = reader.pixels[y][x]
reader.pixels[y][x] = (min(r + 50, 255), g, b)
reader.write('output.bmp')
C++实现
使用C++标准库
#include <iostream>
#include <fstream>
#include <vector>
#include <cstdint>
#pragma pack(push, 1)
struct BMPFileHeader {
uint16_t bfType = 0x4D42; // 'BM'
uint32_t bfSize = 0; // 文件大小
uint16_t bfReserved1 = 0;
uint16_t bfReserved2 = 0;
uint32_t bfOffBits = 54; // 数据偏移
};
struct BMPInfoHeader {
uint32_t biSize = 40; // 信息头大小
int32_t biWidth = 0;
int32_t biHeight = 0;
uint16_t biPlanes = 1;
uint16_t biBitCount = 24; // 每像素位数
uint32_t biCompression = 0;
uint32_t biSizeImage = 0;
int32_t biXPelsPerMeter = 2835;
int32_t biYPelsPerMeter = 2835;
uint32_t biClrUsed = 0;
uint32_t biClrImportant = 0;
};
#pragma pack(pop)
struct Pixel {
uint8_t r, g, b;
};
class BMP {
public:
BMPFileHeader fileHeader;
BMPInfoHeader infoHeader;
std::vector<std::vector<Pixel>> pixels;
int width, height;
int padding;
BMP() : width(0), height(0), padding(0) {}
bool read(const std::string& filename) {
std::ifstream file(filename, std::ios::binary);
if (!file) {
std::cerr << "无法打开文件: " << filename << std::endl;
return false;
}
// 读取文件头
file.read(reinterpret_cast<char*>(&fileHeader), sizeof(BMPFileHeader));
if (fileHeader.bfType != 0x4D42) {
std::cerr << "不是有效的BMP文件" << std::endl;
return false;
}
// 读取信息头
file.read(reinterpret_cast<char*>(&infoHeader), sizeof(BMPInfoHeader));
width = infoHeader.biWidth;
height = abs(infoHeader.biHeight);
// 计算填充
int rowSize = ((width * infoHeader.biBitCount + 31) / 32) * 4;
padding = rowSize - (width * 3); // 24位图像
// 移动到像素数据
file.seekg(fileHeader.bfOffBits);
// 读取像素数据
pixels.resize(height);
for (int y = 0; y < height; y++) {
pixels[y].resize(width);
for (int x = 0; x < width; x++) {
uint8_t b, g, r;
file.read(reinterpret_cast<char*>(&b), 1);
file.read(reinterpret_cast<char*>(&g), 1);
file.read(reinterpret_cast<char*>(&r), 1);
pixels[y][x] = {r, g, b};
}
// 跳过填充字节
if (padding > 0) {
file.seekg(padding, std::ios::cur);
}
}
// 如果高度为正,图像是倒置的
if (infoHeader.biHeight > 0) {
std::reverse(pixels.begin(), pixels.end());
}
file.close();
return true;
}
bool write(const std::string& filename) {
std::ofstream file(filename, std::ios::binary);
if (!file) {
std::cerr << "无法创建文件: " << filename << std::endl;
return false;
}
// 计算文件大小
int rowSize = ((width * infoHeader.biBitCount + 31) / 32) * 4;
int pixelDataSize = rowSize * height;
fileHeader.bfSize = 54 + pixelDataSize;
infoHeader.biSizeImage = pixelDataSize;
// 写入文件头
file.write(reinterpret_cast<char*>(&fileHeader), sizeof(BMPFileHeader));
// 写入信息头
infoHeader.biHeight = -height; // 顶向下存储
file.write(reinterpret_cast<char*>(&infoHeader), sizeof(BMPInfoHeader));
// 写入像素数据
std::vector<Pixel> reversedPixels = pixels;
std::reverse(reversedPixels.begin(), reversedPixels.end());
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
file.put(reversedPixels[y][x].b);
file.put(reversedPixels[y][x].g);
file.put(reversedPixels[y][x].r);
}
// 写入填充字节
for (int p = 0; p < padding; p++) {
file.put(0);
}
}
file.close();
return true;
}
void process() {
// 示例操作:反转颜色
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
pixels[y][x].r = 255 - pixels[y][x].r;
pixels[y][x].g = 255 - pixels[y][x].g;
pixels[y][x].b = 255 - pixels[y][x].b;
}
}
}
};
// 使用示例
int main() {
BMP bmp;
if (bmp.read("input.bmp")) {
std::cout << "宽度: " << bmp.width << ", 高度: " << bmp.height << std::endl;
bmp.process(); // 处理图像
if (bmp.write("output.bmp")) {
std::cout << "保存成功" << std::endl;
}
}
return 0;
}
关键注意事项
BMP格式特点
- 字节顺序:BMP使用小端序(Little-Endian)
- 颜色顺序:存储顺序是BGR(蓝、绿、红)
- 行对齐:每行字节数必须是4的倍数,不足的用0填充
- 图像方向:BMP通常是从下到上存储
常见问题
# 1. 调色板处理(8位BMP)
def read_8bit_bmp(filename):
# 8位BMP需要读取调色板
pass
# 2. 透明通道处理
def handle_alpha(img):
# RGBA模式
if img.mode == 'RGBA':
# 使用白色背景
background = Image.new('RGB', img.size, (255, 255, 255))
background.paste(img, mask=img.split()[3])
return background
return img
# 3. 大文件处理
def process_large_bmp(filename):
# 使用迭代器逐行处理
with Image.open(filename) as img:
width, height = img.size
for y in range(height):
pixels = [] # 处理当前行
for x in range(width):
pixel = img.getpixel((x, y))
# 处理像素
pixels.append(process_pixel(pixel))
性能优化
import numpy as np
# 使用numpy加速
def fast_process_bmp(input_path, output_path):
img = Image.open(input_path)
pixels = np.array(img)
# 矢量操作(比循环快很多)
# 改为灰度
gray = np.dot(pixels[..., :3], [0.299, 0.587, 0.114])
# 保存结果
result = Image.fromarray(gray.astype('uint8'), mode='L')
result = result.convert('RGB') # BMP通常使用RGB
result.save(output_path, 'BMP')
实用工具函数
def get_bmp_info(filename):
"""获取BMP文件信息"""
with Image.open(filename) as img:
info = {
'file': filename,
'format': img.format,
'mode': img.mode,
'size': img.size,
'info': img.info
}
# 添加文件大小信息
import os
info['file_size'] = os.path.getsize(filename)
return info
def convert_bmp_to_rgb24(bmp_path, output_path):
"""转换为标准24位BMP"""
img = Image.open(bmp_path)
if img.mode != 'RGB':
img = img.convert('RGB')
img.save(output_path, 'BMP')
def create_test_bmp(width, height, color=(255, 0, 0)):
"""创建测试BMP图像"""
img = Image.new('RGB', (width, height), color)
img.save('test.bmp', 'BMP')
print(f"创建测试图像: {width}x{height}")
# 批量处理
def batch_convert_to_bmp(input_dir, output_dir):
"""批量转换图像为BMP格式"""
import os
from pathlib import Path
os.makedirs(output_dir, exist_ok=True)
for file in Path(input_dir).glob('*'):
if file.suffix.lower() in ['.jpg', '.png', '.gif']:
try:
img = Image.open(file)
# 确保RGB模式
if img.mode != 'RGB':
img = img.convert('RGB')
output_path = Path(output_dir) / (file.stem + '.bmp')
img.save(output_path, 'BMP')
print(f"转换: {file.name} -> {output_path.name}")
except Exception as e:
print(f"转换失败 {file.name}: {e}")
代码提供了完整的BMP图像读写解决方案,适用于不同场景和需求。