This post is to serve the face recognition project.
In that project, we need a little tool to visualize/display Eigen faces. It would be very helpful during debugging/testing to display the Eigen faces and mean faces visually after they are calculated. We would also like to display an image to see if our IO part is correct or not.
The ORL face data set I use is of PGM format, so I need to load these images and display them.
First let’s get familiar with the PGM image format. PGM stands for Netpbm grayscale image. Its format is every easy:
P5 or P2
# 0 or more lines for comments
nrows ncolumns
max-value
image content
The format of image content part could be either text(P2) or binary(P5). More detail is here and here.
Before reading the file, let’s define the data structure for a PGM image. Because it is grey scale, thus it is actually an F# matrix:
type pgm = matrix
We would use two readers: a BinaryReader for binary image content and a StreamReader for text image content. (I think we could also use StreamReader for both, But I started using BinaryReader for binary image content and added the text image content later, so two readers are used.)
exception PgmFormatError of string
let readPgm path =
let rec skipCommentLine br =
let l = getline br
if l.StartsWith("#") then
skipCommentLine br
else
l
let br = new BinaryReader(File.Open(path, FileMode.Open))
let pgmType = getline br
let sizeStr = skipCommentLine br
let s = sizeStr.Split ' '
let ncol = int (s.[0])
let nrow = int (s.[1])
printfn "nrow = %d ncol = %d" nrow ncol
let maxStr = getline br
if (pgmType.StartsWith("P5")) then
// binary format
let mutable b = Array.create (nrow * ncol) 0uy
let nread = br.Read(b, 0, (nrow*ncol))
let bb = b
Matrix.init nrow ncol (fun i j -> float (bb.[i*ncol+j]))
elif (pgmType.StartsWith("P2")) then
// text format
br.Close() // close the file and re-open
use sr = new StreamReader(path)
sr.ReadLine() |> ignore
let mutable line = sr.ReadLine()
while line.StartsWith("#") do
line <- sr.ReadLine()
sr.ReadLine() |> ignore
let str =
seq {
while not sr.EndOfStream do
yield sr.ReadLine()
}
|> Seq.fold (fun a b -> a + b) ""
let pic = Matrix.create nrow ncol 0.0
let vals = str.Split([|' ';'\t';'\r';'\n'|], StringSplitOptions.RemoveEmptyEntries)
printfn "%A" vals
Matrix.init nrow ncol (fun i j -> float (vals.[i*ncol+j]))
else
// unknown format
raise (PgmFormatError(path))
The getline function for BinaryReader:
let getline (br:BinaryReader) =
let chSeq = seq {Next we write the writer for a pgm image, I only implemented the writer for the text content case as this is mainly used for outputting some intermediate results, thus need to be human-readable.
let ch = ref (br.ReadChar())
while !ch <> '\n' do
yield !ch
ch := br.ReadChar()
}
let chArr = chSeq |> Seq.toArray
let line = new String(chArr)
line
let writePgm (path:string) (p:pgm) =
use fs = new FileStream(path, FileMode.Create)
use sw = new StreamWriter(fs, Encoding.ASCII)
sw.Write("P2\n{0} {1}\n255\n", p.NumCols, p.NumRows)
for i=0 to (p.NumRows - 1) do
for j=0 to (p.NumCols - 1) do
sw.Write(" " + string (p.[i,j]))
sw.WriteLine()
Finally, the viewer. F# actually is quite good at doing small GUI programs. First inherits from a form:
type PGMViewer(pic:matrix, name:string) as this =
inherit Form(Width = 300, Height = 300)
then create a main menu, and menu items in it, a PictureBox to display bitmap images and add a SaveFileDialog:
let mainmenu = new MainMenu()
let mFile = new MenuItem()Set necessary properties of these objects (put these code in a do block)
let miFileSave = new MenuItem()
let miExit = new MenuItem()
let pb = new PictureBox()
let filesave = new SaveFileDialog()
do// savefile dialog
// set the property of picture box
let nrow = pic.NumRows
let ncol = pic.NumCols
let image = new Bitmap(ncol, nrow)
printfn "%A %A" nrow ncol
for i=0 to nrow-1 do
for j=0 to ncol-1 do
image.SetPixel(j, i, Color.FromArgb(int pic.[i,j], int pic.[i,j], int pic.[i,j]))
pb.Image <- image
pb.Height <- nrow
pb.Width <- ncol
// add picture box
this.Controls.Add(pb)
// set memu property
miFileSave.Index <- 0
miFileSave.Text <- "&Save"
miExit.Index <- 1
miExit.Text <- "&Exit"
mFile.Index <- 0
mFile.Text <- "&File"
mFile.MenuItems.Add(miFileSave) |> ignore
mFile.MenuItems.Add(miExit) |> ignore
mainmenu.MenuItems.Add(mFile) |> ignore
// add menu
this.Menu <- mainmenu
And add event handler for click event of the save menu item:
filesave.DefaultExt <- "pgm"Put them together:
filesave.Filter <- "PGM Files|*.pgm"
let filesaveClick e =
if pb.Image = null then
()
else
if filesave.ShowDialog() = DialogResult.OK then
let bm = pb.Image :?> Bitmap
let pic = Matrix.init (bm.Height) (bm.Width) (
fun i j ->
let c = bm.GetPixel(j,i)
float c.R * 0.3 + float c.G * 0.59 + float c.B * 0.11
)
writePgm filesave.FileName pic
miFileSave.Click.Add(filesaveClick)
open System.Windows.Forms
open System.Drawing
type PGMViewer(pic:matrix, name:string) as this =
inherit Form(Width = 300, Height = 300)
let mainmenu = new MainMenu()
let mFile = new MenuItem()
let miFileSave = new MenuItem()
let miExit = new MenuItem()
let pb = new PictureBox()
let filesave = new SaveFileDialog()
do
// set the property of picture box
let nrow = pic.NumRows
let ncol = pic.NumCols
let image = new Bitmap(ncol, nrow)
printfn "%A %A" nrow ncol
for i=0 to nrow-1 do
for j=0 to ncol-1 do
image.SetPixel(j, i, Color.FromArgb(int pic.[i,j], int pic.[i,j], int pic.[i,j]))
pb.Image <- image
pb.Height <- nrow
pb.Width <- ncol
// add picture box
this.Controls.Add(pb)
// set memu property
miFileSave.Index <- 0
miFileSave.Text <- "&Save"
miExit.Index <- 1
miExit.Text <- "&Exit"
mFile.Index <- 0
mFile.Text <- "&File"
mFile.MenuItems.Add(miFileSave) |> ignore
mFile.MenuItems.Add(miExit) |> ignore
mainmenu.MenuItems.Add(mFile) |> ignore
// add menu
this.Menu <- mainmenu
// savefile dialog
filesave.DefaultExt <- "pgm"
filesave.Filter <- "PGM Files|*.pgm"
let filesaveClick e =
if pb.Image = null then
()
else
if filesave.ShowDialog() = DialogResult.OK then
let bm = pb.Image :?> Bitmap
let pic = Matrix.init (bm.Height) (bm.Width) ( fun i j ->
let c = bm.GetPixel(j,i)
float c.R * 0.3 + float c.G * 0.59 + float c.B * 0.11
)
writePgm filesave.FileName pic
miFileSave.Click.Add(filesaveClick)
// set title of the dialog
this.Text <- "PGMViewer" + " - " + name
And let’s test the viewer now:
(new PGMViewer(readPgm @"C:\temp\temp4.pgm")).Show()
(Copyright: this face is from the ORL face database.)
No comments:
Post a Comment