画像認識のプレ加工に F# を使う

最終的には OpenCV と組み合わせるはずなのですが、いちいち C++ で組むのも面倒なので、事前加工の部分を F# で組みます。

image

こんな画像をグレースケールに変えたり、2値化するだけですね。

imageimage

imageimage

■画像加工にパイプを使う

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

カテゴリー: F# パーマリンク