You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Python中不依赖外部库裁剪BMP并保存?代码问题排查

解决BMP图像裁剪变形问题并替换PIL实现手动保存

我来帮你搞定这两个核心问题:图像变形和移除PIL依赖手动保存BMP文件。

一、修复图像变形问题

你的代码里有两个关键错误直接导致了图像变形:

1. 列索引判断逻辑错误

在repack_sub_pixels函数中,判断水平裁剪范围时错误地用了origin_y(垂直起始位置),而不是origin_x(水平起始位置)。因为每个像素占3字节,正确的列索引范围应该是origin_x * 3到(origin_x + width) * 3。

2. 未处理BMP行对齐填充字节

BMP格式强制要求每行的字节数必须是4的倍数,不足的部分会用空字节(通常是\x00)填充。你的read_rows函数没有跳过这些填充字节,导致读取的行数据混入了无效填充,打乱了像素排列顺序。

二、替换PIL,手动实现BMP文件保存

我们需要严格遵循BMP格式规范,手动构建文件头、信息头,处理像素数据的行对齐,最后按BMP要求的"从下到上"顺序写入像素数据。

修改后的完整代码

import numpy as np
import struct

def read_rows(path, origin_x, origin_y, height, width):
    image_file = open(path, "rb")
    # 读取BMP原图的宽度和高度
    image_file.seek(18, 0)
    bmp_w = struct.unpack('I', image_file.read(4))[0]
    bmp_h = struct.unpack('I', image_file.read(4))[0]
    
    # 检查裁剪范围是否超出原图边界
    if origin_x + width > bmp_w or origin_y + height > bmp_h:
        print(f'Dimensions are outside the figure size, choose between {bmp_w}x{bmp_h} pixels')
        return None
    
    # 计算每行的实际字节数(含对齐填充)
    row_pixel_bytes = bmp_w * 3
    padding = (4 - (row_pixel_bytes % 4)) % 4
    total_row_bytes = row_pixel_bytes + padding
    
    image_file.seek(54)  # 跳过14字节文件头+40字节信息头,直接定位到像素数据
    rows = []
    
    for _ in range(bmp_h):
        row = []
        # 读取一行的有效像素(BMP存储顺序是BGR)
        for _ in range(bmp_w):
            b = ord(image_file.read(1))
            g = ord(image_file.read(1))
            r = ord(image_file.read(1))
            row.append(b)
            row.append(g)
            row.append(r)
        # 跳过当前行的填充字节
        image_file.read(padding)
        # 插入到列表开头,因为BMP像素是从下到上存储的
        rows.insert(0, row)
    
    image_file.close()
    return rows

def repack_sub_pixels(rows, origin_x, origin_y, height, width):
    print("Repacking pixels...")
    sub_pixels = []
    # 遍历目标垂直范围的行
    for jdx in range(origin_y, origin_y + height):
        row = rows[jdx]
        # 截取目标水平范围的像素字节
        start_idx = origin_x * 3
        end_idx = (origin_x + width) * 3
        sub_pixels.extend(row[start_idx:end_idx])
    print(f"Packed {len(sub_pixels)} sub-pixels.")
    return sub_pixels

def save_bmp(output_name, pixels, width, height):
    # 计算裁剪后图像的行填充字节
    row_pixel_bytes = width * 3
    padding = (4 - (row_pixel_bytes % 4)) % 4
    total_pixel_size = len(pixels) + (height * padding)
    
    # 构建BMP文件头(14字节)
    file_header = struct.pack('<2sIHHIH',
                              b'BM',  # 文件标识
                              54 + total_pixel_size,  # 文件总大小
                              0, 0,  # 保留字段
                              54)  # 像素数据起始偏移量
    
    # 构建BMP信息头(40字节,BITMAPINFOHEADER)
    info_header = struct.pack('<IIIHHIIIIII',
                              40,  # 信息头大小
                              width,  # 图像宽度
                              height,  # 图像高度
                              1,  # 颜色平面数
                              24,  # 每像素位数(24位真彩色)
                              0,  # 无压缩
                              total_pixel_size,  # 像素数据总大小(含填充)
                              0, 0,  # 默认分辨率
                              0, 0)  # 24位无需颜色表
    
    # 处理像素行对齐,生成最终像素数据(BMP要求从下到上存储)
    pixel_data = b''
    padding_bytes = b'\x00' * padding
    for i in reversed(range(height)):
        start = i * row_pixel_bytes
        end = start + row_pixel_bytes
        row_bytes = bytes(pixels[start:end])
        pixel_data += row_bytes + padding_bytes
    
    # 写入文件
    with open(output_name, 'wb') as f:
        f.write(file_header)
        f.write(info_header)
        f.write(pixel_data)
    print(f'File saved as {output_name}')

def crop(file_name, output_name, a, b, h, w):
    '''
    Takes input file name , output file name, origin x from left , origin y from top , height of cropped image from origin , width of cropped image from origin.
    '''
    rows = read_rows(file_name, a, b, h, w)
    if not rows:
        return
    sub_pixels = repack_sub_pixels(rows, a, b, h, w)
    save_bmp(output_name, sub_pixels, w, h)

# 测试调用
crop('tiger.bmp', 'new_output.bmp', 0, 100, 100, 100)

关键修改点说明

  1. read_rows函数:

    • 新增了行填充字节的计算与跳过逻辑,确保读取的行数据仅包含有效像素。
    • 简化了像素读取流程,直接按BMP的BGR顺序收集通道数据。
  2. repack_sub_pixels函数:

    • 修正了水平范围的判断逻辑,用origin_x替代origin_y,确保裁剪区域正确。
    • 改用extend方法高效收集像素数据,避免循环append的冗余操作。
  3. save_bmp函数:

    • 严格按照BMP格式规范构建文件头和信息头,确保文件能被系统正常识别。
    • 处理了裁剪后图像的行对齐填充,保证每行字节数为4的倍数。
    • 按BMP要求的"从下到上"顺序写入像素数据,避免图像倒置。

内容的提问来源于stack exchange,提问作者caxefaizan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 10:57:52