4色に絞っても再現度高い
同じ色数指定でも違う結果に
k平均法はそういうものみたい
k平均法、色数4指定の時
- 最初にランダムで色を4つ作る、これが最初のパレット
- 画像のピクセルの色とパレットの4色と比較、一番近い色に画像の色を分けて4つのグループを作る、これをすべてのピクセルで行う
- グループの平均色をパレットの色に置き換えて、2に戻るの繰り返し
上限は100回、
停止条件は元のパレット色とグループの平均色の差が1未満になった時、とか
色の距離、色の差
今回は一番単純な方法でRGBの各色の差の2乗を足したのをルート
比較するだけなら最後のルートの計算は必要ないかな
色1と色2の距離なら
(色1のR-色2のR)^2+(色1のG-色2のG)^2+(色1のB-色2のB)^2
これのルート
色1(155,75,34)、色2(181,147,5)の距離は
(155-182)^2+(75-147)^2+(34-5)^2
729+5184+841=6754
√6754≒81.86
81.86
うーん、
Google 日本語入力はべき乗の計算はできるけどルートの計算はできないみたい
平均色
これも単純に全
ピクセルのRGBそれぞれを足したのを
ピクセル数で割っただけで計算した
デザイン画面
<Window x:Class="_20180226_代表色選択k平均法.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:_20180226_代表色選択k平均法"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="200"/>
</Grid.ColumnDefinitions>
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
<Image Name="MyImage" UseLayoutRounding="True" Stretch="None"/>
</ScrollViewer>
<StackPanel Grid.Column="1">
<StackPanel Orientation="Horizontal">
<TextBlock Text="色数" VerticalAlignment="Center" Margin="4,0" FontSize="18"/>
<TextBox Name="NumericTextBox" VerticalContentAlignment="Center" HorizontalContentAlignment="Right"
Text="{Binding ElementName=NumericScrollBar, Path=Value, UpdateSourceTrigger=PropertyChanged}"
Width="40" FontSize="18"/>
<ScrollBar Name="NumericScrollBar" Value="3" Minimum="1" Maximum="256" SmallChange="1" LargeChange="1"
RenderTransformOrigin="0.5,0.5">
<ScrollBar.RenderTransform>
<RotateTransform Angle="180"/>
</ScrollBar.RenderTransform>
</ScrollBar>
</StackPanel>
<Button Name="Button1" Content="button1"/>
<TextBlock Name="TextBlockLoopCount" Text="loop count"/>
<WrapPanel Name="MyWrapPanel">
</WrapPanel>
</StackPanel>
</Grid>
</Window>
<feff>using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.IO;
namespace _20180226_代表色選択k平均法
{
<summary>
</summary>
public partial class MainWindow : Window
{
BitmapSource OriginBitmap;
string ImageFileFullPath;
Border[] MyPalette;
const int MAX_PALETTE_COLOR_COUNT = 20;
public MainWindow()
{
InitializeComponent();
this.Title = this.ToString();
this.AllowDrop = true;
this.Drop += MainWindow_Drop;
Button1.Click += Button1_Click;
AddBorders();
}
private void Button1_Click(object sender, RoutedEventArgs e)
{
PalettePanColorDel();
if (OriginBitmap == null) { return; }
MyImage.Source = ReduceColor減色(OriginBitmap, (int)NumericScrollBar.Value);
}
private void PalettePanColorDel()
{
for (int i = 0; i < MAX_PALETTE_COLOR_COUNT; ++i)
{
MyPalette[i].Background = null;
}
}
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;
}
<summary>
</summary>
<param name="source"></param>
<param name="colorCount"></param>
<returns></returns>
private BitmapSource ReduceColor減色(BitmapSource source, int colorCount)
{
string neko = "start" + "\n";
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);
long p = 0;
Color myColor;
int pIndex;
double distance, min, diff = 0;
Color[] palette = GetRandomColorPalette(colorCount);
for (int i = 0; i < palette.Length; ++i)
{
neko += palette[i].ToString() + "\n";
}
List<Color>[] colorList = new List<Color>[palette.Length];
for (int j = 0; j < 100; ++j)
{
for (int i = 0; i < palette.Length; ++i)
{
colorList[i] = new List<Color>();
}
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{
p = y * stride + (x * 4);
myColor = Color.FromRgb(pixels[p + 2], pixels[p + 1], pixels[p]);
pIndex = 0;
distance = GetColorDistance(myColor, palette[0]);
min = distance;
for (int i = 1; i < palette.Length; ++i)
{
distance = GetColorDistance(myColor, palette[i]);
if (min > distance)
{
min = distance;
pIndex = i;
}
}
colorList[pIndex].Add(myColor);
}
}
Color[] newPalette = new Color[palette.Length];
for (int i = 0; i < newPalette.Length; ++i)
{
myColor = GetAverageGolor(colorList[i]);
neko += myColor.ToString() + "\n";
diff += GetColorDistance(palette[i], myColor);
palette[i] = myColor;
}
TextBlockLoopCount.Text = "ループ回数 = " + j.ToString();
diff /= palette.Length;
neko += diff.ToString() + "\n";
if (diff < 1f) { break; }
diff = 0;
}
for (int i = 0; i < palette.Length; ++i)
{
MyPalette[i].Background = new SolidColorBrush(palette[i]);
}
Console.WriteLine(neko);
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{
p = y * stride + (x * 4);
myColor = Color.FromRgb(pixels[p + 2], pixels[p + 1], pixels[p]);
min = GetColorDistance(myColor, palette[0]);
pIndex = 0;
for (int i = 0; i < palette.Length; ++i)
{
distance = GetColorDistance(myColor, palette[i]);
if (min > distance)
{
min = distance;
pIndex = i;
}
}
myColor = palette[pIndex];
pixels[p + 2] = myColor.R;
pixels[p + 1] = myColor.G;
pixels[p] = myColor.B;
}
}
wb.WritePixels(new Int32Rect(0, 0, w, h), pixels, stride, 0);
return wb;
}
private double GetColorDistance(Color c1, Color c2)
{
return Math.Sqrt(
Math.Pow(c1.R - c2.R, 2) +
Math.Pow(c1.G - c2.G, 2) +
Math.Pow(c1.B - c2.B, 2));
}
private Color GetAverageGolor(List<Color> colorList)
{
long r = 0, g = 0, b = 0;
int cCount = colorList.Count;
if (cCount == 0)
{
return Color.FromRgb(127, 127, 127);
}
for (int i = 0; i < cCount; ++i)
{
r += colorList[i].R;
g += colorList[i].G;
b += colorList[i].B;
}
return Color.FromRgb((byte)(r / cCount), (byte)(g / cCount), (byte)(b / cCount));
}
private void AddBorders()
{
NumericScrollBar.Maximum = MAX_PALETTE_COLOR_COUNT;
MyPalette = new Border[MAX_PALETTE_COLOR_COUNT];
Border border;
for (int i = 0; i < MyPalette.Length; i++)
{
border = new Border()
{
Width = 20,
Height = 20,
BorderBrush = new SolidColorBrush(Colors.AliceBlue),
BorderThickness = new Thickness(1f),
Margin = new Thickness(1f)
};
MyPalette[i] = border;
MyWrapPanel.Children.Add(border);
}
}
private void MainWindow_Drop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop) == false) { return; }
string[] filePath = (string[])e.Data.GetData(DataFormats.FileDrop);
OriginBitmap = GetBitmapSourceWithChangePixelFormat2(filePath[0], PixelFormats.Pbgra32, 96, 96);
if (OriginBitmap == null)
{
MessageBox.Show("not Image");
}
else
{
MyImage.Source = OriginBitmap;
ImageFileFullPath = filePath[0];
}
}
<summary>
</summary>
<param name="filePath"></param>
<param name="pixelFormat"></param>
<param name="dpiX"></param>
<param name="dpiY"></param>
<returns></returns>
private BitmapSource GetBitmapSourceWithChangePixelFormat2(
string filePath, PixelFormat pixelFormat, double dpiX = 0, double dpiY = 0)
{
BitmapSource source = null;
try
{
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
var bf = BitmapFrame.Create(fs);
var convertedBitmap = new FormatConvertedBitmap(bf, pixelFormat, null, 0);
int w = convertedBitmap.PixelWidth;
int h = convertedBitmap.PixelHeight;
int stride = (w * pixelFormat.BitsPerPixel + 7) / 8;
byte[] pixels = new byte[h * stride];
convertedBitmap.CopyPixels(pixels, stride, 0);
if (dpiX == 0) { dpiX = bf.DpiX; }
if (dpiY == 0) { dpiY = bf.DpiY; }
source = BitmapSource.Create(
w, h, dpiX, dpiY,
convertedBitmap.Format,
convertedBitmap.Palette, pixels, stride);
};
}
catch (Exception)
{
}
return source;
}
}
}
ループ抜けの条件は
ループ回数100以上かパレット新旧の色差が0(全く同じ)になったときにしてあるので時間がかかる
処理時間は
指定色数
ループの回数
この3つが大きいほど時間がかかる
3色、ループ抜けの条件の色差0
こんな小さな画像で3色指定でもこんなに時間がかかる
最短では8回は約1秒
画像の大きさと色数は変化させていないから
単純にループ回数で時間が決まる
選ばれた色はどれもそんなに差がないからループ回数は少ない方がいいけど
完全一致までのループ回数は最初のパレットの色がランダムで決められて
これで差がつくので運だねえ
ループ回数で出てくる数値は偏っていいて多いのは12前後、8や9はなかなかでない、27,28はめったにでなくて20回に1回くらい、17以上26以下は40回試して1回もでなかった
ループ抜けの条件を緩くしてみる
色の差が0(全く同じ)から1未満に変更
ループ回数が28から8へと3分の1になったから
時間も3分の1になったみたい
変換結果の画像は3秒も1秒のも見た目同じなので
色の差は1未満でも全然行ける
もっと緩くして色差5未満
時間だと0.5秒から1秒くらいかな、色差1未満のと変わんない
パレットの色で赤系統のがでた、ランダム性があると面白い
ここまでの結果だとループ抜けの条件の色差は1未満でいいかな、5未満でも良い結果だけど時間が1未満のときと変わんないからねえ
色数10の場合
ループ抜け条件色差0
色数を増やすと時間かかるけど全体の色としては少ない赤系も選ばれるようになった
ループ抜け条件色差1未満
赤系統が選ばれないこともあったけど結果は良好
やっぱりループ抜け条件色差は1未満でいいかな
ループ抜け条件色差5未満
処理時間0.5~1.5秒
なぜか色数3のときとループ回数がほとんど変わらない、速い
変換結果も悪くないから5未満でもいいかなあ
色数20
ループ抜け条件色差0
ループ抜け条件色差1未満
変換結果は色差0のときよりもいい感じがする、少なくとも劣ってはいないなあ