(追記: どの応募案がどのクラスに分類されたかの結果を貼り忘れていました。CSVファイルをダウンロード)
2014年の建築界隈の主要トピックスの一つであるGuggenheim Helsinkiのデザインコンペティションは1715の応募数を集め、2002年の大エジプト博物館コンペの応募数1557を凌ぐ史上最大規模の建築デザインコンペとなったようです。(参考)
建築デザインの経済的求心力がビルバオ・グッゲンハイムで明らかになったことが、人々の関心を建築に集めたことは間違いないでしょう。
応募案はhttp://designguggenheimhelsinki.org/stageonegallery/view/ にアクセスすれば一覧出来ますが、とにかく多すぎる。審査員もうんざりしたことでしょう。僕は傾向を見たいだけなので、適当に画像処理技術を使ってクラスタリングしてみました。
審査員も適当にザッピングして評価してるよ。きっと。建築の歴史はメディアの歴史でどうこうむにゃむにゃ言うつもりはありません。
あわせて読みたい:
Contents
- 1 素材集め
- 2 処理系の準備
- 3 手法
- 3.1 クラス0: 216案, ファイナリスト1(GH-76091181)
- 3.2 クラス1: 181案, ファイナリスト0
- 3.3 クラス2: 284案, ファイナリスト1(GH-121371443)
- 3.4 クラス3: 186案, ファイナリスト0
- 3.5 クラス4: 250案, ファイナリスト3(GH-04380895, GH-1128435973, GH-5631681770)
- 3.6 クラス5: 271案, ファイナリスト0
- 3.7 クラス6: 210案, ファイナリスト1(GH-5059206475)
- 3.8 クラス7: 117案, ファイナリスト0
- 3.9 クラス4-0: 76案, ファイナリスト1(GH-5631681770)
- 3.10 クラス4-1: 59案, ファイナリスト0
- 3.11 クラス4-2: 115案, ファイナリスト2(GH-04380895, GH-1128435973)
素材集め
http://designguggenheimhelsinki.org/stageonegallery/view/にアクセスしてctrl+sでhtmlと画像ファイルをダウンロードします。なぜかファイナリストの画像だけダウンロードできなかったので、ファイナリスト6人のエントリーIDごとに”http://a5.designguggenheimhelsinki.org/GH-1128435973-partC1.jpg”にアクセスして画像をダウンロードします。
これで150×150ピクセルの1715個の画像が集まりました。
処理系の準備
解析に使うのはもちろんPython。なぜなら彼もまた特別な存在だからです。
Anacondaをインストールすればだいたい必要なもの(numpyとかpandasとか)はインストールされます。
あとは追加で画像処理ライブラリのpillow(PIL)をインストールしましょう。コマンドプロンプトから以下をタイプ。
easy_install pillow
手法
教師ありの画像認識的な分類(食べ物の画像かどうか)の例はいくつかあったのですが、教師なしで応募案の画像をクラスタリングする、という感じの例はありませんでした。
なので、画像を読み込んで特徴ベクトル化するところまでは教師あり分類の例にしたがって、特徴ベクトル群をk-meansでクラスタリングしてみます。
参考にしたのは以下。
まず、1715個の画像を読み込んで、150×150のRGB値のデータをRandomized PCAで2次元まで減らしてプロットしてみます。
あんまりうまいこと分かれている感じはしませんが、まあそんなもんでしょう。
では、今度は20次元にしたデータをscikit-learnのk-meansで8つのクラスタに分けてみます。
クラス0: 216案, ファイナリスト1(GH-76091181)
青空の遠景にひっそりとたたずむ画像がほとんどです。あっ、コンペ要旨の割と最初らへんに出てくる画像ですねコレ。堂々とした正攻法が好印象です。
ここに属するファイナリストはGH-76091181。色にこだわりを感じます。
クラス1: 181案, ファイナリスト0
光と影のコントラストを強調したパースが多いです。この建築のここがいいっしょ!?という強い意志とフェティシズムを感じます。しかし周辺との関係はわかりづらいかも。
クラス2: 284案, ファイナリスト1(GH-121371443)
全体的に薄暗く、描線の多い絵 or 雲の存在感のあるマジックアワー、といったところ。雲の画像の方はコンペ要旨の最後らへんの画像です。
クラス3: 186案, ファイナリスト0
クラス2よりもさらに時間は進み、夜との境目~夜。個人的にはこれぐらいの時刻の写真が好きです。
クラス4: 250案, ファイナリスト3(GH-04380895, GH-1128435973, GH-5631681770)
全体的に低めな彩度や霞のかかった画像。エッジの効いた彫刻のような存在感ではなく、曖昧な視界にヌッと現れる不気味な存在感、という感じ。
ファイナリストはGH-04380895, GH-1128435973, GH-5631681770と3つも属しています。
カッチリした形で勝負するのでないところが、ヘルシンキという文脈に合うのかもしれません。
クラス5: 271案, ファイナリスト0
さらに白い。白いが昼間の画像が多い感じです。ヘルシンキの天気を考慮したもののその文脈での美しさを見つけられなかった、という感じでしょうか。
クラス6: 210案, ファイナリスト1(GH-5059206475)
白すぎだろ!空が無いものだったり背景が白の図面などが属します。
ファイナリストはGH-5059206475
うーん白い。白いっていうかなんだこれは。
クラス7: 117案, ファイナリスト0
上下余白のある画像です。そういうの分類しにくいからマジ勘弁です。
ここまででクラス4が強いことが分かりました。彩度は低めで、かと言って低すぎず、明度も高すぎず、そしてなにより光っていること。超大事なようです。
ではクラス4をさらに3つぐらいに分割してみましょう。
クラス4-0: 76案, ファイナリスト1(GH-5631681770)
クラス4-1: 59案, ファイナリスト0
クラス4-2: 115案, ファイナリスト2(GH-04380895, GH-1128435973)
やはり霧か…
僕の持論として、美しい画像 = 一つに統一された傾向を持っている画像という法則があるのですが、霧の画像というのは、全体に一つのフィルタをかけるようなものなので統一感が出しやすいのですね。他に統一感のある画像としては、夕焼け(色相)、落ち葉(ベクトル)、木漏れ日(色相、コントラスト)などがありますね。
とにかくここで結論として言えることは、ヘルシンキのコンペでは
- 霧を使うこと
- 建築を光らせること
- 暗すぎてもだめ
という部分を守ると当選確率が上がるかも、という結果論です。これで誰か僕を建築批評シンポジウムか何かに呼んでくれるでしょう(ワクワク)。
一応最後にコードを載せておきます。
#-*- encoding: utf-8 -*-
from PIL import Image
import numpy as np
import os
import pandas as pd
import pylab as plt
import shutil
from sklearn.decomposition import RandomizedPCA
from sklearn.cluster import KMeans
STANDARD_SIZE = (150, 150)
def img_to_matrix(filename, verbose=False):
"""
load img file as a np.ndarray
"""
img = Image.open(filename)
if verbose:
print('changing size from %s to %s' % (str(img.size), str(STANDARD_SIZE)))
img = img.resize(STANDARD_SIZE)
imgArray = np.asarray(img)
return imgArray
def flatten_image(img):
"""
takes in an (m, n) numpy array and flattens it
into an array of shape (1, m * n)
"""
s = img.shape[0] * img.shape[1] * img.shape[2]
img_wide = img.reshape(1, s)
return img_wide[0]
def plot_in_2_dimensions(data):
"""
plot in 2 dimensions. (Reshape by RandomizedPCA)
"""
pca = RandomizedPCA(n_components=2)
X = pca.fit_transform(data)
df = pd.DataFrame({"x": X[:, 0], "y": X[:, 1], "name": images})
colors = ['red', 'yellow']
plt.scatter(df['x'], df['y'], c=colors[0], label=images)
# labels
# for image, x, y in zip(images, X[:,0], X[:,1]):
# plt.annotate(image, xy = (x, y), xytext = (-20, 20),
# textcoords = 'offset points', ha = 'right', va = 'bottom',
# bbox = dict(boxstyle = 'round,pad=0.5', fc = 'black', alpha = 0.5))
plt.savefig('pca_feature.png')
plt.show()
def main():
img_dir = 'images/'
images = [img_dir + f for f in os.listdir(img_dir)]
data = []
for image in images:
img = img_to_matrix(image)
img = flatten_image(img)
data.append(img)
data = np.array(data)
# plot_in_2_dimensions(data)
# k-means clustering
pca = RandomizedPCA(n_components=20)
X = pca.fit_transform(data)
km = KMeans(n_clusters=3)
labels = km.fit_predict(X)
for i, image in enumerate(images):
print(image[7:] + "," + str(labels[i]))
shutil.copyfile(image, "clustered/" + str(labels[i]) + image[6:])
if __name__ == '__main__':
main()
コメント