午後わてんのブログ

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

色の距離は難しい、いくつか試したけどわからなかった

色の距離
減色処理で指定したパレットの色に変換する時
パレットの中から一番近い色を探す必要がある
 
      パレットがこの3色の時
 
  この色はどの色に近いのか
僕の目から見ると  が一番近い
こういう処理
今まではこの処理をRGBの値からユークリッド距離で行っていたけど
色によってはいまいちな結果になることがある
 
パレットがこの2色のとき
  RGB(160,129,66)
  RGB(20,206,49)
 
  RGB(115,195,61)この色に近いのは
僕の目から見ると  なんだけどRGBだと  だと判断される
 
イメージ 1
RGBでのユークリッド距離はRGBそれぞれの差の2乗したものを合計したもの
(R1-R2)^2+(G1-G2)^2+(B1-B2)^2
これの平方根を求めればユークリッド距離なんだけど
正確な距離を知りたいわけじゃなくてどっちが近いか知りたいだけだから
今回は平方根は必要ない
 

基準色 と色1 との距離

(115-160)^2+(195-129)^2+(61-66)^2=6406
基準色 と色2 との距離
(115-20)^2+(195-206)^2+(61-49)^2=9290
√6406=80.0
√9290=96.4
 
こんな感じでRGBでの計算だとたまにイマイチなことがあったのでRGB以外でも計算してみようと作ったのがこのアプリ
イメージ 2
 ダウンロード先
 
ランダムな20色を基準色に近い順に並べる処理をいろいろな計算で行う
距離の計算方法は上から
rgb 今まで使っていたRGBでのユークリッド距離
rgb2 RGBそれぞれに重み付けした、よくわかっていない
HSV円柱Ad HSV円柱モデルそれぞれの値を合計しただけ
HSV円錐Ad ↑の円錐モデル
HSV円柱Eu HSV円柱モデルでのユークリッド距離
HSV円錐Ad ↑の円錐モデル
HSV円柱Tri HSV円柱モデルでの三角関数を使ってみた距離
HSV円錐Tri ↑の円錐モデル
XYZ XYZ色空間でのユークリッド距離、よくわかっていない
Lab Lab色空間でのユークリッド距離、よくわかっていない
色相 HSVの色相
彩度 HSVの彩度
明度 HSVの明度
このなかでXYZとLabはほとんど理解できていないので僕の計算方法が間違っている可能性がとても高いので目安にもならないかも
HSVの色相、彩度、明度はああ、こうなるんだなあっていう参考用
本命はHSVの円柱、円錐関係
 
このアプリで遊んだ結果
色の距離は難しい、余計にわからなくなった
その過程
 
基準色はそのままでランダム色を変更してみる
イメージ 3
重要なのは基準色に一番近い色をパレット(ランダム色)から選ぶことなので
左端の色が基準色に近いかどうか
今回はどの計算方法でも良い結果
 
イメージ 4
これは意見が別れた
円錐合計はピンクを推してきた、円錐ユークリッドEuも2番めにピンクを持ってきている
明度を見るとピンクは3番目、他の赤系と比べて高いのがわかる
ってことは円錐モデルは明度同士の距離を重視できるってことかなあ
 
イメージ 5
このパレットから赤に近いのを選ぶのは迷う
RGBの判断は暗めの赤
HSV系はピンクが多い、2番目には黄色が入っているのは明度が近いからだねえ
ここまでだとRGBのユークリッド距離で十分な結果
今度は基準色も変えてみる
 
イメージ 6
基準色を明るい青系の灰色、彩度が12.98と低いものに変えてみた結果
RGBは残念な結果になっている、これは違うなあ
対してHSV系はいい結果
 
イメージ 7
パレットだけ変更
かなり意見が別れたけど、どれも納得できるかなあ
 

f:id:gogowaten:20191212113604p:plain

パレットだけ変更
HSV系はどれも大差ないかなあと思っていたら
 
イメージ 9
円錐モデルのユークリッド距離の結果がいまいち
いいと思うのはRGBとHSV三角関数Tri、Lab
 
 
明度が低い色
イメージ 10
明度が低い青紫が基準色
良好なのはRGBとHSV三角関数、XYZ
他のHSVはイマイチかな、色相の近い紫を選んでいる、特に円錐Adは違う
 
 

f:id:gogowaten:20191212113619p:plain

明度が低い基準色だとRGBは外さないなあ、逆にHSV系はたまに外している
 
 
再度と明度ともに低い色の場合
イメージ 12
RGBとHSVの中では安定していた円錐三角関数が外している
他のHSVは良好かな、でもこのパターンは難しいな
 
パレット変更
イメージ 13
RGBはぜんぜん違う緑色を選んだ
 
 

f:id:gogowaten:20191212113633p:plain

いや、もうホントわかんない
 
基準色の明度が極端に低くて黒に近い色なら、色相を無視して明度が低い色を近い色とする、とか
基準色の彩度が極端に低くて色相がわからないときも、色相を無視して彩度と明度を重視する、とか
そんな味付け(重み付け)みたいな事すれば良さそうなんだけど、コレガワカラナイ
 
 
 
 
 
 
RGBとHSVの相互変換はこの前作ったDLLを使って
以下は距離の計算部分のコード
 
//2色間のRGBユークリッド距離
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));
}


//ColorをHSVに変換して
//H、S、Vそれぞれの差を足し算
//HSVの各範囲はHは0から360、SとVは0から1
private double GetColorDistanceHSV円柱and円錐Add(Color c1, Color c2, bool Conical)
{
    HSV iHsv2, iHsv1;
    if (Conical == false)//円柱モデル
    {
        iHsv2 = HSV.Color2HSV(c2);
        iHsv1 = HSV.Color2HSV(c1);
    }
    else//円錐モデル
    {
        iHsv1 = HSV.Color2HSV_ConicalModel(c1);
        iHsv2 = HSV.Color2HSV_ConicalModel(c2);
    }
    double h = Math.Abs(iHsv1.Hue - iHsv2.Hue);
    double s = Math.Abs(iHsv1.Saturation - iHsv2.Saturation);
    double v = Math.Abs(iHsv1.Value - iHsv2.Value);
    //hは180が反対側の色で一番遠い色
    if (h > 180f) { h = Math.Abs(h - 360f); }
    //hも0-1の値に変換
    h /= 180f;
    return h + s + v;
}



//HSVのユークリッド距離
private double GetColorDistanceHSVEuclidean(Color c1, Color c2, bool Conical)
{
    HSV iHsv1, iHsv2;
    if (Conical == false)//円柱モデル
    {
        iHsv1 = HSV.Color2HSV(c1);
        iHsv2 = HSV.Color2HSV(c2);
    }
    else//円錐モデル
    {
        iHsv2 = HSV.Color2HSV_ConicalModel(c2);
        iHsv1 = HSV.Color2HSV_ConicalModel(c1);
    }

    double h = Math.Abs(iHsv1.Hue - iHsv2.Hue);
    double s = Math.Abs(iHsv1.Saturation - iHsv2.Saturation);
    double v = Math.Abs(iHsv1.Value - iHsv2.Value);
    //hは180が反対側の色で一番遠い色
    if (h > 180f) { h = Math.Abs(h - 360f); }
    //hも0-1の値に変換
    h /= 180f;
    double distance = Math.Sqrt(Math.Pow(h, 2f) + Math.Pow(s, 2f) + Math.Pow(v, 2f));
    return distance;
}


//HSV距離三角関数
/// <summary>
/// HSVと三角関数を使って2色間の距離を測る
/// 円錐モデルのHSVを使うときはConicalにTrue
/// </summary>
/// <param name="c1"></param>
/// <param name="bColor"></param>
/// <param name="Conical">円錐モデル</param>
/// <returns></returns>
private double GetColorDistanceHSV円柱or円錐Tryangle(Color c1, Color bColor, bool Conical)
{
    HSV iHsv, bHsv;
    if (Conical == false)//円柱モデル
    {
        iHsv = HSV.Color2HSV(c1);
        bHsv = HSV.Color2HSV(bColor);
    }
    else//円錐モデル
    {
        iHsv = HSV.Color2HSV_ConicalModel(c1);
        bHsv = HSV.Color2HSV_ConicalModel(bColor);
    }
    double iRadian = iHsv.Hue / 180 * Math.PI;
    double bRadian = bHsv.Hue / 180 * Math.PI;
    double ix = Math.Cos(iRadian) * iHsv.Saturation;
    double bx = Math.Cos(bRadian) * bHsv.Saturation;
    double iy = Math.Sin(iRadian) * iHsv.Saturation;
    double by = Math.Sin(bRadian) * bHsv.Saturation;
    double distance = Math.Sqrt(
        Math.Pow(ix - bx, 2) +
        Math.Pow(iy - by, 2) +
        Math.Pow(iHsv.Value - bHsv.Value, 2));
    return distance;
}


//xyz?
private double[] RGBtoXYZ(Color c)
{
    double r = c.R / 255f;
    double g = c.G / 255f;
    double b = c.B / 255f;
    r = (r > 0.04045) ? Math.Pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92);
    g = (g > 0.04045) ? Math.Pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92);
    b = (b > 0.04045) ? Math.Pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92);

    double x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805);
    double y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722);
    double z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505);
    return new double[] { x, y, z };
}

//XYZ?のユークリッド距離?
private double GetColorDistanceXYZ(Color c1, Color c2)
{
    double[] xyz1 = RGBtoXYZ(c1);
    double[] xyz2 = RGBtoXYZ(c2);
    double distance = Math.Sqrt((
        Math.Pow(xyz1[0] - xyz2[0], 2) +
        Math.Pow(xyz1[1] - xyz2[1], 2) +
        Math.Pow(xyz1[2] - xyz2[2], 2)));
    return distance;
}



//Lab?
private double[] RGBtoLab(Color c)
{
    double[] xyz = RGBtoXYZ(c);
    double x = xyz[0] * 100;
    double y = xyz[1] * 100;
    double z = xyz[2] * 100;
    x /= 95.047;
    y /= 100;
    z /= 108.883;

    x = (x > 0.008856) ? Math.Pow(x, 1f / 3f) : (7.787 * x) + (4 / 29);
    y = (y > 0.008856) ? Math.Pow(y, 1f / 3f) : (7.787 * y) + (4 / 29);
    z = (z > 0.008856) ? Math.Pow(z, 1f / 3f) : (7.787 * z) + (4 / 29);

    double L = (116 * y) - 16;
    double a = 500 * (x - y);
    double b = 200 * (y - z);
    return new double[] { L, a, b };
}

//Lab?のユークリッド距離?
private double GetColorDistanceLab(Color c1, Color c2)
{
    double[] Lab1 = RGBtoLab(c1);
    double[] Lab2 = RGBtoLab(c2);
    double distance = Math.Sqrt((
        Math.Pow(Lab1[0] - Lab2[0], 2) +
        Math.Pow(Lab1[1] - Lab2[1], 2) +
        Math.Pow(Lab1[2] - Lab2[2], 2)));
    return distance;
}



今回のアプリには以前作ったRGBとHSVを相互変換するDLLを使ったんだけど
円錐モデルは書いていかなかったので追加した
その追加した部分


/// <summary>
/// Color(RGB)をHSV(円錐モデル)に変換、Hの値は0fから360f、SとVは0fから1f
/// </summary>
/// <param name="color"></param>
/// <returns></returns>
public static HSV Color2HSV_ConicalModel(Color color)
{
    byte R = color.R;
    byte G = color.G;
    byte B = color.B;
    byte Max = Math.Max(R, Math.Max(G, B));
    byte Min = Math.Min(R, Math.Min(G, B));
    if (Max == 0) { return new HSV(360f, 0f, 0f); }

    double chroma = Max - Min;
    double h = 0;
    double s = chroma / 255f;//円錐モデル
    double v = Max / 255f;

    if (Max == Min) { h = 360f; }
    else if (Max == R)
    {
        h = 60f * (G - B) / chroma;
        if (h < 0) { h += 360f; }
    }
    else if (Max == G)
    {
        h = 60f * (B - R) / chroma + 120f;
    }
    else if (Max == B)
    {
        h = 60f * (R - G) / chroma + 240f;
    }
    else { h = 360f; }

    return new HSV(h, s, v);
}

/// <summary>
/// RGBをHSV(円錐モデル)に変換、RGBそれぞれの値を指定する
/// </summary>
/// <param name="r"></param>
/// <param name="g"></param>
/// <param name="b"></param>
/// <returns></returns>
public static HSV Color2HSV_ConicalModel(byte r, byte g, byte b)
{
    return Color2HSV_ConicalModel(Color.FromRgb(r, g, b));
}



/// <summary>
/// 円錐モデルのHSVをColorに変換
/// </summary>
/// <param name="hsv">円錐モデルのHSV</param>
/// <returns></returns>
public static Color HSV_ConicalModel2Color(HSV hsv)
{
    double Max = hsv.Value * 255f;
    double Min = (hsv.Value - hsv.Saturation) * 255f;
    double d = Max - Min;

    double h = hsv.Hue;
    double r = 0, g = 0, b = 0;

    if (h < 60)
    {
        r = Max;
        g = Min + d * h / 60f;
        b = Min;
    }
    else if (h < 120)
    {
        r = Min + d * (120f - h) / 60f;
        g = Max;
        b = Min;
    }
    else if (h < 180)
    {
        r = Min;
        g = Max;
        b = Min + d * (h - 120f) / 60f;
    }
    else if (h < 240)
    {
        r = Min;
        g = Min + d * (240f - h) / 60f;
        b = Max;
    }
    else if (h < 300)
    {
        r = Min + d * (h - 240f) / 60f;
        g = Min;
        b = Max;
    }
    else
    {
        r = Max;
        g = Min;
        b = Min + d * (360f - h) / 60f;
    }
    return Color.FromRgb(
        (byte)Math.Round(r, MidpointRounding.AwayFromZero),
        (byte)Math.Round(g, MidpointRounding.AwayFromZero),
        (byte)Math.Round(b, MidpointRounding.AwayFromZero));
}

/// <summary>
/// 円錐モデルのHSVをColorに変換
/// </summary>
/// <param name="h"></param>
/// <param name="s"></param>
/// <param name="v"></param>
/// <returns></returns>
public static Color HSV_ConicalModel2Color(double h,double s,double v)
{
    return HSV_ConicalModel2Color(new HSV(h, s, v));
}
 
 

f:id:gogowaten:20191212113906p:plain

動作確認しているところ
 
 
 
参照したところ
色の距離(色差)の計算方法 - Qiita
https://qiita.com/shinido/items/2904fa1e9a6c78650b93
うーん、難しい、今回試した方法以外にもまだある
 
CIE XYZ表色系(9): XYZ-RGB の変換式 と カラートライアングル
http://www.enjoy.ne.jp/~k-ichikawa/CIEXYZ_RGB.html
JavaScriptでRGBからLab色空間への変換 - Qiita
https://qiita.com/hachisukansw/items/09caabe6bec46a2a0858
RGBをXYZ、Labに変換するコードはこちらからパク…参考にしました
 
カラーコード変換ツール | Hex、RGB、HSVCMYK、XYZ、LAB、HSLに対応
https://syncer.jp/color-converter
XYZ、Labの変換が正しくできたかどうかの判断はこちらを参考にしました
 
HSVやHSLからRGBへの変換 ( プログラム ) - Color Model:色をプログラムするブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/pspevolution7/17680244.html
プログラミング 第6弾 ( プログラム ) - Color Model:色をプログラムするブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/pspevolution7/17682985.html
円錐モデルのHSVとRGBの相互変換はPSPさんのBlogを参考にしました
ありがとうごさいます!
 
 
色のソートにSortedListを使っていて、距離をKey、色をValueにして追加している
違う色どうしでも稀に同じ距離になることがあって、SortedListはKeyの重複を許さないのでここでエラーになる
 
 
ダウンロード先(ヤフーボックス)
 
関連記事
2018/2/20は3週間前
WPF、Color(RGB)とHSVを相互変換するdll作ってみた、オブジェクトブラウザ使ってみた ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15380324.html