LoginSignup
1
1

More than 5 years have passed since last update.

golangを使用して濃度変換を行う。

Last updated at Posted at 2016-12-27

https://github.com/kaepa3/imaging
とセミ連動中

今日のお題

  1. ヒストグラムの出力

テキストの方はついに「コードは空白になっています」とか言ってます。
タイトルからすると、そう言ったコードがあるのがウリの本じゃないのかと思いつつ
やってきます。

ヒストグラムの出力

とりあえず、グラフは以下のを参考にする。
http://takedajs.hatenablog.jp/entry/2016/04/03/094529

この辺

    "github.com/gonum/plot"
    "github.com/gonum/plot/plotter"
    "github.com/gonum/plot/plotutil"

を使うということでgo get

$ go get github.com/gonum/plot

bezierがないそうなので、追加

$ go get github.com/biogo/graphics/bezier

ソースコード

func (ef *effect) Histogram() {
    //  グラフの準備
    p, _ := plot.New()
    p.Title.Text = "histogram"
    p.X.Label.Text = "X"
    p.Y.Label.Text = "Y"
    //プロットの準備
    // 先に入れ物を作る
    const ColorSize = math.MaxUint8 + 1
    var dataSet [RGBAMax][ColorSize]uint16

    ef.imageLoop(ef.inputImage.Bounds(), func(x, y int) color.RGBA64 {
        //画素をそれぞれ数える
        r, g, b, a := ef.inputImage.At(x, y).RGBA()
        dataSet[IndexR][getIndex(r)]++
        dataSet[IndexG][getIndex(g)]++
        dataSet[IndexB][getIndex(b)]++
        dataSet[IndexA][getIndex(a)]++
        return color.RGBA64{0, 0, 0, 0}
    })
    // 各値グラフのプロット
    for key, v := range dataSet {
        if key == IndexA {
            continue
        }
        var line plotter.XYer
        plots := make(plotter.XYs, ColorSize)
        line = plots
        for i := 0; i < len(v); i++ {
            plots[i].X = float64(i)
            plots[i].Y = float64(v[i])
        }
        graph, _, _ := plotter.NewLinePoints(line)
        graph.Color = plotutil.Color(key)
        p.Add(graph)
        p.Legend.Add("line:"+strconv.Itoa(key), graph)

    }
    p.Save(5*vg.Inch, 5*vg.Inch, "sampleimage/line_sample.png")
    return
}

画像の構成要素をカウントしてプロットしているだけですね。

plotのライブラリ見ているとimage.Imageを返せそうだけど、対応はそのうち。
もしくは学習用だしやらない。し、それなら関数分けたい。

出力画像

line_sample.png

線形濃度変換

ヒストグラムで255階調だと100〜200までしか使ってない。そう言った場合に基準の100を0として、200を255としたらコントラストがはっきりするんでないの?っていう話だと思う。

$ Z' = Z_{max} \times \bigr(\frac{Z-a}{b-a}\bigl)$

数式眺めると今のZの値はaからbの何分の1でしょうか?という話ですね。
文字より数式眺めた方が良いですな。

ソースコード

aとbの値は今回固定としています。

func (ef *effect) LinearDensity() image.Image {
    con := func(val uint32) uint16 {
        var levelA uint16 = 0x10
        var levelB uint16 = 0xFF00

        v := math.MaxUint16 * (float64((uint16(val) - levelA)) / float64((levelB - levelA)))
        if v > math.MaxUint16 {
            v = math.MaxUint16
        } else if v < 0 {
            v = 0
        }
        return uint16(v)
    }
    return ef.imageLoop(ef.inputImage.Bounds(), func(x, y int) color.RGBA64 {
        r, g, _, a := ef.inputImage.At(x, y).RGBA()
        return color.RGBA64{con(r), con(g), con(g), uint16(a)}
    })
}

特出すべきは先の数式と比較してa以下の値、b以上の画素が含まれた場合のif文が追加されています。aが10としても8とかの数値がある可能性があるので、その対策ですね。
あと、float64にしないと桁落ちの誤差で出力が死にます。なるほどなという感じです。

出力画像

test_linerden.jpg
さっきのヒストグラムから青の要素がないのにフィルタしてるからノイズが乗ると。
実用を考えたらRGBそれぞれの値に対して設定値(aとb)が必要になるんでしょうな。

非線形な濃度変換

明るさだそうな。

$ Z' = Z_{max} \times \bigr(\frac{Z}{Z_{max}}\bigl)^γ$

γの値が1より大きいと暗くなって、それより小さければ明るくなると。
明るくする場合は元画像のrの値が2なら4にするみたいな感じだと思う。
全部ヒストグラム見ながら作業しろって話なんだろうけど、面倒だからまた今度。

ソースコード

conの内容変えるだけなので、そこだけ抽出

    con := func(val uint32) uint16 {
        gamma := 0.5
        v := math.MaxUint16 * math.Pow(float64(val)/float64(math.MaxUint16), gamma)
        if v > math.MaxUint16 {
            v = math.MaxUint16
        } else if v < 0 {
            v = 0
        }
        return uint16(v)
    }

出力結果

はい、明るくなりました。
test_unden.jpg

 コントラスト改善

Zmax/2より大きい場合は以下の式で与えられ
$ Z' = \frac{Z_{max}}{2} \times \sqrt{\frac{2Z - Z_{max}}{Z_{max}}} $
小さい時は
$ Z' = Z_{max} \times \bigr(\frac{Z}{Z_{max}}\bigl)^2$
とする。

ソースコード

con := func(val uint32) uint16 {
    maxUint := float64(math.MaxUint16)
    var v uint16
    if val > math.MaxUint16/2 {
        v = uint16((maxUint / 2.0) * (1.0 + math.Sqrt((2.0*float64(val)-maxUint)/maxUint)))
    } else {
        // 非線形濃度変換
        v = unlinerCon(val, 2)
    }
    if v > math.MaxUint16 {
        v = math.MaxUint16
    } else if v < 0 {
        v = 0
    }
    return uint16(v)
}

出力結果

test_contrast.jpg
この辺をスライダーとかでできるものを作らんと、今後だるいのかもしれない。
けどサンプルとニアリーだから良いという方向

ヒストグラムの平坦化

ここに関しては式をあてにするのが正解だった数式は以下にあります。
http://ipr20.cs.ehime-u.ac.jp/column/gazo_syori/chapter2.html

ではソースコード

// ヒストグラム平均化
func (ef *effect) AverageHistogram() image.Image {
    rect := ef.inputImage.Bounds()
    //ヒストグラムの取得
    _, _, _, hisL := ef.makeHistogramData()
    //lookupTBLの作成
    luTbl := createLookupTable(hisL, rect)
    //描画
    var buf effect
    buf.inputImage = ef.imageLoop(rect, func(x, y int) color.RGBA64 {
        r, g, b, a := ef.inputImage.At(x, y).RGBA()
        return color.RGBA64{luTbl[r], luTbl[g], luTbl[b], uint16(a)}
    })
    //  画像の作成
    buf.Histogram("ave histgram", "x", "y", "sampleimage/hist_ave.png")
    return buf.inputImage
}

変換テーブル作成

func createLookupTable(his [ColorWidth]uint16, rect image.Rectangle) (table [ColorWidth]uint16) {
    var sum, val uint16
    //平均画素数
    average := uint16(((rect.Size(). * rect.Size().Y) / ColorWidth) + 1)
    //平均値の計算
    for i, v := range his {
        sum += v
        val += sum / average
        sum %= average
        table[i] = val
    }
    return
}

ポイントは以下としている
1. (NTSC 系加重平均法)のヒストグラムから変換テーブルを作る
2. 変換テーブル(lookupTBL)は最初に書いた数式

くらい。

 出力画像

test_ave.png

ヒストグラム
hist_ave.png

まぁ、見やすくなってるから概ねよかろう
さらっと、ヒストグラムも改造し出してるし、
次章に入る前にきっちりリファクタリングですね。

1
1
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
1
1