*Aya's Prelude*

大学院生のよしなごと

グレースケール画像の低ランク近似withSVDで遊んだ(追記:RGBもやってみた)

学校の授業で楽しそうな定理をやったので実際にやってみた.

定理

定理書こうと思ったけどはてなブログLaTeX使うのめんどくさすぎる. やめた. 簡単にいうとある行列を特異値分解したときにそのUSVを使って低ランク k \leq nなる kで近似できるよって話. まじでLaTeX.

設定

実際に低ランクの行列でどこまででるのかやってみた. 用いる画像は「[甘美なる宝石]佐久間まゆ」の画像をグレースケールに変換したもの.サイズは800*640. これを以下のkランクまでの情報を持つ行列で近似していく.

結果

結果は以下のようになった.

  • 元の画像

f:id:remark_tzi:20191105234004j:plain
元画像

  • k = 600

f:id:remark_tzi:20191105233912j:plain
k = 600

  • k = 500

f:id:remark_tzi:20191105234039j:plain
k = 500

  • k = 400

f:id:remark_tzi:20191105234058j:plain
k = 400

  • k = 300

f:id:remark_tzi:20191105234112j:plain
k = 300

  • k = 200

f:id:remark_tzi:20191105234128j:plain
k = 200

  • k = 100

f:id:remark_tzi:20191105234145j:plain
k = 100

  • k = 50

f:id:remark_tzi:20191105234158j:plain
k = 50

気持ち

低ランクで結構絵になっててすげーという気持ちがある.

800640のデータが5050だけの情報であんなにちゃんと出るんだなぁという感動.

別に考察がしたかったりしたわけではないので楽しかったですって感想しか出ないな.

プログラム

環境はPython3. matlabでやればよかったけどまだPC変えてからいれてなかったのでまた今度.

# ライブラリのインポート
import numpy as np
from PIL import Image

# 画像の読み込みとグレースケール化
img = Image.open('images/mayu/mayu.jpg')
gray_img = img.convert('L')
gray_img.save('images/mayu/mayu_mono.jpg')

# 行列化
array = np.array(gray_img)

# 形状の確認
print(array.shape)
(800, 640)
# 落としたい次数(array.shapeで確認した数字以下)
k = 500

# 特異値分解
U,S,V = np.linalg.svd(array)
S = np.diag(S)

# 低ランク近似のための準備
U_k = U[:,:k]
S_k = S[:k,:k]
V_k = V[:k,:]

# 近似
array_k = np.dot(np.dot( U_k , S_k),V_k )

# 画像の保存
img_k = Image.fromarray(np.uint8(array_k))
img_k.save('images/mayu_mono_r%d.jpg' %k)

今後の展望

できれば今度はRGBとかでもやりたいなって思って色々考えたけどとりあえずRGBごとに特異値分解して元に戻すみたいなのしてもいいのかな?とか思った.

先生に色々相談したらRGBだとテンソルになるし高次元特異値分解ってのやってみなとのこと.

こっちもそういう定理あるのかな. 楽しそう.

追記

RGB版もできたら楽しいかなと思ってとりあえず行列ごとに分解して近似してみた.

設定

上の画像と同じもの. 但し, RGBのまま.

結果

kの設定も一緒!

  • 元の画像

f:id:remark_tzi:20191106002646j:plain
元の画像

  • k = 600

f:id:remark_tzi:20191106002702j:plain
k = 6

  • k = 500

f:id:remark_tzi:20191106002726j:plain
k = 500

  • k = 400

f:id:remark_tzi:20191106002739j:plain
k = 400

  • k = 300

f:id:remark_tzi:20191106002753j:plain
k = 300

  • k = 200

f:id:remark_tzi:20191106002805j:plain
k = 200

  • k = 100

f:id:remark_tzi:20191106002818j:plain
k = 100

  • k = 50

f:id:remark_tzi:20191106002833j:plain
k = 50

プログラム

# ライブラリのインポート
import numpy as np
from PIL import Image
# SVDと近似用の関数
def svd_and_approximation(array, rank):
    u,s,v = np.linalg.svd(array)
    s = np.diag(s)
    u_k = u[:,:rank]
    s_k = s[:rank,:rank]
    v_k = v[:rank,:]
    return np.asarray(np.dot(np.dot(u_k,s_k),v_k))
# 画像の読み込み
img = Image.open('images/mayu/mayu.jpg')

# 行列化
array = np.array(img)

# 形状の確認
print(array.shape)
print(array.size)
(800, 640, 3)
1536000
# 落としたい次数(array.shapeで確認した数字以下)
k = 600

# RGBごとに行列を分ける.
R = array[:,:,0]
G = array[:,:,1]
B = array[:,:,2]


array_R = svd_and_approximation(R,k)
array_G = svd_and_approximation(G,k)
array_B = svd_and_approximation(B,k)

rgb_k = np.asarray([array_R,array_G,array_B]).transpose(1,2,0)
 
# 画像の保存
img_k = Image.fromarray(np.uint8(rgb_k))
img_k.save('images/mayu_rgb/mayu_r%d.jpg' %k)