グレースケールの変換式を逆算して変換した時に同じ値になるように調整すれば、どんな色合いになるのか確認してみます。
この記事は次の続編です。
- RGB値の合計が一定の画像変換 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)
結果は以下の通りです。
| 元画像 | 変換後 | 
|---|---|
![]()  | 
![]()  | 
かなり不自然な色合いになってしまいました。
RGB値の平均と、輝度ベース(grayF)とで、グレースケールに変換してみます。
| 平均 | 輝度ベース | 
|---|---|
![]()  | 
![]()  | 
輝度ベースで画像が消えてしまうことが確認できました。
※ 消えるのは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")



