k平均法を使った減色パレットの作成時に手抜きをして処理時間短縮
手を抜くのは重要
どこまで手を抜いても気づかないかを確かめてみるアプリ作った、確かみてみろ!
手抜きの方法は簡単で調査する
ピクセル数に上限を付けるだけ
上の場合だと
手抜きパレットは画像の
ピクセル49152個の内、1000個をランダムに選択した
ピクセルだけを使ってパレットを作成する
真面目パレットは常にすべての
ピクセルを使ってパレットを作成する
それぞれの時間は
0秒570 真面目パレット
0秒014 手抜きパレット
大きく違うけど1秒以下ならどっちでもいいかなあ
こういう小さい画像なら真面目パレットでもいいと思う
できたパレットの色はほとんど一緒
それぞれのパレットで減色
もともとk平均法はランダムの要素があるから
真面目に作ってもこれくらいの揺れはあると思う
100
ピクセルまで落とすと赤が入らないことが多くなった
真面目パレットでも最初の1回目は赤を外しているけど
手抜きパレットは要らない青系が入っているよりマシ
その分時間は0.003と速いけど体感できないからなあ
ってことで手を抜いても1000
ピクセルは使ったほうがいい
大きめの画像
8.896秒と0.013秒 = 8.896/0.013≒684、最大で約700倍の差がついた
パレットのできは比べれば真面目パレットのほうがいいねえ、くらいかな
偶然かもしれないけど手抜きパレットのほうがいいと思う
パレットの色数を8色増やしてみる
20.570秒と0.023秒 = 20.570/0.023≒894、最大約900倍
更に差が開いた
パレットを比べても3色のときと同様、そんなに差はないかなあ
k平均法だと毎回変わるからねえ
実際減色してみても大差ない
これで20秒かかるのと一瞬で終わるなら手を抜かない手はない
遊びを大きくしてみる
3から20に変更
遊びを3から20に変更
ループ回数が減るので速くなる
遊び100
さすがに1回じゃ少なすぎるw
時間やパレットの出来具合から
手を抜くなら遊びを大きくするより
ピクセルを絞ったほうが良さそう
大きい画像
今ではこれくらいでも大きいとは言わないかな?
遊びを3に戻して計測
続行を押して再開
結果
1分54秒368と0.052秒=(1*60+54.368)/0.052≒2199
約2200倍
3145728/1000≒3146
こんな感じで
結果のパレットのできは変わんないねえ
花粉やミツバチの黄色がないのが残念
それぞれのパレットで減色
やっぱりどちらも変わんない
手抜きで十分
k平均法のランダム性を生かして
気に入ったパレットができるまでリセマラ
ここまで速いと今度は減色処理の時間が気になってくる
この大きさだと13秒もかかっている
k平均法で減色パレットを作成するとき
画像のすべてのピクセルからではなく、ランダムに選んだ1000個のピクセルからでも十分なものができる
処理時間はピクセル数に比例するので大きな画像ほど差が出る、2048x1356程度の画像の大きさになると差が2000倍にもなる
手抜きバンザイ
<summary>
</summary>
<param name="source"></param>
<param name="colorCount"></param>
<param name="limitPixel"></param>
<param name="margin"></param>
<param name="textBlock"></param>
<returns></returns>
private Color[] GetPalette(BitmapSource source, int colorCount, int limitPixel, int margin, TextBlock textBlock)
{
Color[] pixelColors;
if (limitPixel == 0)
{
pixelColors = GetAllPixelsColor(source);
}
else
{
pixelColors = GetRandomPixelsColor(source, limitPixel);
}
Color[] oldPalette = GetRandomColorPalette(colorCount);
Color[] nextPalette = new Color[colorCount];
int loopCount = 0;
while (loopCount < 100)
{
loopCount++;
nextPalette = GetNewPalette(oldPalette, pixelColors);
if (GetDiffPalettes(oldPalette, nextPalette) < margin) { break; }
for (int i = 0; i < oldPalette.Length; ++i)
{
oldPalette[i] = nextPalette[i];
}
}
if (textBlock != null)
{
textBlock.Text = $"ループ回数:{loopCount}";
}
return nextPalette;
}
private double GetDiffPalettes(Color[] bPalette, Color[] nPalette)
{
double diff = 0;
for (int i = 0; i < bPalette.Length; ++i)
{
diff += GetColorDistance(bPalette[i], nPalette[i]);
}
diff /= bPalette.Length;
return diff;
}
private Color[] GetNewPalette(Color[] palette, Color[] pixelColors)
{
List<Color>[] colorList = new List<Color>[palette.Length];
for (int i = 0; i < palette.Length; ++i)
{
colorList[i] = new List<Color>();
}
double distance, min;
int pIndex;
Color nowColor;
for (int i = 0; i < pixelColors.Length; i++)
{
nowColor = pixelColors[i];
pIndex = 0;
min = GetColorDistance(nowColor, palette[0]);
for (int j = 1; j < palette.Length; j++)
{
distance = GetColorDistance(nowColor, palette[j]);
if (min > distance)
{
min = distance;
pIndex = j;
}
}
colorList[pIndex].Add(nowColor);
}
Color[] newPalette = new Color[palette.Length];
for (int i = 0; i < newPalette.Length; i++)
{
newPalette[i] = GetAverageGolor(colorList[i]);
}
return newPalette;
}
private Color[] GetRandomColorPalette(int paletteCapacity)
{
Color[] colors = new Color[paletteCapacity];
Random random = new Random();
byte[] r = new byte[3];
for (int i = 0; i < colors.Length; ++i)
{
random.NextBytes(r);
colors[i] = Color.FromRgb(r[0], r[1], r[2]);
Console.WriteLine(colors[i].ToString());
}
return colors;
}
private Color[] GetAllPixelsColor(BitmapSource source)
{
var wb = new WriteableBitmap(source);
int h = wb.PixelHeight;
int w = wb.PixelWidth;
int stride = wb.BackBufferStride;
byte[] pixels = new byte[h * stride];
wb.CopyPixels(pixels, stride, 0);
int p = 0;
Color[] color = new Color[h * w];
for (int i = 0; i < color.Length; ++i)
{
p = i * 4;
color[i] = Color.FromRgb(pixels[p + 2], pixels[p + 1], pixels[p]);
}
return color;
}
private Color[] GetRandomPixelsColor(BitmapSource source, int limit)
{
var wb = new WriteableBitmap(source);
int h = wb.PixelHeight;
int w = wb.PixelWidth;
int stride = wb.BackBufferStride;
byte[] pixels = new byte[h * stride];
wb.CopyPixels(pixels, stride, 0);
if (limit > w * h)
{
return GetAllPixelsColor(source);
}
Color[] color = new Color[limit];
Random random = new Random();
int p = 0;
int x, y;
for (int i = 0; i < limit; ++i)
{
x = random.Next(w);
y = random.Next(h);
p = y * stride + (x * 4);
color[i] = Color.FromRgb(pixels[p + 2], pixels[p + 1], pixels[p]);
}
return color;
}
アプリダウンロード
変換前の画像に戻すのと変換した画像を保存するボタンを付けた
関連記事
2018/03/14
2018/3/4