1. 程式人生 > >可移植畫素圖格式 PPM,灰度圖格式 PGM,點陣圖格式 PBM 的介紹 -- 視訊和影象程式設計基礎之一

可移植畫素圖格式 PPM,灰度圖格式 PGM,點陣圖格式 PBM 的介紹 -- 視訊和影象程式設計基礎之一

可移植畫素圖格式 PPM,灰度圖格式 PGM,點陣圖格式 PBM 的介紹

簡介

可移植畫素圖格式(PPM),可移植灰度圖格式(PGM)和可移植點陣圖格式(PBM)是便於跨平臺的影象格式。有時候也被統稱為 PNM 格式

檔案格式描述

這三種格式其實是一樣的描述方法,只不過 PBM 是單色,PGM 是灰度圖,PPM 使用 RGB 顏色。每個檔案的開頭兩個位元組(ASCII 碼)作為檔案描述符,指出具體格式和編碼形式。

Type Magic number Extension Colors
ASCII Binary
Portable BitMap P1 P4 .pbm 0–1 (white & black)
Portable GrayMap P2 P5 .pgm 0–255 (gray scale)
Portable PixMap P3 P6 .ppm 0–255 (RGB)
Portable Arbitrary Map P7 .pam 0–255 (RGB_ALPHA)

格式例子

PBM

注意每行結束有換行符

P1
# This is an example bitmap of the letter "J"
6 10
0 0 0 0 1 0
0 0 0 0 1 0
0 0 0 0 1 0
0 0 0 0 1 0
0 0 0 0 1 0
0 0 0 0 1 0
1 0 0 0 1 0
0 1 1 1 0 0
0 0 0 0 0 0
0 0 0 0 0 0

上面的影象是一個J

PGM

P2
# Shows the word "FEEP" (example from Netpbm man page on 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

上面的影象是220px-Feep_netbpm_p2_pgm_example

PPM

P3
3 2
255
# The part above is the header
# "P3" means this is a RGB color image in ASCII
# "3 2" is the width and height of the image in pixels
# "255" is the maximum value for each color
# The part below is image data: RGB triplets
255   0   0     0 255   0     0   0 255
255 255   0   255 255 255     0   0   0

把上面六個畫素放大後顯示如下Tiny6pixel

PAM

P7
WIDTH 4
HEIGHT 2
DEPTH 4
MAXVAL 255
TUPLTYPE RGB_ALPHA
ENDHDR
0000FFFF 00FF00FF FF0000FF FFFFFFFF
0000FF7F 00FF007F FF00007F FFFFFF7F

上面資料放大顯示如下:firefox_2018-09-15_00-50-56

用go生成PPM檔案

下面是簡單的ppm包

package ppm
 
import (
    "fmt"
    "io"
    "os"
)

// WriteTo outputs 8-bit P6 PPM format to an io.Writer.
func (b *Bitmap) WritePpmTo(w io.Writer) (err error) {
    // magic number
    if _, err = fmt.Fprintln(w, "P6"); err != nil {
        return
    }

    // comments
    for _, c := range b.Comments {
        if _, err = fmt.Fprintln(w, c); err != nil {
            return
        }
    }

    // x, y, depth
    _, err = fmt.Fprintf(w, "%d %d\n255\n", b.cols, b.rows)
    if err != nil {
        return
    }

    // raster data in a single write
    b3 := make([]byte, 3*len(b.px))
    n1 := 0
    for _, px := range b.px {
        b3[n1] = px.R
        b3[n1+1] = px.G
        b3[n1+2] = px.B
        n1 += 3
    }
    if _, err = w.Write(b3); err != nil {
        return
    }
    return
}

// WriteFile writes to the specified filename.
func (b *Bitmap) WritePpmFile(fn string) (err error) {
    var f *os.File
    if f, err = os.Create(fn); err != nil {
        return
    }
    if err = b.WritePpmTo(f); err != nil {
        return
    }
    return f.Close()
}

下面是生成ppm的程式

package main

// Files required to build supporting package raster are found in:
// * This task (immediately above)
// * Bitmap task

import (
    "raster"
    "fmt"
)

func main() {
    b := raster.NewBitmap(400, 300)
    b.FillRgb(0x240008) // a dark red
    err := b.WritePpmFile("write.ppm")
    if err != nil {
        fmt.Println(err)
    }
}

用C生成PPM檔案

#include <stdio.h>

int main()
{
  const char *filename = "n.pgm";
  int x, y;
  /* size of the image */
  const int x_max = 100;  /* width */
  const int y_max = 100;  /* height */
  /* 2D array for colors (shades of gray) */
  unsigned char data[y_max][x_max];
  /* color component is coded from 0 to 255 ;  it is 8 bit color file */
  const int MaxColorComponentValue = 255;
  FILE * fp;
  /* comment should start with # */
  const char *comment = "# this is my new binary pgm file";

  /* fill the data array */
  for (y = 0; y < y_max; ++y) {
    for (x = 0; x < x_max; ++x) {
      data[y][x] = (x + y) & 255;
    }
  }

  /* write the whole data array to ppm file in one step */
  /* create new file, give it a name and open it in binary mode */
  fp = fopen(filename, "wb");
  /* write header to the file */
  fprintf(fp, "P5\n %s\n %d\n %d\n %d\n", comment, x_max, y_max,
          MaxColorComponentValue);
  /* write image data bytes to the file */
  fwrite(data, sizeof(data), 1, fp);
  fclose(fp);
  printf("OK - file %s saved\n", filename);
  return 0;
}

或者

imglib.h

#ifndef _IMGLIB_0
#define _IMGLIB_0
 
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
#include <math.h>
#include <sys/queue.h>
 
typedef unsigned char color_component;
typedef color_component pixel[3];
typedef struct {
    unsigned int width;
    unsigned int height;
    pixel * buf;
} image_t;
typedef image_t * image;
 
image alloc_img(unsigned int width, unsigned int height);
void free_img(image);
void fill_img(image img,
        color_component r,
        color_component g,
        color_component b );
void put_pixel_unsafe(
           image img,
        unsigned int x,
        unsigned int y,
        color_component r,
        color_component g,
        color_component b );
void put_pixel_clip(
           image img,
        unsigned int x,
        unsigned int y,
        color_component r,
        color_component g,
        color_component b );
#define GET_PIXEL(IMG, X, Y) (IMG->buf[ ((Y) * IMG->width + (X)) ])
#endif

imglib.c

image alloc_img(unsigned int width, unsigned int height)
{
    image img;
    img = malloc(sizeof(image_t));
    img->buf = malloc(width * height * sizeof(pixel));
    img->width = width;
    img->height = height;
    return img;
}
 
void free_img(image img)
{
    free(img->buf);
    free(img);
}
 
void fill_img(
        image img,
        color_component r,
        color_component g,
        color_component b )
{
    unsigned int i, n;
    n = img->width * img->height;
    for (i=0; i < n; ++i)
    {
        img->buf[i][0] = r;
        img->buf[i][1] = g;
        img->buf[i][2] = b;
    }
}
 
void put_pixel_unsafe(
           image img,
        unsigned int x,
        unsigned int y,
        color_component r,
        color_component g,
        color_component b )
{
    unsigned int ofs;
    ofs = (y * img->width) + x;
    img->buf[ofs][0] = r;
    img->buf[ofs][1] = g;
    img->buf[ofs][2] = b;
}
 
void put_pixel_clip(
           image img,
        unsigned int x,
        unsigned int y,
        color_component r,
        color_component g,
        color_component b )
{
    if (x < img->width && y < img->height)
      put_pixel_unsafe(img, x, y, r, g, b);
}

output_ppm

#include "imglib.h"
 
void output_ppm(FILE *fd, image img)
{
  unsigned int n;
  (void) fprintf(fd, "P6\n%d %d\n255\n", img->width, img->height);
  n = img->width * img->height;
  (void) fwrite(img->buf, sizeof(pixel), n, fd);
  (void) fflush(fd);
}

生成raw rgb格式

其實raw rgb和ppm資料是一樣的,我們生成出300幀的ppm資料,然後送給ffplayer(ffmpeg)來播放下面是我的go程式碼:

//
// Created by         :  Harris Zhu
// Filename           :  genrgb.go
// Author             :  Harris Zhu
// Created On         :  2018-09-14 02:13:11
// Last Modified      :  2018-09-14 02:13:11
// Update Count       :  1
// Tags               :
// Description        :
// Conclusion         :
//
//=======================================================================

package main

import (
    "bufio"
    "os"
    "strconv"
    "time"

    "github.com/urfave/cli"
)

func main() {
    app := cli.NewApp()
    app.Name = "genrgb"
    app.Version = "1.0.0"
    app.Compiled = time.Now()
    app.Authors = []cli.Author{
        cli.Author{
            Name:  "Harris Zhu",
            Email: "[email protected]",
        },
    }
    app.Usage = "svpaser <sv file>"
    name := "rgb.data"
    w := 600
    h := 480
    f := 20
    app.Action = func(c *cli.Context) error {
        name = c.Args().Get(0)
        w, _ = strconv.Atoi(c.Args().Get(1))
        h, _ = strconv.Atoi(c.Args().Get(2))
        f, _ = strconv.Atoi(c.Args().Get(3))

        genrgb(name, w, h, f)

        return nil
    }
    app.Run(os.Args)
}

func genrgb(filepath string, w int, h int, f int) {
    fout, err := os.Create(filepath)
    defer fout.Close()
    if err != nil {
        panic(err)
    }
    bufout := bufio.NewWriter(fout)
//    bufout.WriteString("P6\n # this is my ppm file\n")
//    bufout.WriteString(strconv.Itoa(w))
//    bufout.WriteString("\n")
//    bufout.WriteString(strconv.Itoa(h))
//    bufout.WriteString("\n")
//    bufout.WriteString(strconv.Itoa(255))
//    bufout.WriteString("\n")
    r := []byte{255, 0, 0}
    g := []byte{0, 255, 0}
    b := []byte{0, 0, 255}
    dcnt := 0
    //k: frame
    //i: height column
    //j: width line
    for k := 0; k < f; k++ {
        for i := 0; i < h; i++ {
            //fmt.Println("hline: ", i)
            for j := 0; j < w; j++ {
                if k%3 == 0 {
                    bufout.Write(r)
                    dcnt++
                } else if k%3 == 1 {
                    bufout.Write(g)
                    dcnt++
                } else if k%3 == 2 {
                    bufout.Write(b)
                    dcnt++
                }
                //fmt.Println("vline: ", j)
            }
        }
        bufout.Flush()
        //fmt.Printf("total pixels = %d\n", dcnt)
    }
}

下面是makefile:

FILE = genrgb
GENFILE = rgb.data

b build:
    go build  -gcflags "-N -l" $(FILE).go

g gen:
    ./$(FILE) $(GENFILE) 60 40 300

p play:
    cat $(GENFILE) | ffplay -i pipe:0 -f rawvideo -pix_fmt rgb24 -video_size 60x40

依次執行make b; make g; make p可以看到下面的播放視訊

ppm_disp

它交替顯示紅綠藍

總結

RGB比較直觀的影象顯示方法,但相對YUV來說比較佔空間,每個畫素佔用3個byte, 像1080P的影象就要佔用192010803=6,220,800byte, 但它用於作為影象和顯示入門是非常好的。能夠讓你很快就常會程式設計顯示影象。