我们借助经典ML分析绘画的杰作

大家好!我的一个朋友以艺术家的身份学习,并定期谈论这个或那个杰作,独特的构图技术,色彩感知,绘画和才华横溢的艺术家的发展。在这种持续影响的背景下,我决定检查我的工程知识和技能是否适合分析世界文化遗产。

夜幕降临时,我手持一个临时解析器,闯入了在线画廊,并从那里拿出近5万幅画作。让我们看看仅使用经典的ML工具(警告,点击量)对此有何意义。

天真的转变


正如我们许多人从计算机科学课程中所记得的那样,图像被表示为负责每个单独像素颜色的字节数组。通常,使用RGB方案,其中将颜色分为三个分量(红色/绿色/蓝色),当将其与黑色背景相加时,将给出人可以感知的原始颜色。

从现在开始,对于我们来说,所有杰作暂时都只是磁盘上的数字阵列,我们将尝试通过构建每个通道的强度频率分布直方图来表征这些阵列。

我们将使用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()


作品示例:









仔细查看不同图片的直方图后,我们可以发现它们的形式非常具体,并且因作品而异。

在这方面,我们假设直方图是图片的一种投射,这可以在一定程度上对其进行表征。

第一个模型


我们将所有直方图收集到一个大型数据集中,并尝试在其中查找一些“异常”。快速,方便,通常我最喜欢的算法是一类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个值的向量(因为在构建直方图时,它每个bin命中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]


让我们看一下艾瓦佐夫斯基的《第九波》。

原创:



相似的作品:





看起来像梵高的“开花杏仁”。

原件:



相似的作品:





看起来像一个异常的女士早些时候发现红色?

原创:



同类作品:






色彩空间


在那一刻之前,我们一直在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颜色空间,可以明显提高质量。

该标尺的创建者试图使沿标尺轴的颜色变化的感觉尽可能均匀。因此,可以感觉到的颜色变化及其数学评估将尽可能接近。

这是固定轴之一时给定颜色空间的一部分的样子:



让我们看一下模型


在上一步之后,我们仍然具有可以预测某些内容的模型。
让我们看看我们最准确地学习谁的工作。
精确召回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汉斯·扎特斯卡(Hans Zatska)
0.0303960.5187970.057428克劳德·奥斯卡·莫奈
0.2500000.0370370.064516吉罗托·沃尔特

度量标准本身并不理想,但是您需要记住,配色方案只是有关作品信息的一小部分。艺术家使用许多表达方式。我们在这些数据中发现艺术家的某种“笔迹”,这已经是一个胜利。

我们将选择一位艺术家进行更深入的分析。让我们成为克劳德·奥斯卡·莫奈(Claude Oscar Monet)(我会让我的妻子好,她喜欢印象派画家)。

让我们开始他的工作,让模型告诉我们作者并计算频率
预言作者预测数
克劳德·奥斯卡·莫奈186
皮埃尔·奥古斯特·雷诺阿171
文森特 - 梵高25
彼得·保罗·鲁本斯十九
古斯塔夫·多尔17

许多人倾向于混淆莫奈和马奈,而我们的模型更倾向于混淆他与雷诺阿和梵高。让我们看看根据模型,什么与梵高相似。










现在,我们将使用搜索功能查找相似的作品,并查找与上述作品相似的梵高画作(这次我们将测量LUV空间中的距离)。

原创:



相似作品:



原创:



相似作品:






原创:



相似作品:





我对自己满意,将结果展示给朋友,发现直方图方法实际上是很不礼貌的,因为它不分析颜色本身的分布,而是分别分析颜色的分布。另外,颜色的频率与其组成无关,它也很重要。事实证明,当代艺术家已经证明了选择配色方案的方法。所以我发现了约翰内斯·伊滕(Johannes Itten)和他的色轮。

伊滕的色轮




约翰尼斯·伊滕(Johannes Itten)是一位艺术家,艺术理论家和老师,也是有关形式和色彩的著名著作的作者。色轮是最著名的工具之一,可帮助您组合颜色以取悦眼睛。

我们说明了最流行的颜色选择方法:



  1. 互补色-位于圆的相对部分
  2. 相邻的颜色-与圆相邻
  3. 古典三合会-等边三角形顶部的颜色
  4. 对比三合会-等腰三角形顶部的颜色
  5. 矩形规则-矩形顶点处的颜色
  6. 正方形的规则-正方形顶部的颜色

我们作为艺术家进行分析


让我们尝试将获得的知识付诸实践。首先,我们在色轮上获得一系列颜色,并从图片中识别它们。



现在,我们可以成对比较绘画中的每个像素和一系列Itten圆形颜色。我们用色轮中最接近的像素替换原始像素,并计算结果图像中颜色的频率

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












您是否注意到我们的转型后“ Young Arlesian”的变化很小?也许不仅值得测量新颜色的频率,还值得测量转换错误的统计信息-这可以帮助我们进行分析。

但这对于当前的分析是不够的。让我们围成一圈寻找和谐的组合吗?

所有互补对:



所有古典三合会:



和所有正方形:



我们将在绘画中寻找这些组合,找到最有意义的(频率上)并
观察会发生什么。

首先开始:











然后是三合会:













最后是正方形:















肉眼看还不错,但是新指标将有助于确定作品的作者吗?

我们将仅使用Itten的色频,错误特征和发现的和谐组合来训练模型。

这次,最“可预测”的艺术家列表有所变化,这意味着不同的分析方法使我们能够从图片内容中提取更多信息。
精确召回F1分数艺术家
0.0434780.0121950.019048马丁·亨利·吉恩·纪尧姆
0.0326800.0290700.030769卡米尔·毕沙罗(Camille Pissarro)
0.1666670.0196080.035088让·里昂·杰罗姆
0.0769230.0277780.040816特纳,约瑟夫·马洛德·威廉
0.1333330.0243900.041237里恩Poortvliet
0.1000000.0263160.041667马克斯·克林格
0.0267250.2282160.047847皮埃尔·奥古斯特·雷诺阿
0.2000000.0285710.050000Brasilier,安德烈
0.0287450.6390980.055016克劳德·奥斯卡·莫奈

结论


艺术品是独一无二的。艺术家使用许多构图技术,使我们一次又一次地欣赏他们的作品。
配色方案很重要,但远非艺术家作品分析中的唯一组成部分。

许多真实的艺术评论家会嘲笑这种分析的天真,但我仍然对所做的工作感到满意。还有其他一些想法尚未掌握:

  1. 将聚类算法应用于艺术家的配色方案分析。当然,我们可以在那里突出有趣的群体,区分绘画的各种趋势
  2. 将聚类算法应用于单个绘画。搜索可以通过配色方案识别的“图形”。例如,在不同的群集中获取风景,肖像和静物
  3. 不仅搜索对,三元组和正方形,还搜索来自Itten圆的其他组合
  4. 通过按位置分组像素,从频率分析转移到色斑分析
  5. 查找怀疑作者身份的作品,并查看该模型将投票给谁

聚苯乙烯


本文最初是机器学习课程的毕业项目,但一些人建议将其转变为Habr的材料。

我希望你有兴趣。

工作中使用的所有代码都可以在github上找到

All Articles