【Python・OpenCV】超解像で画像を鮮明に!(dnn_superres)

※当サイトではアフィリエイト広告を利用しています

Python プログラミング 画像処理

【Python・OpenCV】超解像で画像を鮮明に!(dnn_superres)

はじめに

超解像(Super Resolution)は、低解像度の画像から高解像度の画像を生成する技術です。
OpenCVのdnn_superresモジュールを使用すると、深層学習ベースの超解像を簡単に実装できます。
この記事では、dnn_superresモジュールの基本的な使い方を中心に解説します。

(広告) OpenCV関連書籍をAmazonで探す

dnn_superresについて

dnn_superresは、OpenCV contribの一つで、深層学習を用いた画像の超解像を実現するためのモジュールです。
超解像とは、低解像度の画像から高解像度の画像を生成する技術を指します。

概要

従来の画像拡大との違い

従来の画像拡大手法(バイキュービック補間など)は、単純に画素を補完するため、画像がぼやけるなどの問題がありました。
一方、dnn_superresは、大量の画像データで学習されたDNNモデルを用いることで、より自然で高品質な画像を生成することができます。

深層学習ベース

  • すべてのモデルが深層学習(Deep Neural Network)を基盤としています
  • 従来の補間方式と比べて、より自然で詳細な画像生成が可能です
  • 事前学習済みのモデルを利用するため、独自の学習は不要です

複数の超解像モデルをサポート

dnn_superresでは、以下のモデルが利用可能です

  • ESPCN (Efficient Sub-Pixel Convolutional Neural Network)
  • EDSR (Enhanced Deep Super-Resolution)
  • FSRCNN (Fast Super-Resolution Convolutional Neural Network)
  • LapSRN (Laplacian Pyramid Super-Resolution Network)

超解像モデルの比較

dnn_superresで利用できる超解像モデルには、それぞれ特徴と得意な領域が異なります。
以下に、各モデルの長所、短所、および適した利用シーンについて詳しく解説します。

EDSR (Enhanced Deep Super-Resolution)

  • 長所
    • 非常に高品質な超解像画像を生成
    • エッジの再現性が非常に高い
    • 様々なスケールファクターに対応でき、様々な画像に適用可能
  • 短所
    • モデルが複雑で、サイズも大きなため、推論に比較的遅い
    • 大量のメモリを消費し、高解像度の画像処理には高性能なGPUが必要になる場合がある
  • 適した利用シーン
    • ポスターや印刷物など、高品質な画像が必要な場合
    • 異なる倍率での超解像が必要な場合

ESPCN (Efficient Sub-Pixel Convolutional Neural Networks)

  • 長所
    • モデルがシンプルで軽量であるため、リアルタイム処理に適する
    • 推論速度が速く、モバイルデバイスなどでも利用可能
    • メモリ使用量が少ない
  • 短所
    • 他のモデルに比べて、超解像性能が若干劣る
    • 複雑なテクスチャの再現性、エッジの鮮明さがやや劣る
  • 適した利用シーン
    • 動画の超解像処理など、リアルタイム性が求められる場合
    • メモリやストレージが限られたモバイルデバイスでの利用
    • ウェブアプリケーションでの使用

FSRCNN (Fast Super-Resolution Convolutional Neural Networks)

  • 長所
    • 軽量で高速
    • 高品質な超解像画像を生成
    • メモリ効率が良好
  • 短所
    • 極端に複雑なパターンの再現性がやや劣る
    • 高倍率での拡大時に細部の質がやや低下
    • エッジの処理がEDSRほど精密でない
  • 適した利用シーン
    • リアルタイム処理でありながら、高品質な画像を求める場合
    • 監視カメラ映像の拡大処理

LapSRN (Laplacian Pyramid Super-Resolution Network)

  • 長所
    • 高倍率(8倍など)での拡大に強い
    • 自然な見た目の出力画像
  • 短所
    • メモリ使用量が多く、推論に時間がかかる場合あり
    • 低倍率での処理が他のモデルより非効率
    • 実装が比較的複雑
  • 適した利用シーン
    • 高倍率の拡大が必要な場合
    • 画像の細部まで復元したい場合
    • 古い低解像度画像のリストア

環境設定

OpenCVのdnn_superresモジュールを実行するためには、OpenCV contribeのインストールが必要となります。

OpenCV contrib(contribution)とは

OpenCV contribは、OpenCVのメインライブラリには含まれていない、実験的な機能やコミュニティが開発した追加機能のコレクションです。

実験的な機能
まだ安定版としてリリースされていない、新しいアルゴリズムや機能が実装されています。これにより、最新の画像処理技術をいち早く試すことができます。

コミュニティ開発
OpenCVコミュニティの開発者たちが、自身の研究やプロジェクトで開発したモジュールを共有する場となっています。そのため、多種多様なモジュールが提供されており、特定の用途に特化した処理を行うことができます。

拡張性
OpenCVの機能を拡張し、より複雑な画像処理タスクに対応することができます。例えば、3Dビジョン、特徴点検出、物体追跡、超解像など、高度な機能が提供されています。

主な機能
以下に代表的な機能の一部を挙げます。

  • text: テキスト検出・認識機能
  • xfeatures2d: SIFT、SURFなどの特徴量検出器
  • bioinspired: 生物学にインスパイアされたビジョンアルゴリズム
  • tracking: オブジェクト追跡アルゴリズム
  • ximgproc: 高度な画像処理フィルタ

詳細は公式のOpenCV modulesのExtra modulesの項目に列挙されています。

contribのインストール方法

下記を実行してOpenCV contribをインストールをしてください。

pip install opencv-contrib-python

これで準備が整いました。

使い方

dnn_superresで利用できる4つの超解像モデルを使用するサンプル コードは以下になります。
このサンプルコードでは、5種類のモデル ファイルの超解像処理を実行します。

処理の流れは、次の通りです。

  1. DnnSuperResImpl_create()で超解像モデルを初期化します。
  2. 入力画像を読み込みます。
  3. モデル ファイルのパスを設定します。
  4. readModel()でモデルファイルを読み込みます。
  5. setModel()でモデルの種類とスケール倍率を設定します。
  6. upsample()メソッドで超解像を実行します。
  7. 4〜6を各モデル ファイルごとに処理します。
  8. 結果を表示します。結果はモデル ファイルごとに個別のウィンドウに表示します。ウィンドウを閉じると、次の結果が表示されます。

入力画像は下記を使用しました。
超解像の効果をわかりやすくするため、一部を切り取って使用します。

モデル ファイルのダウンロード先は下記リンクから入手しました。

Super Resolution using Convolutional Neural Networks

結果は以下の通りとなります。

超解像の結果の画像は、いずれも高品質です。
中でも、EDSRが最も高品質です。

import cv2
from cv2 import dnn_superres
import matplotlib.pyplot as plt
import numpy as np

# dnn_superresオブジェクトを作成
sr = dnn_superres.DnnSuperResImpl_create()

# 画像の読み込み
image = cv2.imread("image.jpg")

# モデル ファイルのパスを設定
path_edsr_x4 = "EDSR_x4.pb"
path_espcn_x4 = "ESPCN_x4.pb"
path_fsrcnn_x4 = "FSRCNN_x4.pb"
path_fsrcnn_small_x4 = "FSRCNN-small_x4.pb"
path_lapsrn_x8 = "LapSRN_x8.pb"

# ----- EDSR x4 -----
# モデルの読み込みと種類、スケールを設定
sr.readModel(path_edsr_x4)
sr.setModel("edsr", 4)

# 超解像の実行
result_edsr_x4 = sr.upsample(image)
# ----- EDSR x4 -----

# ----- ESPCN x4 -----
# モデルの読み込みと種類、スケールを設定
sr.readModel(path_espcn_x4)
sr.setModel("espcn", 4)

# 超解像の実行
result_espcn_x4 = sr.upsample(image)
# ----- ESPCN x4 -----

# ----- FSRCNN x4 -----
# モデルの読み込みと種類、スケールを設定
sr.readModel(path_fsrcnn_x4)
sr.setModel("fsrcnn", 4)

# 超解像の実行
result_fsrcnn_x4 = sr.upsample(image)
# ----- FSRCNN x4 -----

# ----- FSRCNN Small x4 -----
# モデルの読み込みと種類、スケールを設定
sr.readModel(path_fsrcnn_small_x4)
sr.setModel("fsrcnn", 4)

# 超解像の実行
result_fsrcnn_x4_small = sr.upsample(image)
# ----- FSRCNN Small x4 -----

# ----- LapSRN x8 -----
# モデルの読み込みと種類、スケールを設定
sr.readModel(path_lapsrn_x8)
sr.setModel("lapsrn", 8)

# 超解像の実行
result_lapsrn_x8 = sr.upsample(image)
# ----- LapSRN x8 -----

# BGRのチャンネル並びをRGBの並びに変更(matplotlibで結果を表示するため)
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) 
result_edsr_x4 = cv2.cvtColor(result_edsr_x4, cv2.COLOR_BGR2RGB) 
result_espcn_x4 = cv2.cvtColor(result_espcn_x4, cv2.COLOR_BGR2RGB) 
result_fsrcnn_x4 = cv2.cvtColor(result_fsrcnn_x4, cv2.COLOR_BGR2RGB) 
result_fsrcnn_x4_small = cv2.cvtColor(result_fsrcnn_x4_small, cv2.COLOR_BGR2RGB) 
result_lapsrn_x8 = cv2.cvtColor(result_lapsrn_x8, cv2.COLOR_BGR2RGB) 

# 結果を表示
plt.rcParams["figure.figsize"] = [14,6]                                 # ウィンドウサイズを設定
title = "dnn_superres(EDSRx4): codevace.com"
plt.figure(title)                                                       # ウィンドウタイトルを設定
plt.subplots_adjust(left=0.05, right=0.95, bottom=0.03, top=0.95)       # 余白を設定
plt.subplot(121)                                                        # 1行2列の1番目の領域にプロットを設定
plt.imshow(image)                                                       # 入力画像をグレースケールで表示
plt.title('Original Image', fontsize=16)                                # 画像タイトル設定
plt.axis("off")                                                         # 軸目盛、軸ラベルを消す
plt.subplot(122)                                                        # 1行2列の2番目の領域にプロットを設定
plt.imshow(result_edsr_x4)                                              # EDSR x4の結果
plt.title('EDSR x4', fontsize=16)                                       # 画像タイトル設定
plt.axis("off")                                                         # 軸目盛、軸ラベルを消す
plt.show()

title = "dnn_superres(ESPCN x4): codevace.com"
plt.figure(title)                                                       # ウィンドウタイトルを設定
plt.subplots_adjust(left=0.05, right=0.95, bottom=0.03, top=0.95)       # 余白を設定
plt.subplot(121)                                                        # 1行2列の1番目の領域にプロットを設定
plt.imshow(image)                                                       # 入力画像をグレースケールで表示
plt.title('Original Image', fontsize=16)                                # 画像タイトル設定
plt.axis("off")                                                         # 軸目盛、軸ラベルを消す
plt.subplot(122)                                                        # 1行2列の2番目の領域にプロットを設定
plt.imshow(result_espcn_x4)                                             # ESPCN x4の結果
plt.title('ESPCN x4', fontsize=16)                                      # 画像タイトル設定
plt.axis("off")                                                         # 軸目盛、軸ラベルを消す
plt.show()

title = "dnn_superres(FSRCNN x4): codevace.com"
plt.figure(title)                                                       # ウィンドウタイトルを設定
plt.subplots_adjust(left=0.05, right=0.95, bottom=0.03, top=0.95)       # 余白を設定
plt.subplot(121)                                                        # 1行2列の1番目の領域にプロットを設定
plt.imshow(image)                                                       # 入力画像をグレースケールで表示
plt.title('Original Image', fontsize=16)                                # 画像タイトル設定
plt.axis("off")                                                         # 軸目盛、軸ラベルを消す
plt.subplot(122)                                                        # 1行2列の2番目の領域にプロットを設定
plt.imshow(result_fsrcnn_x4)                                            # FSRCNN x4の結果
plt.title('FSRCNN x4', fontsize=16)                                     # 画像タイトル設定
plt.axis("off")                                                         # 軸目盛、軸ラベルを消す
plt.show()

title = "dnn_superres(FSRCNN Small x4): codevace.com"
plt.figure(title)                                                       # ウィンドウタイトルを設定
plt.subplots_adjust(left=0.05, right=0.95, bottom=0.03, top=0.95)       # 余白を設定
plt.subplot(121)                                                        # 1行2列の1番目の領域にプロットを設定
plt.imshow(image)                                                       # 入力画像をグレースケールで表示
plt.title('Original Image', fontsize=16)                                # 画像タイトル設定
plt.axis("off")                                                         # 軸目盛、軸ラベルを消す
plt.subplot(122)                                                        # 1行2列の2番目の領域にプロットを設定
plt.imshow(result_fsrcnn_x4_small)                                      # FSRCNN small x4の結果
plt.title('FSRCNN Small x4', fontsize=16)                               # 画像タイトル設定
plt.axis("off")                                                         # 軸目盛、軸ラベルを消す
plt.show()

title = "dnn_superres(LapSRN x8): codevace.com"
plt.figure(title)                                                       # ウィンドウタイトルを設定
plt.subplots_adjust(left=0.05, right=0.95, bottom=0.03, top=0.95)       # 余白を設定
plt.subplot(121)                                                        # 1行2列の1番目の領域にプロットを設定
plt.imshow(image)                                                       # 入力画像をグレースケールで表示
plt.title('Original Image', fontsize=16)                                # 画像タイトル設定
plt.axis("off")                                                         # 軸目盛、軸ラベルを消す
plt.subplot(122)                                                        # 1行2列の2番目の領域にプロットを設定
plt.imshow(result_lapsrn_x8)                                            # LapSRN x8の結果
plt.title('LapSRN x8', fontsize=16)                                     # 画像タイトル設定
plt.axis("off")                                                         # 軸目盛、軸ラベルを消す
plt.show()

次のサンプルコードでは、処理時間を測定するコードです。

入手画像は、上記サンプルと同じ画像ですが、一部を切り取ったものではなく、640 × 473のサイズのものを使用しまし次の結果となりました。複数回実行しましたが、いづれも同じような結果でした。
実行環境は以下のとおりです。
・Python ver3.13.0
・macOS 15.0.1
・Macbook Air M3

EDSR x4 Elapsed time: 30.408288708 seconds
ESPCN x4 Elapsed time: 0.133784958 seconds
FSRCNN x4 Elapsed time: 0.126822125 seconds
FSRCNN Small x4 Elapsed time: 0.055540208 seconds
LapSRN x8 Elapsed time: 13.478205791 seconds

高品質なESDRが断トツで最も遅く、FSRCNN Small x4はESDRと比較して500倍以上速い結果でした。
FSRCNN Small はFSRCNNの小規模モデルで、処理速度と画質のバランスを追求したものとなっています。
ESPCNとFSRCNNは同じような処理時間で、十分短時間といえる結果です。
LapSRNはスケールが8倍と他に比べて解像度が高いですが、EDSRの半分以下の処理時間で完了しています。

モデルの選択はそれぞれのモデルの特徴を十分検討して決定する必要があります。
本記事では評価していませんが、メモリの使用量を含めて検討するとよいと思います。

また、処理時間の計測は、この記事で解説しています。

import cv2
from cv2 import dnn_superres
import matplotlib.pyplot as plt

# dnn_superresオブジェクトを作成
sr = dnn_superres.DnnSuperResImpl_create()

# 画像の読み込み
image = cv2.imread("large_source_image.jpg")

# TickMeterオブジェクトを作成
tm = cv2.TickMeter()

# ----- EDSR x4 -----
# 時間計測開始
tm.start()

# モデルの読み込みと種類、スケールを設定
sr.readModel(path_edsr_x4)
sr.setModel("edsr", 4)

# 超解像の実行
result_edsr_x4 = sr.upsample(image)

# 時間計測終了
tm.stop()
elapsed_time = tm.getTimeSec()
print("EDSR x4 Elapsed time:", elapsed_time, "seconds")
# ----- EDSR x4 -----
tm.reset()

# ----- ESPCN x4 -----
# 時間計測開始
tm.start()

# モデルの読み込みと種類、スケールを設定
sr.readModel(path_espcn_x4)
sr.setModel("espcn", 4)

# 超解像の実行
result_espcn_x4 = sr.upsample(image)

# 時間計測終了
tm.stop()
elapsed_time = tm.getTimeSec()
print("ESPCN x4 Elapsed time:", elapsed_time, "seconds")
# ----- ESPCN x4 -----
tm.reset()

# ----- FSRCNN x4 -----
# 時間計測開始
tm.start()

# モデルの読み込みと種類、スケールを設定
sr.readModel(path_fsrcnn_x4)
sr.setModel("fsrcnn", 4)

# 超解像の実行
result_fsrcnn_x4 = sr.upsample(image)

# 時間計測終了
tm.stop()
elapsed_time = tm.getTimeSec()
print("FSRCNN x4 Elapsed time:", elapsed_time, "seconds")
# ----- FSRCNN x4 -----
tm.reset()

# ----- FSRCNN Small x4 -----
# 時間計測開始
tm.start()

# モデルの読み込みと種類、スケールを設定
sr.readModel(path_fsrcnn_small_x4)
sr.setModel("fsrcnn", 4)

# 超解像の実行
result_fsrcnn_x4_small = sr.upsample(image)

# 時間計測終了
tm.stop()
elapsed_time = tm.getTimeSec()
print("FSRCNN Small x4 Elapsed time:", elapsed_time, "seconds")
# ----- FSRCNN Small x4 -----
tm.reset()

# ----- LapSRN x8 -----
# 時間計測開始
tm.start()

# モデルの読み込みと種類、スケールを設定
sr.readModel(path_lapsrn_x8)
sr.setModel("lapsrn", 8)

# 超解像の実行
result_lapsrn_x8 = sr.upsample(image)

# 時間計測終了
tm.stop()
elapsed_time = tm.getTimeSec()
print("LapSRN x8 Elapsed time:", elapsed_time, "seconds")
# ----- LapSRN x8 -----

おわりに

OpenCVのdnn_superresモジュールを使用することで、簡単に高品質な超解像を実現できます。
数行のコードを書くだけで簡単に利用できます。
また、深層学習を用いた高品質な画像超解像を実現しており、様々な場面で活用できそうです。

ご質問や取り上げて欲しい内容などがありましたら、コメントをお願いします。
最後までご覧いただきありがとうございました。

■(広告)OpenCVの参考書としてどうぞ!■

参考リンク

-Python, プログラミング, 画像処理
-