古典的なMLを使用して、絵画の傑作を分析します

みなさん、こんにちは!私の友人は芸術家として勉強し、この傑作やその傑作について、独特の構図技法、色の知覚について、絵画の進化や素晴らしい芸術家について定期的に話します。この絶え間ない影響を背景に、私はエンジニアリングの知識とスキルが世界の文化遺産の分析に適しているかどうかを確認することにしました。

夜間にカバーされたその場しのぎのパーサーで武装して、私はオンラインギャラリーに突入し、そこから約5万点の絵画を持ち出しました。従来のMLツール(注意、トラフィック)のみを使用して、これで何が面白いか見てみましょう。

素朴な変容


私たちの多くがコンピュータサイエンスのレッスンで覚えているように、画像は、個々のピクセルの色に関与するバイトの配列として表されます。原則として、RGBスキームが使用されます。このスキームでは、色は3つのコンポーネント(赤/緑/青)に分割され、黒の背景と合計すると、人が知覚する元の色になります。

今のところ、すべての傑作は一時的にディスク上の数値の配列になっているだけなので、各チャネルの強度周波数の分布のヒストグラムを作成して、これらの配列の特性を調べます。

計算にはnumpyを使用し、matplotlibを使用して視覚化します。

ソース
#      
def load_image_by_index(i):
    image_path = paintings_links.iloc[i].img_path
    img = cv2.imdecode(np.fromfile(str(Path.cwd()/image_path), np.uint8), cv2.IMREAD_UNCHANGED)
    return img    
#    
def get_hist_data_by_index(img_index):
    bin_div = 5 
    img = load_image_by_index(img_index)
    b, bins=  np.histogram(img[:,:,0], bins=255//bin_div, range=(0,255), density=True)
    g = np.histogram(img[:,:,1], bins=255//bin_div, range=(0,255), density=True)[0]
    r = np.histogram(img[:,:,2], bins=255//bin_div, range=(0,255), density=True)[0]
    return bins, r, g, b
#       
def plot_image_with_hist_by_index(img_index, height=6):
    bins, r, g, b = get_hist_data_by_index(img_index)
    img = load_image_by_index(img_index)
    fig = plt.figure(constrained_layout=True)

    if img.shape[0] < img.shape[1]:
        width_ratios = [3,1]
    else:
        width_ratios = [1,1]
        
    gs = GridSpec(3, 2, figure=fig, 
                  width_ratios = [3,1]
                 )
    ax_img = fig.add_subplot(gs[:,0])

    ax_r = fig.add_subplot(gs[0, 1])
    ax_g = fig.add_subplot(gs[1, 1], sharey=ax_r)
    ax_b = fig.add_subplot(gs[2, 1], sharey=ax_r)

    ax_img.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB),aspect = 'equal')
    ax_img.axis('off')
    
    ax_r.bar(bins[:-1], r, width = 5, color='red',alpha=0.7)
    ax_g.bar(bins[:-1], g, width = 5, color='green',alpha=0.7)
    ax_b.bar(bins[:-1], b, width = 5, color='blue',alpha=0.7)

    ax_r.axes.get_xaxis().set_ticks([])
    ax_r.axes.get_yaxis().set_ticks([])
    ax_g.axes.get_xaxis().set_ticks([])
    ax_g.axes.get_yaxis().set_ticks([])
    ax_b.axes.get_xaxis().set_ticks([])
    ax_b.axes.get_yaxis().set_ticks([])
    fig.suptitle("{} - {}".format(paintings_links.iloc[img_index].artist_name, 
                                 paintings_links.iloc[img_index].picture_name),ha= "left")
    
    fig.set_figheight(height)
    plt.axis('tight')
    if img.shape[0] < img.shape[1]:
        fig.set_figwidth(img.shape[1] *height / img.shape[0] *1.25)
    else:
        fig.set_figwidth(img.shape[1] *height / img.shape[0] *1.5)
    plt.show()


作品の例:









さまざまな画像のヒストグラムを注意深く見たところ、それらの形式は非常に固有であり、作品ごとに大きく異なることに気づくことができます。

この点で、ヒストグラムは画像のキャストの一種であり、ある程度特徴付けることができると想定しています。

最初のモデル


すべてのヒストグラムを1つの大きなデータセットに収集し、その中にいくつかの「異常」がないか探します。高速で便利な、一般的にこのような目的で私のお気に入りのアルゴリズムは、1つのクラスsvmです。sklearnライブラリの実装を使用します

ソース
#       ,       
res = []
error = []
for img_index in tqdm(range(paintings_links.shape[0])):
    try:
        bins, r, g, b = get_hist_data_by_index(img_index)
        res.append(np.hstack([r,g,b]))
    except:
        res.append(np.zeros(153,))
        error.append(img_index)
        
np_res = np.vstack(res)
#    
pd.DataFrame(np_res).to_pickle("histograms.pkl")
histograms = pd.read_pickle("histograms.pkl")
#  .         .   10    
one_class_svm = OneClassSVM(nu=10 / histograms.shape[0], gamma='auto')
one_class_svm.fit(histograms[~histograms.index.isin(bad_images)])
#  
svm_outliers = one_class_svm.predict(histograms)
svm_outliers = np.array([1 if label == -1 else 0 for label in svm_outliers])
#   
uncommon_images = paintings_links[(svm_outliers ==1) & (~histograms.index.isin(bad_images))].index.values
for i in uncommon_images:
    plot_image_with_hist_by_index(i,4)


ギャラリーのゴミ箱で何が異常なのかを見てみましょう。

鉛筆で


作業非常に暗い色で作業:

赤の女性:

大ざっぱなもの:

非常に暗い肖像:


類似の求人を検索


さて、私たちのモデルは、他のすべてから遠く離れて、異常なものを見つけます。

しかし、色の似た作品を見つけるのに役立つツールを作成できますか?

これで、各画像は153の値のベクトルで特徴付けられます(ヒストグラムを作成するとき、ビンごとに5強度単位にヒットするため、各チャネルの合計255/5 = 51周波数)。

関心のあるベクトル間の距離を計算することにより、「類似度」を決定できます。ここの学校でおなじみのユークリッド距離は、ベクトル成分の長さに多くの注意を払うでしょう、そして、絵を構成する陰影のセットにもっと注意を払いたいと思います。ここでは、たとえばテキスト分析の問題で広く使用されている距離の余弦測度を見つけます。このタスクに適用してみましょう。実装はscipyライブラリから取得します。

ソース
#  ,        
from scipy import spatial
def find_closest(target_id,n=5):
    distance_vector = np.apply_along_axis(spatial.distance.cosine,
                        arr=histograms,
                       axis=1,
                       v=histograms.values[target_id])
    return np.argsort(distance_vector)[:n]


アイバゾフスキーの「第9の波」のように見えるものを見てみましょう。

原作:



同様の作品:





ゴッホの「花のアーモンド」のように見えるもの。

原作:



同様の作品:





そして、以前に赤で発見された異常な女性のように見えますか?

オリジナル:



類似作品:






色空間


その瞬間まで、RGBカラースペースで作業していました。理解するのにとても便利ですが、私たちの仕事には理想的ではありません。

たとえば、RGBカラーキューブの瀬戸際を



見てください。肉眼では、顔に大きな変化が見られない領域と、色の知覚が非常に急激に変化する比較的小さな領域があることがわかります。この非線形の知覚は、マシンが人がするように色を評価することを妨げます。

幸いなことに、多くの色空間があり、おそらくいくつかは私たちのタスクに適合します。

私たちは、人間の問題を解決する上での有用性を比較することにより、お気に入りの色空間を選択します。たとえば、キャンバスのコンテンツからアーティストを計算してみましょう。

opencvライブラリーから使用可能なすべての色空間を取得し、それぞれでxgboostをトレーニングし、遅延選択のメトリックを確認します。

ソース
# ,        
def get_hist_data_by_index_and_colorspace(bgr_img, colorspace):
    bin_div = 5
    img_cvt = cv2.cvtColor(bgr_img, getattr(cv2, colorspace))
    c1, bins =  np.histogram(img_cvt[:,:,0], bins=255//bin_div, range=(0,255), density=True)
    c2 = np.histogram(img_cvt[:,:,1], bins=255//bin_div, range=(0,255), density=True)[0]
    c3 = np.histogram(img_cvt[:,:,2], bins=255//bin_div, range=(0,255), density=True)[0]
    return bins, c1, c2, c3
#        
all_res = {}
all_errors = {}
for colorspace in list_of_color_spaces:
    all_res[colorspace] =[]
    all_errors[colorspace] =[]
for img_index in tqdm(range(paintings_links.shape[0]) ):
    
    for colorspace in list_of_color_spaces:
        try:
            bgr_img = load_image_by_index(img_index)
            bins, c1, c2, c3 = get_hist_data_by_index_and_colorspace(bgr_img, colorspace)
            all_res[colorspace].append(np.hstack([c1, c2, c3]))
        except:
            all_res[colorspace].append(np.zeros(153,))
            all_errors[colorspace].append(img_index)
all_res_np = {}
for colorspace in list_of_color_spaces:  
    all_res_np[colorspace] = np.vstack(all_res[colorspace])
res = []
#        
for colorspace in tqdm(list_of_color_spaces):
    temp_df = pd.DataFrame(all_res_np.get(colorspace))
    temp_x_train =   temp_df[temp_df.index.isin(X_train.index.values)]
    temp_x_test =   temp_df[temp_df.index.isin(X_test.index.values)]

    xgb=XGBClassifier()
    xgb.fit(temp_x_train, y_train)
    current_res = classification_report(y_test, xgb.predict(temp_x_test), labels=None, target_names=None, output_dict=True).get("macro avg")
    current_res["colorspace"] = colorspace
    res.append(current_res)
pd.DataFrame(res).sort_values(by="f1-score")


精度想起f1スコア色空間
0.0013290.0036630.001059COLOR_BGR2YUV
0.0032290.0046890.001849COLOR_BGR2RGB
0.0030260.0041310.001868COLOR_BGR2HSV
0.0029090.0045780.001934COLOR_BGR2XYZ
0.0035450.0044340.001941COLOR_BGR2HLS
0.0039220.0047840.002098COLOR_BGR2LAB
0.0051180.0048360.002434COLOR_BGR2LUV

LUV色空間を使用することにより、品質の目に見える向上が得られました。

このスケールの作成者は、スケールの軸に沿った色の変化の認識をできるだけ均一にすることを試みました。これのおかげで、知覚される色の変化とその数学的評価は可能な限り近くなります。

これは、特定の色空間のスライスが、軸の1つを固定するときにどのように見えるかです。



モデルを見てみましょう


前のステップの後、何かを予測できるモデルがまだあります。
私たちが誰の仕事を最も正確に学んでいるか見てみましょう。
精度想起f1スコアアーティスト
0.0425530.0194170.026667イリヤ・エフィモビッチ・レーピン
0.0555560.0200000.029412ウィリアムメリットチェイス
0.0714290.0222220.033898ボナールピエール
0.0354610.0352110.035336ジル・エルブグレン
0.1000000.0217390.035714ジャン・オーギュスト・ドミニク・イングレス
0.0228140.2240660.041411ピエール・オーギュスト・ルノワール
0.1000000.0285710.044444アルバートビアシュタット
0.2500000.0322580.057143ハンス・ザツカ
0.0303960.5187970.057428クロードオスカーモネ
0.2500000.0370370.064516ジロットウォルター

メトリック自体は理想からはほど遠いですが、配色は作業に関する情報のほんの一部であることを覚えておく必要があります。アーティストは多くの表現手段を使用します。このデータでアーティストの特定の「手書き」を見つけたという事実は、すでに勝利です。

より深い分析のために、アーティストの1人を選択します。それをクロードオスカーモネにしましょう(私は妻を素敵にします、彼女は印象派が好きです)。

彼の仕事を取り、モデルに著者を教えて頻度を計算してもらいましょう
予測著者予測数
クロードオスカーモネ186
ピエール・オーギュスト・ルノワール171
ヴィンセント・ヴァン・ゴッホ25
ピーターポールルーベンス十九
ギュスターヴ・ドレ17

多くの人々はモネとマネを混同する傾向があり、私たちのモデルは彼をルノワールとゴッホと混同することを好む。モデルによれば、ゴッホに似ているものを見てみましょう。










次に、類似した作品の検索機能を使用して、上記の作品に類似したヴァンゴッホの絵画を見つけます(今回はLUV空間の距離を測定します)。

オリジナル:



類似作品:



オリジナル:



類似作品:






オリジナル:



類似作品:





私は満足しました。友人に結果を示したところ、ヒストグラムアプローチは色自体ではなく、そのコンポーネントの分布を個別に分析するため、実際には非常に失礼であることがわかりました。さらに、色の構成として重要なのは、色の頻度ではありません。現代のアーティストは、配色の選択に対するアプローチを証明していることが判明しました。ヨハネス・イッテンと彼のカラーホイールについて知りました。

イッテンのカラーホイール




ヨハネスイッテンは、芸術家、芸術理論家、教師であり、形や色に関する有名な本の著者です。カラーホイールは、目を楽しませるために色を組み合わせるのに役立つ最もよく知られたツールの1つです。

最も一般的な色選択方法を示します。



  1. 補色-円の反対側にあります
  2. 隣接する色-円に隣接
  3. クラシックトライアド-正三角形の頂点の色
  4. コントラストトライアド-二等辺三角形の上端の色
  5. 四角形のルール-四角形の頂点の色
  6. 正方形のルール-正方形の上部の色

アーティストとして分析


習得した知識を実践してみましょう。まず、カラーホイールに色の配列を配置し、画像からそれらを認識します。



これで、絵の各ピクセルをイッテンサークルカラーの配列とペアで比較できます。元のピクセルをカラーホイールの最も近いピクセルに置き換え、結果の画像の色の頻度を計算します

ソース
#          
def plot_composition_analysis(image_index):
    img = load_image_by_index(image_index)
    luv_img = cv2.cvtColor(load_image_by_index(image_index), cv2.COLOR_BGR2LUV)
    closest_colors = np.argmin(euclidean_distances(luv_img.reshape(-1,3),wheel_colors_luv),axis=1)
    wheel_colors2[closest_colors].reshape(luv_img.shape)
    color_areas_img = wheel_colors2[closest_colors].reshape(img.shape)

    v, c = get_image_colors(image_index)
    #         
    c_int = (c*img.shape[1]).astype(int)
    c_int_delta = img.shape[1] - sum(c_int)
    c_int[np.argmax(c_int)] = c_int[np.argmax(c_int)] + c_int_delta

    _ = []

    for i, vi in enumerate(v):
        bar_width = c_int[i]
        _.append(np.tile(wheel_colors2[vi], (150,bar_width,1)))
    color_bar_img = np.hstack(_)
    final_image = np.hstack([
                                np.vstack([img,
                                           np.tile(np.array([254,254,254]),(160,img.shape[1],1))]),

                                np.tile(np.array([254,254,254]),(img.shape[0]+160,10,1)),
                                np.vstack([color_areas_img,
                                           np.tile(np.array([254,254,254]),(10,img.shape[1],1)),
                                           color_bar_img])
                            ])
    h = 12
    w = h / final_image.shape[1] * final_image.shape[0]
    fig = plt.figure(figsize=(h,w))
    plt.imshow(cv2.cvtColor(final_image.astype(np.uint8), cv2.COLOR_BGR2RGB),interpolation='nearest', aspect='auto')
    plt.title("{} - {}".format(paintings_links.iloc[image_index].artist_name, 
                             paintings_links.iloc[image_index].picture_name),ha= "center")
    plt.axis('off');












「ヤングアルレシアン」が私たちの変容後にほとんど変わっていないことに気づきましたか?おそらく、新しい色の頻度だけでなく、変換エラーの統計も測定する価値があります。これは、分析に役立ちます。

しかし、これは現在の分析には十分ではありません。円の調和のとれた組み合わせを探しましょうか?

すべての補完ペア:



すべての古典的なトライアド:



すべての四角:



これらの組み合わせを絵画で探し、最も重要なもの(頻度)を見つけ、
何が起こるか確認します。

ペアを開始するには:











次に、3つ組:













最後に、四角形:















目で見て悪くはありませんが、新しいメトリックは作品の作者を決定するのに役立ちますか?

イッテンの色の頻度、エラー特性、調和の取れた組み合わせのみを使用してモデルをトレーニングします。

今回、最も「予測可能な」アーティストのリストが少し変更されました。つまり、分析への別のアプローチにより、画像の内容からさらに情報を抽出することができました。
精度想起f1スコアアーティスト
0.0434780.0121950.019048マーティン、アンリジャンギヨーム
0.0326800.0290700.030769カミーユピサロ
0.1666670.0196080.035088ジャンレオンジェローム
0.0769230.0277780.040816ターナー、ジョセフ・マロード・ウィリアム
0.1333330.0243900.041237貧しい人々、リエン
0.1000000.0263160.041667マックス・クリンガー
0.0267250.2282160.047847ピエール・オーギュスト・ルノワール
0.2000000.0285710.050000ブラジル人、アンドレ
0.0287450.6390980.055016クロードオスカーモネ

結論


芸術作品はユニークです。アーティストは、自分の作品を何度も賞賛する多くの作曲技法を使用しています。
配色は重要ですが、アーティストの作品の分析における唯一の要素とはほど遠いものです。

多くの本物の芸術評論家は分析の素朴さを笑いますが、それでも私は行われた仕事に満足しました。まだ手に届いていないアイデアがいくつかあります。

  1. クラスタリングアルゴリズムをアーティストの配色の分析に適用します。確かに私たちはそこで興味深いグループを強調し、絵画のさまざまな傾向を区別できます
  2. クラスタリングアルゴリズムを個々の絵画に適用します。配色で識別できる「プロット」を検索します。たとえば、異なるクラスターでは、風景、ポートレート、静物を取得します
  3. ペア、トリプル、スクエアだけでなく、イッテンのサークルの他の組み合わせも検索
  4. ピクセルを場所ごとにグループ化することにより、周波数分析からカラースポット分析に移行する
  5. 著者が疑わしい作品を見つけ、モデルが誰に投票するかを確認します

PS


この記事は、もともとは機械学習コースの卒業プロジェクトでしたが、Habrの資料に変えることを推奨する人もいました。

興味を持っていただければ幸いです。

作業で使用されるすべてのコードはgithubで入手できます

All Articles