1. 程式人生 > >Python pgm解析和格式轉換

Python pgm解析和格式轉換

下載ORL人臉資料庫,發現其影象檔案格式為pgm,之前也遇到過這種情況,這次仔細分析它的使用,並編寫指令碼用於影象格式之間的轉換

參考:

pgm

目錄

  1. PGM解析
  2. 格式轉換

PGM解析

pgm(行動式灰度圖,Portable Gray Map)是Netpbm開源工程設計的一種影象格式,除了pgm外,還有pbmppm

一個pgm檔案可以表示一個或多個pgm影象,其檔案內容如下:

1. A "magic number" for identifying the file type. A pgm image's magic number is the two characters "P5".
2. Whitespace (blanks, TABs, CRs, LFs).
3. A width, formatted as ASCII characters in decimal.
4. Whitespace.
5. A height, again in ASCII decimal.
6. Whitespace.
7. The maximum gray value (Maxval), again in ASCII decimal. Must be less than 65536, and more than zero.
8. A single whitespace character (usually a newline).
9. A raster of Height rows, in order from top to bottom. Each row consists of Width gray values, in order from left to right. Each gray value is a number from 0 through Maxval, with 0 being black and Maxval being white. Each gray value is represented in pure binary by either 1 or 2 bytes. If the Maxval is less than 256, it is 1 byte. Otherwise, it is 2 bytes. The most significant byte is first.

1. 用於識別檔案型別的“幻數”。PGM影象的幻數是兩個字元“P5”。
2. 空格(blanks, TABs, CRs, LFs)。
3. 寬度,格式為ASCII十進位制數字。
4. 空格。
5. 高度,同樣為ASCII十進位制數字。
6. 空格。
7. 最大灰度值(Maxval),同時是ASCII十進位制。範圍為[0,6536]。
8. 單個空白字元(通常是換行符)。
9. 從上到下,從左到右排列灰度值。每個灰度值取值為[0,Maxval],其中0表示黑色,Maxval表示白色。每個灰度值由1個或2個位元組的純二進位制表示。如果最大值小於256,則為1位元組。否則,它是2位元組。最重要的位元組是第一個。

Notepad++開啟一個PGM檔案

P5
92 112
255
01-/19'*515<L[c_PKB6/12+.5=FTi厒n^Qk_P9...

它的幻數是P5,寬為92,高為112,最大值為255

Plain PGM

還有其中格式的PGM檔案,它的幻數是P2,稱為Plain PGM,這種格式的變化在於:

1. There is exactly one image in a file.
2. The magic number is P2 instead of P5. 
3. Each pixel in the raster is represented as an ASCII decimal number (of arbitrary size).
4. Each pixel in the raster has white space before and after it. There must be at least one character of white space between any two pixels, but there is no maximum.
5. No line should be longer than 70 characters.

1. 檔案中僅有單個影象。
2. 幻數是P2。
3. 柵格中的每個畫素表示為ASCII十進位制數(任意大小)。
4. 光柵中的每個畫素在其前後都有白色空間。在任何兩個畫素之間必須至少有一個空白字元。
5. 沒有一行應該長於70個字元。

示例如下:

P2
# feep.pgm
24 7
15
0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
0  3  3  3  3  0  0  7  7  7  7  0  0 11 11 11 11  0  0 15 15 15 15  0
0  3  0  0  0  0  0  7  0  0  0  0  0 11  0  0  0  0  0 15  0  0 15  0
0  3  3  3  0  0  0  7  7  7  0  0  0 11 11 11  0  0  0 15 15 15 15  0
0  3  0  0  0  0  0  7  0  0  0  0  0 11  0  0  0  0  0 15  0  0  0  0
0  3  0  0  0  0  0  7  7  7  7  0  0 11 11 11 11  0  0 15  0  0  0  0
0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0

為什麼灰度圖會有一個灰度取值兩個位元組?

平常都是在RGB模式下工作,灰度取值為[0 - 255],所以一個位元組就好了

但其實還有更多的灰度級數,比如16位灰度,那就需要2個位元組

格式轉換

寫了一個python程式,可以批量處理,也可以單個影象轉換

# -*- coding: utf-8 -*-
from __future__ import print_function

import cv2
import time
import os
import operator
import numpy as np
import argparse
from PIL import Image

__author__ = 'zj'

image_formats = ['jpg', 'JPG', 'jpeg', 'JPEG', 'png', 'PNG']


def is_pgm_file(in_path):
    if not os.path.isfile(in_path):
        return False
    if in_path is not str and not in_path.endswith('.pgm'):
        return False
    return True


def convert_pgm_by_PIL(in_path, out_path):
    if not is_pgm_file(in_path):
        raise Exception("%s 不是一個PGM檔案" % in_path)
    # 讀取檔案
    im = Image.open(in_path)
    im.save(out_path)


def convert_pgm_P5(in_path, out_path):
    """
    將pgm檔案轉換成其它影象格式
    讀取二進位制檔案,先讀取幻數,再讀取寬和高,以及最大值
    :param in_path: 輸入pgm檔案路徑
    :param out_path: 輸出檔案路徑
    """
    if not is_pgm_file(in_path):
        raise Exception("%s 不是一個PGM檔案" % in_path)
    with open(in_path, 'rb') as f:
        # 讀取兩個位元組 - 幻數,並解碼成字串
        magic_number = f.readline().strip().decode('utf-8')
        if not operator.eq(magic_number, "P5"):
            raise Exception("該影象有誤")
        # 讀取高和寬
        width, height = f.readline().strip().decode('utf-8').split(' ')
        width = int(width)
        height = int(height)
        # 讀取最大值
        maxval = f.readline().strip()
        # 每次讀取灰度值的位元組數
        if int(maxval) < 256:
            one_reading = 1
        else:
            one_reading = 2
        # 建立空白影象,大小為(行,列)=(height, width)
        img = np.zeros((height, width))
        img[:, :] = [[ord(f.read(one_reading)) for j in range(width)] for i in range(height)]
        cv2.imwrite(out_path, img)
        print('%s save ok' % out_path)


def convert_pgm_P5_batch(in_dir, out_dir, res_format):
    """
    批量轉換PGM檔案
    :param in_dir: pgm資料夾路徑
    :param out_dir: 輸出資料夾路徑
    :param res_format: 結果影象格式
    """
    if not os.path.isdir(in_dir):
        raise Exception('%s 不是路徑' % in_dir)
    if not os.path.isdir(out_dir):
        raise Exception('%s 不是路徑' % out_dir)
    if not res_format in image_formats:
        raise Exception('%s 暫不支援' % res_format)
    file_list = os.listdir(in_dir)
    for file_name in file_list:
        file_path = os.path.join(in_dir, file_name)
        # 若為pgm檔案路徑,那麼將其進行格式轉換
        if is_pgm_file(file_path):
            file_out_path = os.path.join(out_dir, os.path.basename(file_name) + '.' + res_format)
            convert_pgm_P5(file_path, file_out_path)
        # 若為目錄,則新建結果檔案目錄,遞迴處理
        elif os.path.isdir(file_path):
            file_out_dir = os.path.join(out_dir, file_name)
            if not os.path.exists(file_out_dir):
                os.mkdir(file_out_dir)
            convert_pgm_P5_batch(file_path, file_out_dir, res_format)
        else:
            pass
    print('batch operation over')


if __name__ == '__main__':
    script_start_time = time.time()

    parser = argparse.ArgumentParser(description='Format Converter - PGM')

    ### Positional arguments

    ### Optional arguments

    parser.add_argument('-i', '--input', type=str, help='Path to the pgm file')
    parser.add_argument('-o', '--output', type=str, help='Path to the result file')
    parser.add_argument('--input_dir', type=str, help='Dir to the pgm files')
    parser.add_argument('--output_dir', type=str, help='Dir to the result files')
    parser.add_argument('-f', '--format', default='png', type=str, help='result image format')
    parser.add_argument('-b', '--batch', action="store_true", default=False, help='Batch processing')

    args = vars(parser.parse_args())
    # print(args)
    in_path = args['input']
    out_path = args['output']

    isbatch = args['batch']
    in_dir = args['input_dir']
    out_dir = args['output_dir']
    res_format = args['format']

    if in_path is not None and out_path is not None:
        # 轉換單個pgm檔案格式
        convert_pgm_P5(in_path, out_path)
        # convert_pgm_by_PIL(in_path, out_path)
    elif isbatch:
        # 批量轉換
        convert_pgm_P5_batch(in_dir, out_dir, res_format)
    else:
        print('請輸入相應引數')

    print('Script took %s seconds.' % (time.time() - script_start_time,))

使用PIL庫也可以讀取PGM檔案,然後儲存為其它格式影象,我自己寫了一個解析二進位制檔案的方式,速度比呼叫PIL庫快大約2.5

轉換單個PGM檔案:

python PGMConverter.py -i INPUT -o OUTPUT

例如:

python PGMConverter.py -i 1.pgm -o 3.png

轉換整個PGM資料夾

python PGMConverter.py --batch --input_dir INPUT_DIR --output_dir OUTPUT_DIR -f FORMAT

INPUT_DIR替換成PGM資料夾路徑,OUTPUT_DIR替換成結果檔案路徑(該資料夾需提前新建),FORMAT替換成結果影象格式

例如:

python PGMConverter.py --batch --input_dir c:\\face\\att_faces --output_dir c:\\face\\att_face_png -f png