午後わてんのブログ

ベランダ菜園とWindows用アプリ作成(WPFとC#)

単純減色(ポスタライズ?)試してみた、WPFとC#

今回は
WPF、普通の写真画像を8色に減色 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15342796.html
この記事の続き
8色に減色だったのを27色,64,512、…ってしてみた
8色のときはRGBそれぞれの色の強さ0か255を2階調(分割)して0か255に分けて
その組み合わせは
R,G,B
黒 (0, 0, 0)
(255,0, 0)
緑 (0, 255, 0)
青 (0, 0, 255)
黄色 (255,255, 0)
水色 (0, 255,255)
赤紫 (255,0, 255)
(255,255, 255)
の8色だった
今回はこの階調(分割)数を増やして
3階調なら0,127,255の3つの強さ
R,G,B
黒 (0, 0, 0)
赤黒 (127,0, 0)
(255,0, 0)
0と255の間に中間の127が入る
RGBそれぞれで3階調で組み合わせが合計27色
4階調なら0,85,170,255で64色、5階調で512色…
 
イメージ 1
元の画像は普通のjpeg画像なので
1677万色
 
イメージ 2
RGB各2階調の全8色は前回と同じ
8色と言っても元の画像に赤、緑、黄色、赤紫はないから
実質は白、黒、青、水色の4色
 
イメージ 3
3階調27色、これもファミコンっぽい
8色のほうがいいかなあ
 
イメージ 4
4階調64色、メガドライブな感じ、いやもうちょっときれいだったかw
3階調よりはいいけど、まだ違う
 
イメージ 5
5階調125色、これもメガドライブだなあ
 
イメージ 6
6階調216色
PCエンジン…はもっときれいだったかな
 
いいね
 
イメージ 8
16階調4096色
このへんまで来ると元の画像と差がなくなってくる
 
イメージ 9
128階調で約210万色
元の画像と見分けがつかない
 
イメージ 10
256階調、1677万色は元の画像と同じ色数なので
全く同じはず
 
RGBそれぞれを2階調
イメージ 14
区切り位置(閾値)127.5は128かも
0から255は256階調なのでそれを2階調にするときは
256/2=128
128が一つの区切り位置(閾値)になる
イメージ 11
閾値128で0か255を分けているのが水色のところ
0から255を0か255の2階調に変換する(分ける)から閾値はその中間の128にしている
0 になるのは0から127の 範囲は128
255 になるのは128から255の 範囲は128
ちょうど2等分できている
 
 
3階調の場合
イメージ 13
閾値の85.3と171は256/3=85.333333…
これが一つ分の区切り位置になるから
1つ目の区切り位置は85.3*1=85.3
2つめの区切り位置は85.3*2=170.6
なんか微妙に画像と違うけど修正めんどくさい
0 になるのは0から85 範囲は86
127 になるのは86から170 範囲は85
255 になるのは171から255 範囲は85
これでだいたい3等分できている
イメージ 12
4階調
イメージ 15
こんなふうに階調ごとに書くのはめんどくさいので
 
 
色の値(強さ)と階調数を渡して変換した色の値(強さ)を返す
イメージ 16
階調数3で値が100の色を渡すと
frequency=256/3=85.3333…
if(100<85.3*1)=false
if(100<85.3*2)=True
v=255/(3-1)=127.5
return 127.5*(2-1)=127.5をbyte型にキャストで127
127を返す
 
階調数5で値200を渡すと
frequency=256/5=51.2
if(200<51.2*1)=false
if(200<51.2*2)=false
if(200<51.2*3)=false
if(200<51.2*4)=True
v=255/(5-1)=63.75
return 63.75*(4-1)=191.25をbyte型にキャストで191
191を返す
 
256を渡された階調数で割っているのは
閾値の一区切りの値
を求めるためなのはさっきの通りで
もう一つの255を階調数-1で割っているのは
変換後の値のための一区切りの値
3階調
イメージ 18
255/(3-1)=127.5
127.5*0=0
127.5*1=127.5
127.5*2=255
これで変換後の3階調の値になる
 
こんな感じになるけど今見たらIFのところの不等号は<=のほうがいいかなあ
これを使って
イメージ 17
こう
これで階調ごとに書かなくて済むようになった!
これでできたんだけどもっと速く!
 
ポスタリゼーション(階調変更)
http://www.sm.rim.or.jp/~shishido/post.html
こちらで紹介されている方法
僕のアタマでは全部は理解できなかったので部分的に参考にして
//対応表を作成しておいて、それに当てはめて判定、速い
private BitmapSource GensyokuNumeric2Table(BitmapSource source, int division)
{
    //変換対応表取得
    byte[] converter = GetConverterArray(division);

    var wb = new WriteableBitmap(source);
    int h = wb.PixelHeight;
    int w = wb.PixelWidth;
    int stride = wb.BackBufferStride;
    byte[] pixles = new byte[h * stride];
    wb.CopyPixels(pixles, stride, 0);
    long p = 0;

    for (int y = 0; y < h; ++y)
    {
        for (int x = 0; x < w; ++x)
        {
            p = y * stride + (x * 4);
            //対応表に当てはめて色変換
            pixles[p + 2] = converter[pixles[p + 2]];
            pixles[p + 1] = converter[pixles[p + 1]];
            pixles[p + 0] = converter[pixles[p + 0]];
        }
    }
    wb.WritePixels(new Int32Rect(0, 0, w, h), pixles, stride, 0);
    return wb;
}

/// <summary>
/// 変換対応表作成
/// </summary>
/// <param name="division">分割数(階調数)</param>
/// <returns></returns>
private byte[] GetConverterArray(int division)
{
    //範囲           
    float frequency = 256f / division;
    float[] range = new float[division + 1];
    for (int i = 0; i < range.Length; ++i)
    {
        range[i] = i * frequency;
    }

    //指定する値
    frequency = 255f / (division - 1);
    byte[] color = new byte[division];
    for (int i = 0; i < color.Length; ++i)
    {
        color[i] = (byte)(i * frequency);
    }

    //元の256階調全てに対する変換結果の配列作成、対応表
    //配列のindexが元の色の強さでvalueが変換後の色の強さになる
    byte[] converter = new byte[256];
    int j = 0;
    for (int i = 0; i < 256; ++i)
    {
        if (i >= range[j + 1]) { j++; }
        converter[i] = color[j];
    }
    return converter;
}
 
 
 
 
イメージ 19
さっきの方法ではピクセルごとに毎回最初から閾値を求めてから判定して変換だった
これは最初に変換用の一覧表を作っておいて、あとはそれを使って判定するだけ
3階調なら0から
0 になるのは0から85
127 になるのは86から170
255 になるのは171から255
逆に言うと
0から85は 0
86から170は 127
171から255は 255
なので配列を使って一覧表を作ると
indexをもとの値
valueを変換後の値にして
素数は0から255なので256個
って見たほうが早い
イメージ 20
converterってのがそれ、中を見ると
 
イメージ 21
いっぱい入っている
イメージ 22
85までは0で86から127が入っていて
次の区切りは170…
 
イメージ 23
170まで127
それ以降は255が入っている
こうして作った一覧表を使って
 
イメージ 24
元画像の色の配列pixels
indexはp+2
今のpは0なのでpixels[2]を変換する
 
 
イメージ 25
pixels[2]の値は34
これを一覧表のconverterのindexにして中の値を
入れれば変換完了になる
 
 
イメージ 26
converter[34]の中の値は0
 
 
イメージ 27
converter[34]の値0がpixels[2]に入って
変換完了!
 
イメージ 28
他の値も同じように一覧表を使って変換
75は0へ
 
イメージ 29
155は127に変換
 
こんなふうに最初に変換の一覧表を作ると変換処理が簡単になるので速度が上がる
いやーよく思いつくなあ、スゴイ
 
 
どれくらい速くなるかなんだけど画像の大きさ(ピクセル数)と指定する階調数によるはずで
ピクセル数、階調数ともに大きくなるほど差が大きくなって
2048x1536ピクセルの画像を変換する時
最初の方式だと
2階調 0.5秒くらい
8階調 1秒弱
16階調 1.5秒くらい
128階調 5秒くらい
256階調 10秒
だったのが
一覧表方式だと
2階調 一瞬
256階調 一瞬!
同じ結果を得るにしても方法に依っては、こんなに差が出るのが面白いねえ
 
イメージ 30
ポスタライズって言うみたいねえ、減色してベッタリしたような画像にするの
単純減色って呼んでた
 
イメージ 31
グレースケールでも128階調は元の画像と見分けつかないなあ
イメージ 32
32,48はわかるけど64階調は注意して見ないとわかんない
 
コード
gogowaten/20180226forMyBlog
https://github.com/gogowaten/20180226forMyBlog
わてんはGitHubがわからん
ローカルのリポジトリになっているのをGitHubにコピー(クローン?)して同期したいんだけどググってもGitHubをローカルにコピーばかり出てくる、逆なんだよなあ
 
アプリダウンロード先
ここの20180224_.zip
 
つづきは2日後
単純減色(ポスタライズ)にオーダード(パターン)ディザリング、WPFC# ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15391499.html
 
 
関連記事
1ヶ月前
WPF、普通の写真画像を8色に減色 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15342796.html
 
2018/03/02は4日後
単純減色と誤差拡散とディザリング ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15394008.html