LoginSignup
2
0

More than 5 years have passed since last update.

グレースケールで消える画像に変換

Last updated at Posted at 2016-11-17

グレースケールの変換式を逆算して変換した時に同じ値になるように調整すれば、どんな色合いになるのか確認してみます。

この記事は次の続編です。

変換

グレースケールへの変換を行う関数です。

let grayF (r, g, b) =
    r * 0.299 + g * 0.587 + b * 0.114

Colorからfloatのタプルに変換する関数です。

let toFloat (c:Color) =
    float c.R, float c.G, float c.B

グレースケールに変換したとき1.0になるような比例定数を求めて色を調整する関数です。

let adjust2 (c:Color) =
    let r, g, b = toFloat c
    let c = grayF (r, g, b)
    r / c, g / c, b / c

画像全体に対してadjust2を適用してRGB値の配列を生成する関数です。

let adjust2rgb (bmp:Bitmap) =
 [| for y = 0 to bmp.Height - 1 do
    for x = 0 to bmp.Width  - 1 do
    yield bmp.GetPixel(x, y) |> adjust2 |]

RGBの最大値を指定した値(v)に揃えた画像を生成する関数です。

let conv2 v (bmp:Bitmap) =
    let bmp = new Bitmap(bmp)
    let rgb = adjust2rgb bmp
    let conv =
        let max =
            rgb
            |> Seq.map (fun (r, g, b) -> r |> max g |> max b)
            |> Seq.max
        if max = 0. then
            let c = Color.FromArgb(v, v, v)
            fun _ -> c
        else
            let a = float v / max
            fun (r, g, b) ->
                Color.FromArgb(a * r |> int, a * g |> int, a * b |> int)
    let en = (rgb :> IEnumerable<float * float * float>).GetEnumerator()
    for y = 0 to bmp.Height - 1 do
        for x = 0 to bmp.Width - 1 do
            if en.MoveNext() then
                bmp.SetPixel(x, y, conv en.Current)
    bmp

次のように使います。

let bmp = new Bitmap("Lenna.bmp")
let adj2 = bmp |> conv2 255
adj2.Save("Lenna-adj2.png", Imaging.ImageFormat.Png)

結果は以下の通りです。

元画像 変換後
Lenna.png Lenna-adj2.png

かなり不自然な色合いになってしまいました。

RGB値の平均と、輝度ベース(grayF)とで、グレースケールに変換してみます。

平均 輝度ベース
Lenna-adj2-avg.png Lenna-adj2-gray.png

輝度ベースで画像が消えてしまうことが確認できました。

※ 消えるのはgrayFの変換式に依存しています。別の変換式(平均など)を使用すれば消えません。

まとめ

コードをまとめて整理したものを掲載します。

RGB2.fsx
#r "System.Drawing"

open System.Collections.Generic
open System.Drawing
open System.IO

let suffix fn sfx =
    let dir = Path.GetDirectoryName fn
    let fn2 = Path.GetFileNameWithoutExtension fn
    Path.Combine(dir, fn2 + sfx)

let average (c:Color) =
    let c = (int c.R + int c.G + int c.B) / 3
    Color.FromArgb(c, c, c)

let toFloat (c:Color) =
    float c.R, float c.G, float c.B

let grayF (r, g, b) =
    r * 0.299 + g * 0.587 + b * 0.114

let gray (c:Color) =
    let c = c |> toFloat |> grayF |> int
    Color.FromArgb(c, c, c)

let conv f (bmp:Bitmap) =
    let bmp = new Bitmap(bmp)
    for y = 0 to bmp.Height - 1 do
        for x = 0 to bmp.Width - 1 do
            bmp.SetPixel(x, y, f (bmp.GetPixel(x, y)))
    bmp

let adjust2 (c:Color) =
    let r, g, b = toFloat c
    let c = grayF (r, g, b)
    r / c, g / c, b / c

let adjust2rgb (bmp:Bitmap) =
 [| for y = 0 to bmp.Height - 1 do
    for x = 0 to bmp.Width  - 1 do
    yield bmp.GetPixel(x, y) |> adjust2 |]

let conv2 v (bmp:Bitmap) =
    let bmp = new Bitmap(bmp)
    let rgb = adjust2rgb bmp
    let conv =
        let max =
            rgb
            |> Seq.map (fun (r, g, b) -> r |> max g |> max b)
            |> Seq.max
        if max = 0. then
            let c = Color.FromArgb(v, v, v)
            fun _ -> c
        else
            let a = float v / max
            fun (r, g, b) ->
                Color.FromArgb(a * r |> int, a * g |> int, a * b |> int)
    let en = (rgb :> IEnumerable<float * float * float>).GetEnumerator()
    for y = 0 to bmp.Height - 1 do
        for x = 0 to bmp.Width - 1 do
            if en.MoveNext() then
                bmp.SetPixel(x, y, conv en.Current)
    bmp

let save fn (bmp:Bitmap) =
    bmp.Save(fn + ".png", Imaging.ImageFormat.Png)
    bmp.Dispose()

let src = "Lenna.bmp"

let bmp = new Bitmap(src)
let adj2 = bmp |> conv2 255
adj2 |> conv average |> save (suffix src "-adj2-avg" )
adj2 |> conv gray    |> save (suffix src "-adj2-gray")
adj2                 |> save (suffix src "-adj2")
2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0