LoginSignup
2
2

More than 1 year has passed since last update.

Pythonによる画像圧縮(特異値分解)

Last updated at Posted at 2022-01-22

#はじめに
今回Pythonの学習も兼ねて行列近似を実装してみました。
実装は、行列の近似の応用例の1つである画像圧縮です。
行列近似を理解するには特異値分解の理解が必要ですが、筆者は説明できないのでほかサイトをご参照ください。

#実装
まずは結果を載せます。
結果

original
cute_dakkusu_original.jpg

r = 10
cute_dakkusu_original_r10.jpg

r = 100
cute_dakkusu_original_r100.jpg

pythonコード
(docコメントの書き方間違っていたらごめんなさい)

matrix_approximation.py
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()

#追記
カラー画像を圧縮コードも作成しました。

結果

original
cute_dakkusu_original.jpg

r = 10
cute_dakkusu_original_r10.jpg

r = 100
cute_dakkusu_original_r100.jpg

カラー画像.py
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/

2
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2