LoginSignup
3
0

More than 5 years have passed since last update.

RGB値の合計が一定の画像変換

Last updated at Posted at 2016-11-17

明度と輝度に関する記事を見て、RGB値の合計が一定になるような画像変換を確認したくなったので、実験してみました。

参考にした記事です。

この記事には続編があります。

この記事には関連記事があります。

色の変換

RGBの合計が常に一定になるように変換する関数を定義します。

let adjust v (c:Color) =
    let r, g, b = int c.R, int c.G, int c.B
    let sum = r + g + b
    let r2, g2, b2 =
        if sum = 0 then
            v / 3, v / 3, v / 3
        else
            r * v / sum, g * v / sum, b * v / sum
    Color.FromArgb(r2, g2, b2 + (v - (r2 + g2 + b2)))

※ 誤差を青成分に押し付けています。

これを受け取って全ピクセルに適用する関数を定義します。

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 bmp = new Bitmap("Lenna.bmp")
let adj = bmp |> conv (adjust 255)
adj.Save("Lenna-adj.png", Imaging.ImageFormat.Png)

※ BMPファイルはQiitaに貼れませんが、可逆圧縮するためJPEGではなくPNGにしています。

結果は以下の通りです。

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

かなり見づらいですが、痕跡は残っています。

グレースケール

単純に平均を取る方法でのグレースケール変換を実装します。

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

比較対象として、輝度を考慮した変換も実装します。

let gray (c:Color) =
    let c = float c.R * 0.299 + float c.G * 0.587 + float c.B * 0.114 |> int
    Color.FromArgb(c, c, c)

これらを使って先ほど生成した画像を処理します。

(adj |> conv average).Save("Lenna-adj-avg.png" , Imaging.ImageFormat.Png)
(adj |> conv gray   ).Save("Lenna-adj-gray.png", Imaging.ImageFormat.Png)
平均 輝度ベース
Lenna-adj-avg.png Lenna-adj-gray.png

平均を取ると画像が消えてしまうことが確認できました。

比較として元画像にも適用してみます。

(bmp |> conv average).Save("Lenna-avg.png" , Imaging.ImageFormat.Png)
(bmp |> conv gray   ).Save("Lenna-gray.png", Imaging.ImageFormat.Png)
平均 輝度ベース
Lenna-avg.png Lenna-gray.png

平均でもそれほど悪くないように見えますが、輝度ベースに比べてのっぺりした印象です。

まとめ

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

RGB.fsx
#r "System.Drawing"

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 adjust v (c:Color) =
    let r, g, b = int c.R, int c.G, int c.B
    let sum = r + g + b
    let r2, g2, b2 =
        if sum = 0 then
            v / 3, v / 3, v / 3
        else
            r * v / sum, g * v / sum, b * v / sum
    Color.FromArgb(r2, g2, b2 + (v - (r2 + g2 + b2)))

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

let gray (c:Color) =
    let c = float c.R * 0.299 + float c.G * 0.587 + float c.B * 0.114 |> 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 save fn (bmp:Bitmap) =
    bmp.Save(fn + ".png", Imaging.ImageFormat.Png)
    bmp.Dispose()

let src = "Lenna.bmp"

let bmp = new Bitmap(src)
bmp |> conv average |> save (suffix src "-avg" )
bmp |> conv gray    |> save (suffix src "-gray")

let adj = bmp |> conv (adjust 255)
adj |> conv average |> save (suffix src "-adj-avg" )
adj |> conv gray    |> save (suffix src "-adj-gray")

bmp |> save (suffix src ""    )
adj |> save (suffix src "-adj")
3
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
3
0