文章目录
- 零. 前言
- 一. pgm基本概念
- 二. pgm基本信息读取
- 三. pgm图像渲染
- 四. 代码优化
零. 前言
这学期要学多媒体信息隐藏对抗,发现其中的图像数据集文件都是pgm文件形式的。虽然是图像文件,但是却不能直接通过图像查看器来打开,上网一搜:”如何打开pgm文件?“多半是使用第三方软件photoshop之类的。
都是能写代码的人了,难道为了看几张图片还要下一个几G软件吗?
至此,我就开始考虑如何使用python读取pgm(Portable Gray Map)文件并显示出来。
一. pgm基本概念
如果使用记事本的方式打开,可以看到如下格式(以P2为例):
P2
width height
max_gray_value
pixel1 pixel2 pixel3 ... pixelN
...
例如下面的P5:
下面我们逐行解析一下:
- 首行:Magic Number(魔数)是portable像素图片文件中的一个标识符,用于指示文件的类型和格式。以下是文件中可能出现的几种魔数及其含义:
- P1:表示这是一个ASCII格式的黑白二值图像。在这种格式下,像素的灰度值只能是0或1。
- P2:表示这是一个ASCII格式的灰度图像。在这种格式下,像素的灰度值可以是0到最大灰度值之间的任何整数。
- P3:表示这是一个ASCII格式的彩色图像。在这种格式下,每个像素包含三个分量(红、绿、蓝)的灰度值。
- P4:表示这是一个二进制格式的黑白二值图像。在这种格式下,像素的灰度值只能是0或1。
- P5:表示这是一个二进制格式的灰度图像。在这种格式下,像素的灰度值可以是0到最大灰度值之间的任何整数。
- P6:表示这是一个二进制格式的彩色图像。在这种格式下,每个像素包含三个分量(红、绿、蓝)的灰度值。
基于此,我们可以得到如下表格:
魔数 | 类型 | 编码方式 | 文件后缀 |
---|---|---|---|
P1 | 单色图 | ASSII | PBM |
P2 | 灰度图 | ASSII | PGM |
P3 | 像素图 | ASSII | PPM |
P4 | 单色图 | 二进制 | PBM |
P5 | 灰度图 | 二进制 | PGM |
P6 | 像素图 | 二进制 | PPM |
-
第二行:
width
和height
表示图像的宽度和高度。 -
第三行:
- 如果是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)}")
这个小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()
运行后,图像渲染如下:
四. 代码优化
最后,优化一下代码,根据首行的魔数来兼容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.")
基于此,其实也可以继续优化兼容其他4种文件,比如xx.ppm,xxx.pbm 文件。
再完善一点可以封装成一个小型的ppm/pgm/pbm图像显示器.exe。
不过那个就不在笔者的考虑范围内了。