質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
87.20%
Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

解決済

Pythonで、いくつかの指定した色に似た色になるように、画像のピクセルを置き換えたい

dotter
dotter

総合スコア1

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

1回答

0評価

0クリップ

3212閲覧

投稿2021/02/17 05:29

編集2021/02/17 13:45

前提・実現したいこと

Pythonにおいて、PILで画像処理を行なっています。
画像全体をピクセルごとに、予め用意したカラーパレットの色に変換したいと考えています。
ソースコードにある通り、(r,g,b)の順に並べたpaletteというタプルのリストを作り、画像内で1ピクセルごとにどの色を示すタプルが最も近い色なのかを判定しようとしています。
以下のソースコードではbase.jpgというファイルを処理してoutput.jpgを作ろうとしています。

私はpython初心者のため、ソースコードに汚い部分がると思います。
よろしくお願い致します。

該当のソースコード

Python

from PIL import Image import numpy as np im=Image.open("base.jpg") #元画像base.jpg new_im=Image.new('RGB',(im.size[0],im.size[1]), (255,255,255)) #元画像と同じ大きさ、背景白の新たな画像 def rgb_to_xyz(rgb): #rgb(255まで)→xyzの割合(0<=return<=1)を出す a=[0.4124,0.3575,0.1805] b=[0.2127,0.7152,0.0722] c=[0.0193,0.1192,0.9504] k=np.array([a,b,c]) #変換用倍率の行列 t=k.dot(np.array([x/255 for x in rgb])) return (t[0]/0.9504,t[1]/1.0001,t[2]/1.0889) #↑白(255,255,255)で(0.9504,1.0001,1.0889)なので割合として返す def F(t): #L*a*b*用の関数 if t>216/24389: return 116*pow(t,1/3)-16 else: return 24389/27*t def Lab(x,y,z): #xyz方式をL*a*b*に変換 L=F(y) a=125/29*(F(x)-F(y)) b=50/29*(F(y)-F(z)) return (L,a,b) def dis(a,b): #ユークリッド距離(2乗での比較) return sum([pow(Lab(*rgb_to_xyz(a))[x]-Lab(*rgb_to_xyz(b))[x],2) for x in range(2)]) palette=[(60, 60, 60), (120, 120, 120), (130, 130, 130), (90, 90, 90), (255, 255, 255), (130, 135, 145), (70, 175, 170), (190, 100, 130), (140, 60, 170), (100, 50, 40), (130, 130, 200), (40, 60, 40), (60, 100, 120), (80, 120, 170), (120, 40, 40), (90, 0, 0), (200, 0, 0), (170, 100, 40), (200, 185, 130), (120, 85, 60), (100, 70, 40), (80, 60, 40), (100, 140, 45), (115, 150, 70), (0, 170, 45), (100, 160, 20), (180, 180, 40), (200, 190, 60), (80, 100, 40), (0, 100, 0), (50, 70, 30), (40, 65, 40)] #↑用意した色のテンプレート(rgb) for x in range(im.size[0]): for z in range(im.size[1]): rgb=im.getpixel((x,z))[0:3] m=[dis(rgb,p) for p in palette] #↑現在指定しているピクセルの色(rgb)とパレットそれぞれを比較したリスト作成 new_im.putpixel((x,z),palette[m.index(min(m))]) #作成したリストの最小値に値するタプルで色付け new_im.save("output.jpg",quarity=90)

2つ目のソースコード

Python

from PIL import Image import numpy as np im=Image.open("base.jpg") #元画像base.jpg new_im=Image.new('RGB',(im.size[0],im.size[1]), (255,255,255)) #元画像と同じ大きさ、背景白の新たな画像 def rgb_to_xyz(rgb): #rgb(255まで)→xyzの割合(0<=return<=1)を出す a=[0.4124,0.3575,0.1805] b=[0.2127,0.7152,0.0722] c=[0.0193,0.1192,0.9504] k=np.array([a,b,c]) #変換用倍率の行列 t=k.dot(np.array([x/255 for x in rgb])) return (t[0]/0.9504,t[1]/1.0001,t[2]/1.0889) #↑白(255,255,255)で(0.9504,1.0001,1.0889)なので割合として返す def F(t): #L*a*b*用の関数 if t>216/24389: return 116*pow(t,1/3)-16 else: return 24389/27*t def Lab(x,y,z): #xyz方式をL*a*b*に変換 L=F(y) a=125/29*(F(x)-F(y)) b=50/29*(F(y)-F(z)) return (L,a,b) def cos(a,b): return sum([change(*a)[x]*change(*b)[x] for x in range(2)]) def C94(L1,a1,b1,L2,a2,b2): k1=0.045 k2=0.015 kL=1 C1=(a1**2+b1**2)**(0.5) C2=(a2**2+b2**2)**(0.5) dC=C1-C2 dH=((a1-a2)**2+(b1-b2)**2-dC**2)**(0.5) Sc=1+k1*C1 Sh=1+k2*C2 return (((L1-L2)/kL)**2+(dC/Sc)**2+(dH/Sh)**2)**(0.5) def dis(a,b): return sum([C94(*Lab(*rgb_to_xyz(a)),*Lab(*rgb_to_xyz(b))) for x in range(2)]) palette=[(60, 60, 60), (120, 120, 120), (130, 130, 130), (90, 90, 90), (255, 255, 255), (130, 135, 145), (70, 175, 170), (190, 100, 130), (140, 60, 170), (100, 50, 40), (130, 130, 200), (40, 60, 40), (60, 100, 120), (80, 120, 170), (120, 40, 40), (90, 0, 0), (200, 0, 0), (170, 100, 40), (200, 185, 130), (120, 85, 60), (100, 70, 40), (80, 60, 40), (100, 140, 45), (115, 150, 70), (0, 170, 45), (100, 160, 20), (180, 180, 40), (200, 190, 60), (80, 100, 40), (0, 100, 0), (50, 70, 30), (40, 65, 40)] #↑用意した色のテンプレート(rgb) f![イメージ説明](9792dc25b0893bd07159f748839d1633.png)n range(im.size[0]): for z in range(im.size[1]): rgb=im.getpixel((x,z))[0:3] m=[dis(rgb,p) for p in palette] #↑現在指定しているピクセルの色(rgb)とパレットそれぞれを比較したリスト作成 new_im.putpixel((x,z),palette[m.index(min(m))]) #作成したリストの最小値に値するタプルで色付け new_im.save("output.jpg",quarity=90)

試したこと

近い色を判定するために、まずrgbのタプル同士でのユークリッド距離として近い色を検出させました。
次に、rgb配色のままだとあまり同系色を検出できないことを理解して、rgbをhsv形式に変換して同様にユークリッド距離で判定しました。
ユークリッド距離での計算が思うようにいかなかったため、コサイン類似度(空間ベクトル内での2ベクトルの内積の最小値)によって判定しました。
あまりいい結果が得られないため、次にrgbをxyz方式にして同様にユークリッド距離、コサイン類似度で計算しました。
最後に、現在のソースコードのようにrgb→xyz→Lab*方式に変換してユークリッド距離での判定をしました。
これらのことを行なったのですが似たような色へと変換することが困難でした。

《追記分》

ユークリッド距離での判定がイマイチだったので、2つ目のソースコードでは以下のURLの「CIE94」を試しています。
https://ja.m.wikipedia.org/wiki/色差

ユークリッド距離はそもそもの色が若干違う点、CIE94は大まかには合っているものの黒い斑点が多く入ってしまうこと、が問題点です。

左(base.jpg)→右(output.jpg)です。それぞれ2つ試しています。
イメージ説明

良い質問の評価を上げる

以下のような質問は評価を上げましょう

  • 質問内容が明確
  • 自分も答えを知りたい
  • 質問者以外のユーザにも役立つ

評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

気になる質問をクリップする

クリップした質問は、後からいつでもマイページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

  • プログラミングに関係のない質問
  • やってほしいことだけを記載した丸投げの質問
  • 問題・課題が含まれていない質問
  • 意図的に内容が抹消された質問
  • 過去に投稿した質問と同じ内容の質問
  • 広告と受け取られるような投稿

評価を下げると、トップページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

tiitoi

2021/02/17 11:41 編集

base.jpg を質問欄に貼っていただけますか。現状どうなるのか、どのような結果を期待しているのかがわからないので、コードだけ見てなにかアドバイスをするのは困難です。
dotter

2021/02/17 13:43

コメントありがとうございます。 対応させて頂きました。 よろしくお願い致します。
tiitoi

2021/02/17 14:40 編集

減色処理 (画像をN色で表すこと) ではなく、パレットから色を選ぶことは必須なのでしょうか?パレットの色の中身を確認しましたが、画像によっては元の色に近い色がパレットにないことも考えられ、パレットの色を使って元画像と同じ見た目の画像を生成するというのがそもそも無理だと思います。単にN色に減色したいということであれば、綺麗にやる方法が存在します。

まだ回答がついていません

会員登録して回答してみよう

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
87.20%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問

同じタグがついた質問を見る

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。