• 【Python】读取显示pgm图像文件


    零. 前言

    这学期要学多媒体信息隐藏对抗,发现其中的图像数据集文件都是pgm文件形式的。虽然是图像文件,但是却不能直接通过图像查看器来打开,上网一搜:”如何打开pgm文件?“多半是使用第三方软件photoshop之类的。

    都是能写代码的人了,难道为了看几张图片还要下一个几G软件吗?

    至此,我就开始考虑如何使用python读取pgm(Portable Gray Map)文件并显示出来。

    一. pgm基本概念

    如果使用记事本的方式打开,可以看到如下格式(以P2为例):

    P2
    width height
    max_gray_value
    pixel1 pixel2 pixel3 ... pixelN
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5

    例如下面的P5:

    在这里插入图片描述

    下面我们逐行解析一下:

    • 首行:Magic Number(魔数)是portable像素图片文件中的一个标识符,用于指示文件的类型和格式。以下是文件中可能出现的几种魔数及其含义:
      1. P1:表示这是一个ASCII格式的黑白二值图像。在这种格式下,像素的灰度值只能是0或1。
      2. P2:表示这是一个ASCII格式的灰度图像。在这种格式下,像素的灰度值可以是0到最大灰度值之间的任何整数。
      3. P3:表示这是一个ASCII格式的彩色图像。在这种格式下,每个像素包含三个分量(红、绿、蓝)的灰度值。
      4. P4:表示这是一个二进制格式的黑白二值图像。在这种格式下,像素的灰度值只能是0或1。
      5. P5:表示这是一个二进制格式的灰度图像。在这种格式下,像素的灰度值可以是0到最大灰度值之间的任何整数。
      6. P6:表示这是一个二进制格式的彩色图像。在这种格式下,每个像素包含三个分量(红、绿、蓝)的灰度值。

    基于此,我们可以得到如下表格:

    魔数类型编码方式文件后缀
    P1单色图ASSIIPBM
    P2灰度图ASSIIPGM
    P3像素图ASSIIPPM
    P4单色图二进制PBM
    P5灰度图二进制PGM
    P6像素图二进制PPM
    • 第二行:widthheight 表示图像的宽度和高度。

    • 第三行:

      • 如果是P1、P4:不存在颜色分量的最大值,即没有表示。
      • 如果是P2、P5:max_gray_value 表示灰度值的最大值(通常是255)。
      • 如果是P3、P6:max_color_value 表示颜色分量的最大值(通常是255)。
    • 后续:pixel1, pixel2, … pixelN 是图像的像素值。

      ​ 像素值可以是二进制或ASCII格式,如上图所示,如果无法解析成ASCII形式的字符,则表示这个pgm文件是二进制表示的pixels。

    二. pgm基本信息读取

    针对不同编码方式表示的像素,我们具有不同的读取与解析方案read_binary_pgm以及read_ascii_pgm。

    代码如下:

    def read_binary_pgm(file_path):
        with open(file_path, 'rb') as f:
            # 读取头部信息
            magic_number = f.readline().decode().strip()
            width, height = map(int, f.readline().decode().strip().split())
            max_gray_value = int(f.readline().decode().strip())
    
            # 读取像素值
            pixels = list(f.read())
    
            return (width, height, max_gray_value, pixels)
    
    
    def read_ascii_pgm(file_path):
        with open(file_path, 'r') as f:
            # 读取头部信息
            header = f.readline().strip()
            width, height = map(int, f.readline().strip().split())
            max_gray_value = int(f.readline().strip())
    
            # 读取像素值
            pixels = [int(line) for line in f.readlines() if line.strip()]
    
            return (width, height, max_gray_value, pixels)
    
    
    file_path = '../pgmfiles/1.pgm' # 注意修改你的读取路径
    
    # width, height, max_gray_value, pixels = read_ascii_pgm(file_path)
    width, height, max_gray_value, pixels = read_binary_pgm(file_path)
    
    print(f"Width: {width}, Height: {height}")
    print(f"Max Gray Value: {max_gray_value}")
    print(f"Number of Pixels: {len(pixels)}")
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    这个小demo可以获取到这个pgm的基本信息:

    三. pgm图像渲染

    但是如何通过这些数据来渲染成图像呢?我们可以先将像素值转换为NumPy数组,并通过matplotlib将其显示为图像。

    首先在上述代码顶部导包,并且在尾部追加图像显示代码即可:

    import numpy as np
    import matplotlib.pyplot as plt
    
    # ...上述read代码...
    
    # 将像素列表转换为NumPy数组
    pixels_array = np.array(pixels).reshape(height, width)
    
    # 显示图像
    plt.imshow(pixels_array, cmap='gray', vmin=0, vmax=max_gray_value)
    plt.show()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    运行后,图像渲染如下:
    在这里插入图片描述

    四. 代码优化

    最后,优化一下代码,根据首行的魔数来兼容P2和P5两种情况,实现函数read_pgm。

    import numpy as np
    import matplotlib.pyplot as plt
    
    file_type = None  # 全局的文件类型变量
    
    def read_pgm(file_path):
        global file_type
    
        with open(file_path, 'rb') as f:
            magic_number = f.readline().decode().strip()
            file_type = magic_number  # 更新全局的文件类型变量
    
            width, height = map(int, f.readline().decode().strip().split())
            max_gray_value = int(f.readline().decode().strip())
    
            if magic_number == 'P5':
                # 二进制格式
                pixels = list(f.read())
            elif magic_number == 'P2':
                # ASCII格式
                pixels = [int(line) for line in f.readlines() if line.strip()]
            else:
                raise ValueError("Unsupported file type.")
    
            return (width, height, max_gray_value, pixels)
    
    
    def render_image(pixels, width, height, max_gray_value):
        # 将像素列表转换为NumPy数组
        pixels_array = np.array(pixels).reshape(height, width)
    
        # 显示图像
        plt.imshow(pixels_array, cmap='gray', vmin=0, vmax=max_gray_value)
        plt.show()
    
    
    file_path = '../pgmfiles/1.pgm'
    
    width, height, max_gray_value, pixels = read_pgm(file_path)
    
    print(f"Width: {width}, Height: {height}")
    print(f"Max Gray Value: {max_gray_value}")
    print(f"Number of Pixels: {len(pixels)}")
    
    # 显示图像
    render_image(pixels, width, height, max_gray_value)
    
    # 根据全局的文件类型变量进行其他操作
    if file_type == 'P5':
        print("This is a binary format PGM file.")
    elif file_type == 'P2':
        print("This is an ASCII format PGM file.")
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    基于此,其实也可以继续优化兼容其他4种文件,比如xx.ppm,xxx.pbm 文件。

    再完善一点可以封装成一个小型的ppm/pgm/pbm图像显示器.exe。

    不过那个就不在笔者的考虑范围内了。

  • 相关阅读:
    数据结构 线性表部分代码
    Ansys Lumerical | 用于增强现实系统的表面浮雕光栅
    【SpringBoot教程 01】SpringBoot简介及工程搭建
    如何基于 Apache Doris 构建新一代日志分析平台
    IPKISS Tutorials ------查看使用_generate_instances 方法创建图形的端点
    好奇!为什么gateway和springMVC之间依赖冲突?
    线程纵横:C++并发编程的深度解析与实践
    实用的GCC Makefile语法及参数详解
    迅为RK3588开发板Linux安卓12瑞芯微ARM核心板人工智能工业AI主板
    力扣(LeetCode)130. 被围绕的区域(C++)
  • 原文地址:https://blog.csdn.net/HYY_2000/article/details/133605914