最終的には OpenCV と組み合わせるはずなのですが、いちいち C++ で組むのも面倒なので、事前加工の部分を F# で組みます。
こんな画像をグレースケールに変えたり、2値化するだけですね。
■画像加工にパイプを使う
C++ で OpenCV を使っていた頃から考えていたのですが、画像加工のフィルタ部分はパイプ(>>)を使うとスムースです。当然フィルタなんだから、画像関係の多種の filter と同じなので当然といえば当然。
let path = @"D:\work\PiVistion\src\PiVision\FsVision.Test\data\001.bmp" let pi = FsVision.LoadForm( path ) // グレースケール pi |> Pixel.toGray |> FsVision.SavePixel( @"D:\work\PiVistion\src\PiVision\FsVision.Test\data\001-pi-gray.bmp" )
F# では、こんな風に |> を使ってつなげます。
■PowerPack を使うかどうか?
The Old F# “PowerPack” – Home
https://fsharppowerpack.codeplex.com/
fsprojects/powerpack
https://github.com/fsprojects/powerpack
画像加工の場合、2次元マトリクスが頻発するので、F# powerpack を使うのがベターなんでしょうが、要素として RGBA を使わないといけないので、Matix<RGBA> と定義したいんですよね。が、が、なんとなくうまく動かない。Matrix<‘T> になっているけど、実質 Matirx<float> しか動かないようです。RGBA を別々に扱っても良いのですが、試してみると F# の List#Item は結構遅くて、短時間に 640×480 の画素数を処理するのは難しそうです。シーケンシャルに for it in lst do な感じでイテレータアクセスすれば結構なスピードで動きます。まあ、有限要素法の件もあるし、マトリクス関係は自作しましょう、ということで powerpack 無しで作っています。
どちらかというと、OpenCV の各種関数を F# から使うところに力を入れた感じ。
■RGB/YUV/YIQ/HSV の相互変換
Zaku認識(2) | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/601
かつて Zaku 認識(4年も前の記事になるのか)をやろうと思って、色相とか輝度とかを扱ったので、これの F# 版も作っておきます。3×3 か 4×4 のマトリクスなんだが、これをいちいち自前の matrix に通すと遅いので、直接計算します。さほど手間ではないし。
type RGB = { R:float; G:float; B:float; } type YUV = { Y:float; U:float; V:float } type YIQ = { Y:float; I:float; Q:float } type HSV = { H:float; S:float; V:float } module Pixel = type Conv = static member toYUV (rgb:RGB) = let y = 0.299 * rgb.R + 0.587 * rgb.G + 0.114 * rgb.B let u = -0.169 * rgb.R - 0.331 * rgb.G + 0.500 * rgb.B let v = 0.500 * rgb.R + -0.419 * rgb.G - 0.081 * rgb.B { Y=y; U=u; V=v } static member toRGB (yuv:YUV) = let r = 1.000 * yuv.Y + 0.000 * yuv.U + 1.402 * yuv.V let g = 1.000 * yuv.Y - 0.344 * yuv.U - 0.714 * yuv.V let b = 1.000 * yuv.Y + 1.772 * yuv.U + 0.000 * yuv.V { R=r; G=g; B=b } static member toYIQ ( rgb:RGB ) = let y = 0.2990 * rgb.R + 0.5870 * rgb.G + 0.1140 * rgb.B let i = 0.5959 * rgb.R - 0.2750 * rgb.G - 0.3210 * rgb.B let q = 0.2065 * rgb.R - 0.4969 * rgb.G + 0.2904 * rgb.B { Y=y; I=i; Q=q } static member toRGB ( yiq:YIQ ) = { R = 1.001 * yiq.Y + 0.955 * yiq.I + 0.622 * yiq.Q; G = 0.999 * yiq.Y - 0.272 * yiq.I - 0.648 * yiq.Q; B = 1.004 * yiq.Y - 1.106 * yiq.I + 1.704 * yiq.Q; } static member toHSV ( yiq:YIQ ) = { H = System.Math.Atan2(yiq.I, yiq.Q) ; S = Math.Sqrt( yiq.I * yiq.I + yiq.Q * yiq.Q) ; V = yiq.Y; } static member toYIQ ( hsv:HSV ) = { Y = hsv.V; I = hsv.S * Math.Sin( hsv.H ) ; Q = hsv.S * Math.Cos( hsv.H ) ; } static member norm (rgb:RGB) = let r = if rgb.R < 0.0 then 0.0 else if rgb.R > 1.0 then 1.0 else rgb.R let g = if rgb.G < 0.0 then 0.0 else if rgb.G > 1.0 then 1.0 else rgb.G let b = if rgb.B < 0.0 then 0.0 else if rgb.B > 1.0 then 1.0 else rgb.B { R=r; G=g; B=b } let map (f:(RGB->RGB)) (pi:pixels) = pixels [ for row in pi.Data do yield [ for it in row do yield f( it ) ]] /// <summary> /// gray scale filter /// </summary> /// <param name="pi"></param> let toGray (pi:pixels) = pi |> map ( fun x -> // 加重平均 let c = (0.896 * x.R + 1.76 * x.G + 0.34 * x.B) / 3.0 { R=c; G=c; B=c } ) let to2Value (ce:float) (pi:pixels) = pi |> map ( fun x -> let c = (0.896 * x.R + 1.76 * x.G + 0.34 * x.B) / 3.0 if c <= ce then { R=0.; G=0.; B=0. } else { R=1.; G=1.; B=1. } ) /// <summary> /// sepia filter /// </summary> /// <param name="pi"></param> let toSepia (pi:pixels) = pi |> map ( fun x -> let r = 0.393 * x.R + 0.769 * x.G + 0.189 * x.B let g = 0.349 * x.R + 0.686 * x.G + 0.168 * x.B let b = 0.272 * x.R + 0.534 * x.G + 0.131 * x.B { R=r; G=g; B=b } |> Conv.norm)
こっちはいずれ整理して github に上げる予定です。