おとといからの続き
ダウンロード先
最大長辺 最も長い辺(軸)を持つCubeを分割対象にする
最大体積 Cubeの体積が最大
最大分散Cube 色の分散値が最大、分散値は
ピクセルの数も加味したもの
最大分散辺 RGBごとの辺の分散値が最大、これも
ピクセル数を加味
今回はCubeの選択方法だけ変えて、分割場所と色の選択は固定
分割場所は辺の中央
色の選択はCubeの平均色なので使うパレットはPan1
4色
4色の時点だと
ピクセル数で選択するのはいまいちな結果になった
それ以外は全く同じ色になって花の赤が選択された、いいね
16色
なんだけどこの色数では足りなくてガタガタ
それ以外は青、赤、黄色、黒と違う色重視な感じでバランスが取れている
辺長と体積の2つはおなじに見えるけど微妙に違う
64色
64色まで増やすと
ピクセル数分割の青空もきれいになってきた
グラデーションや背景重視ならこれを選ぶ
256色
4色
辺長がいいねえ
分散の2つも赤が選択されると予想していたけど外れた
4色で最大分散辺での分割での色の選び方の違い
色の選び方を変えたら赤系も入っていた
でも逆に緑がないw
16色
背景と主題がはっきりしない込み入った画像でも
256色
グラデーション画像
256x256、使用色数65536色
4色
結果を見せられればなんとなくわかる
256色
縦横一定割合で変化している色が並んでいるから
選択されるCubeが同じになるのかなあ
4色
256色
128x128、16384色
白と黒のグラデーションの中に赤(255,0,0)
これから3色選ぶとすれば
白、黒、赤になればいい
期待どおり
4色
16色
16色
256色
どれも大差ないねえ
分割Cubeの選択法の感想
背景重視や256色とか色数が多ければ
最大
ピクセル数 >>>>> 辺長 ≒ 体積 > 分散Cube ≒ 分散辺
主題(主体、全景)重視や色数が4色とか少ないときはその反対
って感じかな
残念だったのが分散を使ったもの
もう少し他と差が出るかなあと思っていたし
分散の事自体よくわかっていなくて書くのに時間かかったから
使い方が間違っているかもしれない
もしかしたら
ピクセル数を加味しない分散のほうが良かったのかも?
Cubeクラスは少し変更
RGBそれぞれの平均値と分散値を持つフィールド追加
分散値は分割する時に計算して使う。
BitmapSourceを使うコンスト
ラクタは削除してColorのListからだけにした
ほんとはBitmapSourceからColorのListに変換して、それをColorのListを使うコンスト
ラクタに渡して作成したかったけど書き方がわからなくてこうなった
<summary>
</summary>
public class Cube
{
public byte MinRed;
public byte MinGreen;
public byte MinBlue;
public byte MaxRed;
public byte MaxGreen;
public byte MaxBlue;
public float RedPixAverage;
public float GreenPixAverage;
public float BluePixAverage;
public double RedVariance;
public double GreenVariance;
public double BlueVariance;
public double VarianceMax;
public List<Color> AllPixelsColor;
public List<Color> AllColor;
public int LengthMax;
public int LengthRed;
public int LengthGreen;
public int LengthBlue;
public Cube(List<Color> color)
{
byte lR = 255, lG = 255, lB = 255, hR = 0, hG = 0, hB = 0;
byte cR, cG, cB;
long rAdd = 0, gAdd = 0, bAdd = 0;
AllPixelsColor = new List<Color>();
foreach (Color item in color)
{
cR = item.R; cG = item.G; cB = item.B;
rAdd += cR; gAdd += cG; bAdd += cB;
AllPixelsColor.Add(Color.FromRgb(cR, cG, cB));
if (lR > cR) { lR = cR; }
if (lG > cG) { lG = cG; }
if (lB > cB) { lB = cB; }
if (hR < cR) { hR = cR; }
if (hG < cG) { hG = cG; }
if (hB < cB) { hB = cB; }
}
AllColor = AllPixelsColor.Distinct().ToList();
MinRed = lR; MinGreen = lG; MinBlue = lB;
MaxRed = hR; MaxGreen = hG; MaxBlue = hB;
LengthRed = 1 + MaxRed - MinRed;
LengthGreen = 1 + MaxGreen - MinGreen;
LengthBlue = 1 + MaxBlue - MinBlue;
LengthMax = Math.Max(LengthRed, Math.Max(LengthGreen, LengthBlue));
float count = color.Count;
RedPixAverage = rAdd / count;
GreenPixAverage = gAdd / count;
BluePixAverage = bAdd / count;
RedVariance = double.NaN;
GreenVariance = double.NaN;
BlueVariance = double.NaN;
VarianceMax = double.NaN;
}
private List<Color> GetColorList(BitmapSource source)
{
var bitmap = new FormatConvertedBitmap(source, PixelFormats.Pbgra32, null, 0);
var wb = new WriteableBitmap(bitmap);
int h = wb.PixelHeight;
int w = wb.PixelWidth;
int stride = wb.BackBufferStride;
byte[] pixels = new byte[h * stride];
wb.CopyPixels(pixels, stride, 0);
long p = 0;
var ColorList = new List<Color>();
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{
p = y * stride + (x * 4);
ColorList.Add(Color.FromRgb(pixels[p + 2], pixels[p + 1], pixels[p]));
}
}
return ColorList;
}
ほんとはこれをCubeクラスに書いておいて
public Cube(BitmapSource source)
{
var bitmap = new FormatConvertedBitmap(source, PixelFormats.Pbgra32, null, 0);
var wb = new WriteableBitmap(bitmap);
int h = wb.PixelHeight;
int w = wb.PixelWidth;
int stride = wb.BackBufferStride;
byte[] pixels = new byte[h * stride];
wb.CopyPixels(pixels, stride, 0);
long p = 0;
var ColorList = new List<Color>();
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{
p = y * stride + (x * 4);
ColorList.Add(Color.FromRgb(pixels[p + 2], pixels[p + 1], pixels[p]));
}
}
new Cube(ColorList);
}
こうしたいんだけど違うみたい、書き方がわからん
ここから分割対象の選択法5つ
最大長辺を持つCubeを分割対象に選択
CubeのListを渡して対象になるCubeのインデックスを返す
private int GetSelectIndexLongSideCube(List<Cube> cubeList)
{
int max = 0, index = 0;
for (int i = 0; i < cubeList.Count; ++i)
{
if (max < cubeList[i].LengthMax)
{
max = cubeList[i].LengthMax;
index = i;
}
}
return index;
}
いつも最大とか最小を得るときにはforとifを使っているんだけど
LINQとかを使えば
ラクなのかなあ、
LINQも使えるようになりたい
private int GetIndexSelectManyPidxelsCube(List<Cube> cubeList)
{
int max = 0, index = 0;
for (int i = 0; i < cubeList.Count; ++i)
{
if (max < cubeList[i].AllPixelsColor.Count)
{
max = cubeList[i].AllPixelsColor.Count;
index = i;
}
}
return index;
}
最大体積のCubeを分割対象に選択
private int GetSelectIndexCapacityMaxCube(List<Cube> cubeList)
{
int max = 0, index = 0, capa = 0;
Cube c;
for (int i = 0; i < cubeList.Count; ++i)
{
c = cubeList[i];
capa = (c.MaxRed - c.MinRed) * (c.MaxGreen - c.MinGreen) * (c.MaxBlue - c.MinBlue);
if (max < capa)
{
max = capa;
index = i;
}
}
return index;
}
ここまでは普通、辺の長さや、カウント、直方体の体積は小学生でもわかる
次は分散
これは文系の高校では難しい、というか習わないから、えっ知らないってなる、なった、でも今度からは文系高校でも教えてくれるらしい、いいね!
分散で得られる値は対象の散らばり具合を表して、大きければそれだけ散らかっている、なので分散値が大きいものを分割していけば、散らばりの少ないCubeになる、つまり同じ色がまとまったCubeになるので、減色に都合のいいパレットができる、かも
分散は要素と平均値の差を2乗したのを合計して要
素数で割る
計算手順自体は簡単だけどめんどくさすぎる
これを手動で計算するのはムリだけどパソコン使えば
ラク、相性がいい
さらにエクセルには分散の関数があってVARPっての
これ使えば簡単に求めることができる
private double[] GetRGBごとの分散(Cube iCube)
{
if (double.IsNaN(iCube.VarianceMax) == true)
{
double rVar = 0, gVar = 0, bVar = 0;
Color pxColor;
long count = 0;
count = iCube.AllPixelsColor.Count;
rVar = 0; gVar = 0; bVar = 0;
for (int j = 0; j < count; ++j)
{
pxColor = iCube.AllPixelsColor[j];
rVar += Math.Pow(pxColor.R - iCube.RedPixAverage, 2f);
gVar += Math.Pow(pxColor.G - iCube.GreenPixAverage, 2f);
bVar += Math.Pow(pxColor.B - iCube.BluePixAverage, 2f);
}
iCube.RedVariance = rVar / count;
iCube.GreenVariance = gVar / count;
iCube.BlueVariance = bVar / count;
iCube.VarianceMax = Math.Max(iCube.RedVariance, Math.Max(iCube.GreenVariance, iCube.BlueVariance));
}
return new double[] { iCube.RedVariance, iCube.GreenVariance, iCube.BlueVariance };
}
AllPixelsColorがCubeにあるすべてのピクセルのColor
今回はこれ全部を計算しているけど、もしかしたら
ピクセルの数は無視して純粋に色の数だけで分散を求めたほうが良かったのかも
上のを使って
分散が最大のCubeを分割対象に選択
private double[] GetRGBごとの分散(Cube iCube)
{
if (double.IsNaN(iCube.VarianceMax) == true)
{
double rVar = 0, gVar = 0, bVar = 0;
Color pxColor;
long count = 0;
count = iCube.AllPixelsColor.Count;
rVar = 0; gVar = 0; bVar = 0;
for (int j = 0; j < count; ++j)
{
pxColor = iCube.AllPixelsColor[j];
rVar += Math.Pow(pxColor.R - iCube.RedPixAverage, 2f);
gVar += Math.Pow(pxColor.G - iCube.GreenPixAverage, 2f);
bVar += Math.Pow(pxColor.B - iCube.BluePixAverage, 2f);
}
iCube.RedVariance = rVar / count;
iCube.GreenVariance = gVar / count;
iCube.BlueVariance = bVar / count;
iCube.VarianceMax = Math.Max(iCube.RedVariance, Math.Max(iCube.GreenVariance, iCube.BlueVariance));
}
return new double[] { iCube.RedVariance, iCube.GreenVariance, iCube.BlueVariance };
}
分散が最大の辺を持つCubeを分割対象に選択
private int GetSelectIndexOfMaxVarianceSide(List<Cube> cubeList)
{
int index = 0;
double max = 0;
double variance = 0;
for (int i = 0; i < cubeList.Count; ++i)
{
double[] rgbVariance = GetRGBごとの分散(cubeList[i]);
variance = Math.Max(rgbVariance[0], Math.Max(rgbVariance[1], rgbVariance[2]));
if (max < variance)
{
max = variance;
index = i;
}
}
return index;
}
<summary>
</summary>
<param name="source"></param>
<param name="splitCount"></param>
<param name="GetIndexOfSplitCube"></param>
<param name="SplitBy"></param>
<returns></returns>
private List<Cube> SplitCube(
Cube cube,
int splitCount,
Func<List<Cube>, int> GetIndexOfSplitCube,
Func<Cube, List<Cube>> SplitBy)
{
int loopCount = 1;
var cubeList = new List<Cube>() { cube };
var tempCubeList = new List<Cube>();
var completionList = new List<Cube>();
int index;
while (splitCount > loopCount && cubeList.Count > 0)
{
index = GetIndexOfSplitCube(cubeList);
tempCubeList.Clear();
tempCubeList.AddRange(SplitBy(cubeList[index]));
if (tempCubeList[0].AllPixelsColor.Count == 0 || tempCubeList[1].AllPixelsColor.Count == 0)
{
if (tempCubeList[0].AllPixelsColor.Count == 0)
{
completionList.Add(tempCubeList[1]);
}
else { completionList.Add(tempCubeList[0]); }
}
else
{
cubeList.AddRange(tempCubeList);
}
cubeList.RemoveAt(index);
loopCount++;
}
cubeList.AddRange(completionList);
return cubeList;
}
private List<Cube> SplitCube(
List<Color> listColors,
int splitCount,
Func<List<Cube>, int> GetIndexOfSplitCube,
Func<Cube, List<Cube>> SplitBy)
{
return SplitCube(new Cube(listColors), splitCount, GetIndexOfSplitCube, SplitBy);
}
分割ループのところを直した
それ以上分割できないCubeを分割した場合、一方に全ての
ピクセルが入って、もう一方は
ピクセルが0個になって返ってくる。0個の方は破棄していたんだけど全部入った方はそのままにしていて、次のループでまた分割処理に入るっていうムダなことになっていたので、どちらかかが0個になって返ってきたらそれ以上分割できないと判断して、別のListにストックするようにした
参照したところ
アプリダウンロード(ヤフーボックス)
20180315_メディアンカット法での色の選び方1.1.zip
関連記事
2018/03/20はおととい