#はじめに
今回Pythonの学習も兼ねて行列近似を実装してみました。
実装は、行列の近似の応用例の1つである画像圧縮です。
行列近似を理解するには特異値分解の理解が必要ですが、筆者は説明できないのでほかサイトをご参照ください。
#実装
まずは結果を載せます。
結果
pythonコード
(docコメントの書き方間違っていたらごめんなさい)
def im_comp_gray(A, r):
"""グレースケール画像のランクr近似
Args:
A (numpy.ndarray): 近似対象の行列
r (int): 近似のランク
Returns:
numpy.ndarray: rランク近似された画像
"""
# svdの実行, Falseでeconomy svd実行
# Sは特異値のみなので注意、VはVの転置行列なので注意
U, s, V = np.linalg.svd(A, False)
# m x n の行列を定義
compressed_im = np.ones(A.shape)
# Uを縦ベクトル、Vを横ベクトルにスライス
for i in range(r):
compressed_im += s[i] * U[:, i:i+1].dot(V[i:i+1, :])
return compressed_im
im_path = [input_file_path]
im_out_path = [output_file_path]
r = 100
def main():
# グレースケールでの画像の読み込み
im = cv2.imread(im_path, cv2.IMREAD_GRAYSCALE)
check_image(im)
compressed_im = im_comp_gray(im, r)
# 結果画像の出力
cv2.imwrite(im_out_path, compressed_im)
if __name__ == "__main__":
main()
#追記
カラー画像を圧縮コードも作成しました。
結果
def comp_im(A, r):
"""カラー画像のrランク近似
Args:
A (numpy.ndarray): 近似対象の行列
r (int): 近似のランク
Returns:
numpy.ndarray: rランク近似された画像
"""
# カラーの情報(3)
rgb = A.shape[2]
# ループで処理できるようにを1次元目に設定
# 最後に配列の順番をもとに戻す (transpose()メソッドを使用)
compressed_im = np.ones((rgb, A.shape[0], A.shape[1]))
# RGBを0, 1, 2として処理
for n in range(rgb):
# R, G, Bそれぞれをスライスして取得(1ループ目:R、2ループ目:G、3ループ目:B)
C = A[:, :, n]
# svdの実行, Falseでeconomy svd実行
# Sは特異値のみなので注意、VはVの転置行列なので注意
U, s, V = np.linalg.svd(C, False)
# Uを縦ベクトル、Vを横ベクトルにスライス
for i in range(r):
compressed_im[n] += s[i] * U[:, i:i+1].dot(V[i:i+1, :])
return compressed_im.transpose(1, 2, 0)
im_path = [input_file_path]
im_out_path = [output_file_path]
r = 100
def main():
#カラー画像の読み込み
im = cv2.imread(im_path)
check_image(im)
compressed_im = comp_im(im, r)
# 結果画像の出力
cv2.imwrite(im_out_path, compressed_im)
if __name__ == "__main__":
main()
#補足
行列の近似は、合計でmこの縦ベクトルと横ベクトルの積(u * v)の和を、r番目まで打ち切ります。
そうするともとの行列にほぼ等しくなります。
特異値分解
A = U\Sigma V^T = \sigma_1 u_1 v^T_1 + ... + \sigma_m u_m v^T_m
行列の近似
A \simeq \tilde{A}_r = U_r \Sigma_r V^T_r = \sigma_1 u_1 v^T_1 + ... + \sigma_r u_r v^T_r
#おわりに
白黒の画像の圧縮とカラーの圧縮それぞれやってみました。
この記事を書くにあたって、Pythonの練習も兼ねて実装しましたが、Python結構難しかったです。
ただ、c言語やjava言語にない多次元行列を縦ベクトル、横ベクトルとして切り出せる「スライス」は嬉しい機能だと思いました。
qiitaで記号を載せたり画像を載せたりするのは思いの外大変でした。
丁寧に記事を書いてくださる方いつも感謝しております。
この記事が皆さんのご参考になれば嬉しいです。
なにかご指摘あれば教えていただけると幸いです。以上です。
#参考
実装にあたり以下のサイトを参考にさせていただきました。
特異値分解による行列の低ランク近似の基礎をまとめる
https://seetheworld1992.hatenablog.com/entry/2018/08/16/155013
特異値分解による画像の低ランク近似
https://qiita.com/kaityo256/items/48de63526b469235d16a
Python, OpenCVで画像ファイルの読み込み、保存(imread, imwrite)
https://note.nkmk.me/python-opencv-imread-imwrite/