
画像加工について色々と調査していたら、いろいろと新しい手法がでていて、昔の失敗写真などをいい感じに自動補正できそうな事が判明したので、少し試してみました。
100日後に今時の画像処理の達人になる婆婆
当初、予定としては、全てモデルを使う予定でしたが、SwinIRのUpscaling、Denoiseの活用のみにして、明るさの補正はアルゴリズムベースのメソッドに変更しました。
20年くらい前では限界だった部分、暗いところを立ち上げて明るくした際に、ノイズが乗るので縮小するしかありませんでしたが、最近のDeepLarningベースのモデルを利用すると、画像サイズを妥協せずにノイズを消せるという事が判明したのです。
また、従来のアルゴリズムベースの補正も考えた方が進化しており、簡単に実装できる事が判明したので、両方とも組み合わせたプロセスを検証しました。
DLモデルは画像を入れるだけで、出力変えられるので扱いません。
今回の記事では、古典のトーンカーブ補正とラプラシアンピラミッド合成を扱います。
1980年代の技術ですが、まだまだ現役、下手なDLモデルよりも期待通りの出力を得られます。
トーンカーブ補正
スプライン曲線を使った輝度補正
- Photoshopなどの画像加工ツールに付属している、トーンカーブ補正で利用される機能をpython上で再現する
- 階調の変化を滑らかに自然な輝度のグラデーションを作る事が目的
curve = PchipInterpolator(x, y)
階調補正には、PchipInterpolatorを利用して、白飛びを防ぎつつ、HDR合成用の明るい画像と暗い画像を作り出します。
後ほどのサンプルコードで、img1_bgr、img2_bgrとして登場します。
lutは、スプライン補完で作り出したトーンカーブです。
チャンネル事にcv.LUT(src, lut[, dst])を使って適用します。
# B, G, Rに分解
blue_channel, green_channel, red_channel = cv2.split(img_bgr)
# 各チャンネルにLUTを適用
blue_adjusted = cv2.LUT(blue_channel, lut)
green_adjusted = cv2.LUT(green_channel, lut)
red_adjusted = cv2.LUT(red_channel, lut)
# 結合して出力
cv2.merge([blue_adjusted, green_adjusted, red_adjusted])
ラプラシアンピラミッド合成
人間の目は明るいところも、暗いところも適切に補正した上で認識していますが、
カメラの場合には、認識できる明るさのレンジが限定されているので、
明るいところも、暗いところも、を適正露出に補正した合成が必要です。
明るいところがよく写っている画像と、暗いところがよく写っている画像を2枚貼り合わせると、ちょうどいい画像ができあがります。
2枚の画像から適正なピクセルを選び取るだけのロジックをハードマスクブレンドといいますが、こちらの方法では、境界線がギザギザしてしまう問題があります。
その問題を解消するために、境界線の抽出を活用してぼやかすラプラシアンピラミッド合成があります。
オリジナルから縮小画像を生成して元に戻しオリジナルと比較する事で、欠損したピクセルの差分が取れる、縮小の段階が階段上になっているのでピラミッドという

合成画像 = ブレンド画像1 + ブレンド画像2 + ブレンド画像3

サンプルコード
1. ガウジアンピラミッド作成
マスク画像、明るい画像、暗い画像、それぞれに、ガウシアンピラミッドを作成する
def build_gaussian_pyramid(img, levels):
g_pyramid = [img]
for _ in range(levels):
# ダウンサンプリング
img = cv2.pyrDown(img)
g_pyramid.append(img)
return g_pyramid
2. ラプラシアンピラミッド作成
ガウシアンピラミッド画像を受け取り、ラプラシアンピラミッドを作成する
def build_laplacian_pyramid(g_pyramid):
l_pyramid = []
for i in range(len(g_pyramid) - 1):
# 拡大してサイズを揃える
up = cv2.pyrUp(
g_pyramid[i + 1],
dstsize=(g_pyramid[i].shape[1], g_pyramid[i].shape[0])
)
# 差分を取る
lap = cv2.subtract(g_pyramid[i], up)
l_pyramid.append(lap)
l_pyramid.append(g_pyramid[-1])
return l_pyramid
3. ラプラシアンピラミッド画像をブレンド
サイズごとに画像1と画像2のラプラシアン画像をマスクの荷重を用いて加重和(加重平均)で合成する
return blended_list
def blend_pyramids(lap_list1, lap_list2, gmask_list):
blended_list = []
for lap1, lap2, gm in zip(lap_list1, lap_list2, gmask_list):
# グレースケールなのでカラー用の3chに拡張
gm_3ch = np.stack([gm, gm, gm], axis=2)
blended_level = lap1 * gm_3ch + lap2 * (1 - gm_3ch)
blended_list.append(blended_level)
4. 全ての画像をブレンド
def reconstruct_from_pyramid(pyramid):
img = pyramid[-1]
for i in range(len(pyramid) - 2, -1, -1):
# アップサンプリング
img = cv2.pyrUp(img, dstsize=(pyramid[i].shape[1], pyramid[i].shape[0]))
img = cv2.add(img, pyramid[i])
return img
Image filtering: pyramids: Laplacian pyramid
5. 一連の手続きを呼び出す
# float32 & 正規化
img1 = img1_bgr.astype(np.float32) / 255.0
img2 = img2_bgr.astype(np.float32) / 255.0
# ガウジアンピラミッド
gp_mask = build_gaussian_pyramid(mask, levels)
gp_img1 = build_gaussian_pyramid(img1, levels)
gp_img2 = build_gaussian_pyramid(img2, levels)
# ラプラシアンピラミッド
lp_img1 = build_laplacian_pyramid(gp_img1)
lp_img2 = build_laplacian_pyramid(gp_img2)
# ブレンドする
blended_pyramid = blend_pyramids(lp_img1, lp_img2, gp_mask)
blended = reconstruct_from_pyramid(blended_pyramid)
# 正規化戻し & BGRのまま出力
blended_bgr = np.clip(blended * 255, 0, 255).astype(np.uint8)




