Kami menganalisis karya-karya lukisan dengan bantuan ML klasik

Halo semuanya! Seorang teman saya belajar sebagai seniman dan secara teratur membicarakan karya ini atau itu, tentang teknik komposisi yang unik, tentang persepsi warna, tentang evolusi seni lukis dan seniman yang brilian. Terhadap latar belakang dampak konstan ini, saya memutuskan untuk memeriksa apakah pengetahuan dan keterampilan teknik saya cocok untuk menganalisis warisan budaya dunia.

Berbekal parser darurat di bawah penutup malam, saya masuk ke galeri online dan mengeluarkan hampir 50 ribu lukisan dari sana. Mari kita lihat apa yang menarik untuk dilakukan dengan ini, hanya menggunakan alat ML klasik (hati-hati, lalu lintas).

Transformasi naif


Seperti banyak dari kita ingat dari pelajaran ilmu komputer, gambar direpresentasikan sebagai array byte yang bertanggung jawab atas warna setiap piksel individu. Sebagai aturan, skema RGB digunakan, di mana warna dibagi menjadi tiga komponen (merah / hijau / biru), yang, ketika dijumlahkan dengan latar belakang hitam, memberikan warna asli yang dirasakan oleh seseorang.

Karena sekarang bagi kami semua karya sementara hanya menjadi array angka pada disk, kami akan mencoba untuk mengkarakterisasi array ini dengan membangun histogram distribusi frekuensi intensitas untuk setiap saluran.

Kami akan menggunakan numpy untuk perhitungan, dan memvisualisasikan menggunakan matplotlib.

Sumber
#      
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()


Contoh karya:









Setelah melihat histogram berbagai gambar dengan cermat, kita dapat memperhatikan bahwa bentuknya sangat spesifik dan sangat bervariasi dari pekerjaan ke pekerjaan.

Dalam hal ini, kami membuat asumsi bahwa histogram adalah semacam pemeran gambar, yang memungkinkannya untuk dikarakterisasi sampai batas tertentu.

Model pertama


Kami mengumpulkan semua histogram ke dalam satu dataset besar dan mencoba mencari beberapa “anomali” di dalamnya. Cepat, nyaman, dan umumnya algoritma favorit saya untuk keperluan seperti itu adalah svm satu kelas. Kami akan menggunakan implementasinya dari perpustakaan sklearn

Sumber
#       ,       
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)


Mari kita lihat apa yang aneh yang kita temukan di tempat sampah galeri kita.

Pekerjaan yang dilakukan dengan pensil:


Bekerja dalam warna yang sangat gelap:

Wanita berbaju merah:

Sesuatu yang samar:

Potret yang sangat gelap:


Cari pekerjaan serupa


Nah, model kami menemukan sesuatu yang tidak biasa, jauh dari segalanya.

Tetapi bisakah kita membuat alat yang akan membantu menemukan pekerjaan serupa dalam warna?

Sekarang setiap gambar ditandai oleh vektor dari 153 nilai (karena ketika membangun histogram, itu mencapai 5 unit intensitas per bin, total 255/5 = 51 frekuensi untuk setiap saluran).

Kita dapat menentukan “derajat kesamaan” dengan menghitung jarak antara vektor yang menarik bagi kita. Jarak Euclidean yang akrab dengan sekolah di sini akan memberi banyak perhatian pada panjang komponen vektor, dan kami ingin lebih memperhatikan set warna yang membentuk gambar. Di sini kita akan menemukan ukuran kosinus jarak yang banyak digunakan, misalnya, dalam masalah analisis teks. Mari kita coba terapkan untuk tugas ini. Kami mengambil implementasinya dari perpustakaan yang cerdik.

Sumber
#  ,        
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]


Mari kita lihat apa yang tampak seperti "Gelombang Kesembilan" Aivazovsky.

Asli:



Karya serupa:





Apa yang tampak seperti “Bunga Almond” Van Gogh.

Asli:



Karya serupa:





Dan apa yang tampak seperti wanita aneh yang sebelumnya berwarna merah?

Asli:



karya serupa:






Ruang warna


Sampai saat itu, kami bekerja di ruang warna RGB. Sangat mudah untuk memahami, tetapi jauh dari ideal untuk tugas kita.

Lihat, misalnya, di tepi kubus warna RGB



Dengan mata telanjang Anda dapat melihat bahwa ada area besar pada wajah di mana mata kita tidak melihat perubahan, dan area yang relatif kecil di mana persepsi warna kita berubah sangat tajam. Persepsi non-linear ini mencegah mesin mengevaluasi warna seperti yang dilakukan seseorang.

Untungnya, ada banyak ruang warna, mungkin beberapa akan sesuai dengan tugas kita.

Kami akan memilih ruang warna favorit kami dengan membandingkan kegunaannya dalam memecahkan masalah manusia. Misalnya, mari kita hitung artis dengan isi kanvas!

Ambil semua ruang warna yang tersedia dari pustaka pembuka, latih xgboost di masing-masing, dan lihat metrik pada pilihan yang ditangguhkan.

Sumber
# ,        
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")


presisipenarikanskor f1ruang warna
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

Peningkatan nyata dalam kualitas diberikan oleh penggunaan ruang warna LUV.

Pembuat skala ini mencoba membuat persepsi perubahan warna di sepanjang sumbu skala seseragam mungkin. Berkat ini, perubahan warna yang dirasakan dan evaluasi matematisnya akan sedekat mungkin.

Ini adalah bagaimana sepotong ruang warna yang diberikan terlihat ketika memperbaiki salah satu sumbu:



Mari kita lihat modelnya


Setelah langkah sebelumnya, kami masih memiliki model yang dapat memprediksi sesuatu.
Mari kita lihat karya siapa yang kita pelajari paling akurat.
presisipenarikanskor f1Artis
0,0425530,0194170,026667Ilya Efimovich Repin
0,0555560,0200000,029412William Merrit Chase
0,0714290,0222220,033898Bonnard pierre
0,0354610,0352110,035336Jill elvgren
0,1000000,0217390,035714Jean Auguste Dominic Ingres
0,0228140,2240660,041411Pierre Auguste Renoir
0,1000000,0285710,044444Albert Bierstadt
0,2500000,0322580,057143Hans Zatska
0,0303960,5187970,057428Claude Oscar Monet
0,2500000,0370370,064516Girotto walter

Metrik sendiri jauh dari ideal, tetapi Anda harus ingat bahwa skema warna adalah sebagian kecil dari informasi tentang pekerjaan. Seniman menggunakan banyak cara ekspresif. Fakta yang kami temukan dalam data ini "tulisan tangan" tertentu dari artis sudah merupakan kemenangan.

Kami akan memilih salah satu artis untuk analisis lebih dalam. Biarlah itu Claude Oscar Monet (saya akan membuat istri saya baik, dia suka impresionis).

Mari kita ambil karyanya, minta model untuk memberi tahu kami penulis dan menghitung frekuensi
Penulis yang diprediksiJumlah prediksi
Claude Oscar Monet186
Pierre Auguste Renoir171
Vincent Van Gogh25
Peter Paul Rubenssembilan belas
Gustave Dore17

Banyak orang cenderung membingungkan Monet dan Manet, dan model kami lebih memilih untuk membingungkannya dengan Renoir dan Van Gogh. Mari kita lihat apa yang menurut model itu seperti Van Gogh.










Dan sekarang kita akan menggunakan fungsi pencarian kita untuk karya-karya serupa dan menemukan lukisan Van Gogh mirip dengan karya-karya yang disebutkan di atas (kali ini kita akan mengukur jarak di ruang LUV).

Asli:



Pekerjaan serupa:



Asli:



Karya serupa:






Asli:



Karya serupa:





Puas dengan diri saya sendiri, saya menunjukkan hasilnya kepada seorang teman dan menemukan bahwa pendekatan histogram sebenarnya cukup kasar, karena menganalisis distribusi bukan warna itu sendiri, tetapi komponen-komponennya secara terpisah. Selain itu, frekuensi warna tidak begitu penting seperti komposisi mereka. Ternyata seniman kontemporer telah membuktikan pendekatan untuk pemilihan skema warna. Jadi saya mencari tahu tentang Johannes Itten dan roda warnanya.

Roda warna Itten




Johannes Itten adalah seorang seniman, ahli teori seni dan guru, penulis buku terkenal tentang bentuk dan warna. Roda warna adalah salah satu alat paling terkenal yang membantu memadukan warna agar menyenangkan mata.

Kami menggambarkan metode pemilihan warna yang paling populer:



  1. Warna komplementer - terletak di bagian yang berlawanan dari lingkaran
  2. Warna yang berdekatan - berdekatan dengan lingkaran
  3. Triad Klasik - Warna di Puncak Segitiga Sama Sisi
  4. Contrast Triad - Warna di Puncak Segitiga Sama Kaki
  5. Rectangle Rule - Warna pada simpul persegi panjang
  6. Aturan kotak - warna di bagian atas kotak

Kami menganalisis sebagai seniman


Mari kita coba praktikkan pengetahuan yang didapat. Untuk memulai, kita mendapatkan serangkaian warna yang tergeletak di roda warna, mengenalinya dari gambar.



Sekarang kita dapat membandingkan secara berpasangan setiap piksel dari lukisan kita dengan berbagai warna lingkaran Itten. Kami mengganti piksel asli dengan piksel terdekat di roda warna dan menghitung frekuensi warna pada gambar yang dihasilkan

Sumber
#          
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');












Pernahkah Anda memperhatikan bagaimana "Arlesian Muda" telah berubah sedikit setelah transformasi kami? Mungkin ada baiknya mengukur tidak hanya frekuensi warna baru, tetapi juga statistik tentang kesalahan konversi - ini dapat membantu kami dalam analisis.

Tetapi ini tidak cukup untuk analisis saat ini. Mari kita mencari kombinasi harmonis dalam lingkaran?

Semua pasangan pelengkap:



Semua triad klasik:



Dan semua kotak:



Kami akan mencari kombinasi ini dalam lukisan kami, menemukan yang paling signifikan (dalam frekuensi) dan
melihat apa yang terjadi.

Untuk memulai pasangan:











Lalu triad:













Dan, akhirnya, kuadrat:















Tidak buruk secara mata, tetapi apakah metrik baru akan membantu menentukan penulis karya?

Kami akan melatih model hanya menggunakan frekuensi warna Itten, karakteristik kesalahan, dan kombinasi harmonis yang ditemukan.

Kali ini, daftar artis yang paling "dapat diprediksi" telah berubah sedikit, yang berarti bahwa pendekatan analisis yang berbeda memungkinkan kami untuk mengekstrak beberapa informasi lebih banyak dari isi gambar.
presisipenarikanskor f1Artis
0,0434780,0121950,019048Martin, Henri-Jean-Guillaume
0,0326800,0290700,030769Camille Pissarro
0,1666670,0196080,035088Jean-Leon Jerome
0,0769230,0277780,040816Turner, Joseph Mallord William
0,1333330,0243900,041237Poortvliet, Rien
0,1000000,0263160,041667Max Klinger
0,0267250,2282160,047847Pierre Auguste Renoir
0,2000000,0285710,050000Brasilier, Andre
0,0287450,6390980,055016Claude Oscar Monet

Kesimpulan


Karya seni itu unik. Seniman menggunakan banyak teknik komposisi yang membuat kita mengagumi karya mereka berulang kali.
Skema warna adalah penting, tetapi jauh dari satu-satunya komponen dalam analisis karya seniman.

Banyak kritikus seni sejati akan menertawakan kenaifan analisis, tetapi saya masih puas dengan pekerjaan yang dilakukan. Ada beberapa ide lagi yang belum sampai ke tangan:

  1. Terapkan algoritma pengelompokan untuk analisis skema warna seniman. Tentunya kita bisa menyoroti kelompok-kelompok menarik di sana, membedakan berbagai tren dalam seni lukis
  2. Menerapkan algoritma pengelompokan untuk masing-masing lukisan. Cari "plot" yang dapat diidentifikasi dengan skema warna. Misalnya, di berbagai kluster mendapatkan lanskap, potret, dan benda mati
  3. Cari tidak hanya pasangan, tiga kali lipat dan kuadrat, tetapi juga kombinasi lain dari lingkaran Itten
  4. Pindah dari analisis frekuensi ke analisis titik warna dengan mengelompokkan piksel berdasarkan lokasi
  5. Temukan karya yang kepengarangannya diragukan dan lihat siapa model yang akan memilih

PS


Artikel ini awalnya merupakan proyek kelulusan untuk kursus pembelajaran mesin , tetapi beberapa orang merekomendasikan untuk mengubahnya menjadi materi untuk Habr.

Saya harap Anda tertarik.

Semua kode yang digunakan dalam pekerjaan tersedia di github .

All Articles