本文目录导读:

我来介绍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图像读写解决方案,适用于不同场景和需求。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播