最終的には 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 に上げる予定です。











