画像にフィルムカメラで撮ったようなノイズを付与するスクリプト（Python）

# はじめに
おはこんばんにちは。普段写真を編集することを趣味としているのですが、その過程でどうしても自分で画僧にフィルムカメラで撮ったようなノイズを付与するスクリプトを書いてみたくなったため書きました。
しかし筆者はプログラミング初心者であるため、あまりコーディングに自信がなく、こうして公開することでブラッシュアップをしていただけたらな、同志を募ってモチベーションを高めていきたいなと思いまして、この記事を執筆するに至りました。ちなみにMarkdown記法ですらかなり苦戦しています。スペース打たないとでっかくならないんですね。

# 仕様
## インポートするライブラリ
```
import os
import tkinter
from tkinter import filedialog
import cv2
import numpy as np
```
## 共有変数
```
file_types = [("JPEG files", "*.jpg;*.jpeg"), ("All files", "*")]
initial_dir = os.path.abspath(os.path.dirname(__file__))
noise_strength = 120
filter_strength = 20
alpha = 0.05
```
## スクリプト
```
def load_image():
    try:
        root = tkinter.Tk()
        root.withdraw()

        # 画像のパスを取得
        file_path = filedialog.askopenfilename(filetypes=file_types, initialdir=initial_dir)
        original_image_bgr = cv2.imread(file_path)

        return original_image_bgr

    except Exception as e:
        print(f"An error occurred: {e}")
        return None

def convert_to_vector(original_image):
    return np.asarray(original_image)

def extract_channels(image_vector):
    r_channel = image_vector[:, :, 2]
    g_channel = image_vector[:, :, 1]
    b_channel = image_vector[:, :, 0]
    return r_channel, g_channel, b_channel

def generate_noise(original_image):
    # original_image の範囲でノイズを生成
    noise = np.random.randint(-noise_strength, noise_strength + 1, original_image.shape[:2])
    print(f"Original Noise Shape: {noise.shape}")
    # original_image と同じサイズにリサイズ
    noise = cv2.resize(noise, (original_image.shape[1], original_image.shape[0]))
    print(f"Resized Noise Shape: {noise.shape}")
    # ノイズの形状を (height, width, 1) から (height, width, 3) に変更
    noise = np.stack([noise] * 3, axis=-1)
    print(f"Final Noise Shape: {noise.shape}")
    return noise



def convert_to_monochrome(noise):
    # ノイズをモノクロに変換
    monochrome_noise = np.mean(noise, axis=2, keepdims=True)
    print(noise.shape)#OK
    return monochrome_noise

def apply_blur(noise):
    # デバッグ用にノイズの形状を印字
    print(f"Noise before blur: {noise.shape}")

    # 平均値フィルタをかける
    return cv2.blur(noise, (filter_strength, filter_strength))



def composite_images(original_image, noise):
    # 合成する
    print(f"Original Image Shape: {original_image.shape}")
    print(f"Noise Shape: {noise.shape}")

    # ノイズを (height, width, 3) の形に変形
    noise_expanded = np.repeat(noise[:, :, np.newaxis], 3, axis=-1)

    # noise_expanded を original_image と同じ型に変換
    noise_expanded = noise_expanded.astype(original_image.dtype)
    
    composite_image = cv2.addWeighted(original_image, 1 - alpha, noise_expanded, alpha, 0)
    composite_image = np.clip(composite_image, 0, 255).astype(np.uint8)
    return composite_image


def save_image(image, filename):
    cv2.imwrite(filename, image)

def show_image(image, window_name='Image'):
    cv2.imshow(window_name, image)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

if __name__ == "__main__":
    original_image = load_image()
    
    if original_image is not None:
        image_vector = convert_to_vector(original_image)
        r_channel, g_channel, b_channel = extract_channels(image_vector)
        # Gチャンネルの輝度に基づいてノイズを生成
        noise = generate_noise(original_image)
        # ノイズをモノクロに変換
        monochrome_noise = convert_to_monochrome(noise)
        # 平均値フィルタをかける
        blurred_noise = apply_blur(monochrome_noise)
        # ノイズと元の画像を合成
        composite_image = composite_images(original_image, blurred_noise)

        cv2.imwrite('composite_image.jpg', composite_image)
        show_image(composite_image, 'Composite Image')
```

# 個人的によりこだわりたいところ
・おそらくノイズ生成、合成の次元をそろえる部分がかなり迷宮入りしているため、きれいにしたい。
・取得した画像から輝度を取得し、それを10段階程度に分割してそれそれの段階に応じて個別に任意のノイズ強度を設定できるようにしたい。またそれを行った際に起こるノイズの境界部分の急激なノイズの変化についてはいい感じになだらかにしたい。

以上であります。コメント、ご連絡お待ちしております。