解像度低下・ぼけ、ブロックひずみ、偽輪郭、ジャーキネス、フリッカ、動きぼけ、途切れ/フリーズ、モスキート雑音、エッジビジネス、乱れ(破綻)などの映像劣化が発生し、その原因が何であるかを把握することは、目的とする映像品質評価や品質改善を行うために必要です。
そのための映像評価手法に大きく二つの手法が存在します。
- 主観評価法(DSCQS法、DSIS法、ACR法、ACR-HR法など)
- 客観評価法(SNR、PSNR、SSIMなど)
主観評価とは、あらかじめ決められた手順にのっとって、映像に対するユーザ体験品質を、視覚心理実験によって定量化することです。
また、客観評価法とは、映像メディアに対する主観品質を映像の物理量を用いて推定する方法です。
ここでは各手法の特性および計算式は掲載しません。
:画素数、
:原画像および復号画像の
番目の画素位置における輝度値
なんて難しそうに書いても、単なる差分二乗平均だしね。
客観評価を行うサンプルコードを見つけたので、実画像に適用する方法を書き記しておきます。

なお、評価指標をまとめました(が、一部未記入です)。
Metrics | Meaning of video quality metrics | Using ref / none ref | 日本語 | 説明 |
---|---|---|---|---|
VIF | Visual Information Fidelity | Reference | 視覚情報忠実度 | Netflix VMAF (Video Multimethod Assessment Fusion)で採用されている |
MSE | Mean Square Error | Reference | 平均二乗誤差 | 画素の輝度値の差分を表す |
SSIM | Structural Similarity Metric | Reference | 構造的類似性指数 | 人間が感じる違いをより性格に指標化(歪みを表す指標) |
PSNR | Peak Signal to Noise Ratio | Reference | ピーク信号対雑音比 | 画質の信号が取りうる最大のパワーと劣化をもたらすノイズの比率を表す |
RECO | Relative Polar Edge Coherence | Reference | ||
MS-SSIM | MultiScale Structural Similarity Metric | Reference | ||
3SSIM | 3-Component Structural Similarity Metric | Reference | ||
VQUAD-HD | ||||
VQM | Video Quality Metrics | Reference | ||
PVVQM | Reference | |||
UIQ | Universal Image Quality | |||
MSSIM | Motion SSIM | |||
NIQE | Natural Image Quality Evaluator | None-Reference | NSSベースの特徴の間の距離を測定 | |
BRISQUE | Bind/Referenceless Image Spatial Quality Evaluator | None-Reference |
Full Reference (FR:参照画像指標)を動かしてみる
テスト対象ストリームと参照用ストリームの画素と画素を比較する完全対照テストです。
テスト対象デバイスとゴールデンデバイスのストリームを個別に計測するのではなく、2本のストリームを同時に解析して両者の画素と画素を比較します
かつてはPSNR(Peak Signal to Noise Ratio)が画像圧縮など非可逆圧縮を使ったコーデックの再現性の品質の尺度と使用されることが多かった。
最近は、例えばNetflixはVMAF(Video Multi-Method Assessment Fusion)でVIF(Visual Information Fidelity)などを用いています。
エンコードの変化によって画質が劣化していないかどうか判断するための測定基準としては、人間が実際に観て判断する方法もあるが、全ての映像を確認するのは難しい。そこで、機械学習を用いて自動化する取り組みも進行している。
オープンソースのVMAF(Video Multimethod Assessment Fusion)という測定も活用している。このような検証を重ね、従来だと750kbpsの画質が、270kbpsで実現。データ量4GBで26時間の映像を観られるという。
パス設定を行う
これは一般的な pythonでのimportの説明です。
1 2 3 4 |
. ├── psnr.py └── demo/ └── main.py # <= 呼び出し側 |
jpg_demo.py
1 2 3 4 5 6 |
import sys sys.path.append('..') import psnr psnr_values.append( psnr.psnr(ref, dist) ) |
scikit-imageをインストールする
次のようなエラーが出力されました。
1 2 3 |
/c/Python38/Scripts/pip install skimage ... *** Please install the `scikit-image` package (instead of `skimage`) *** |
「skimage」の代わりに「scikit-image」を使う必要があるようです。
インストール方法は次のとおりです。
1 |
/c/Python38/Scripts/pip install scikit-image |
imageio.imreadを利用する
またエラーが出ました。
1 2 3 |
AttributeError: module 'scipy.misc' has no attribute 'imread imread is deprecated! imread is deprecated in SciPy 1.0.0, and will be removed in 1.2.0. Use imageio.imread instead. |
バージョン1.2.0以降では「imread」は削除されたようです。
代わりに「imageio.imread」を使う必要があります。
ネットで調べると、ソースコードを書き換えるのは手間なので1.2.0以前の「scipy」を入れている人が多かったです。
インストール方法は次のとおりです。
1 2 |
pip uninstall scipy pip install scipy==1.1.0 |
実行すると、大量のエラーが出ました・・・。
1 2 3 4 5 |
command: 'c:\python38\python.exe' -u -c 'import sys, setuptools, tokenize; sys.argv[0] = '"'"'C:\\Users\\0000123921\\AppData\\Local\\Temp\\gnupack\\pip-install-cs65gpl9\\scipy\\setup.py'"'"'; __file__='"'"'C:\\Users\\0000123921\\AppData\\Local\\Temp\\gnupack\\pip-install-cs65gpl9\\scipy\\setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' install --record 'C:\Users\0000123921\AppData\Local\Temp\gnupack\pip-record-zb1u2ijt\install-record.txt' --single-version-externally-managed --compile --install-headers 'c:\python38\Include\scipy' |
駄目だこりゃ・・・・。
Scipy.misc.imreadで使われていた、mode, flatten,はそれぞれ、pilmode, as_grayに変更されています。
次のように書き換えました。
1 2 3 4 5 6 7 |
+import imageio -ref = scipy.misc.imread(ref_file, flatten=True).astype(numpy.float32) +ref = imageio.imread(ref_file, as_gray=True).astype(numpy.float32) -dist = scipy.misc.imread(dist_file, flatten=True).astype(numpy.float32) +dist = imageio.imread(dist_file, as_gray=True).astype(numpy.float32) |
gm convertをインストール
次に出力されたエラーは次のとおりです。
1 2 |
'gm' は、内部コマンドまたは外部コマンド、 操作可能なプログラムまたはバッチ ファイルとして認識されていません。 |
「GraphicsMagick」のインストールが必要です。
「GraphicsMagick-1.3.1.tar.gz」をTar.gzで make したらエラーが出たので、Windows版を持ってきました。
このため、絶対パスで書くと次のように書き換える必要があります。
1 2 |
- subprocess.check_call('gm convert %s -quality %d %s'%(ref_file, quality, dist_file), shell=True) + subprocess.check_call('"C:\Program Files\GraphicsMagick-1.3.35-Q8\gm.exe" convert %s -quality %d %s'%(ref_file, quality, dist_file), shell=True) |
結果
結果は次のようになります。
数値が小さい場合は劣化したと判断しています。
サンプルでは「LENA画像」の画質を落としつつ、VIFP、SSIM、RECO、PSNRの手法を試していました。
VIFP、SSIMは、画質が落ちきるまでは値が下がりにくい傾向があります。
No Reference (NR:非参照画像指標)を動かしてみる
対象のビデオストリームが未知だったり予測できなかったりする場合に使う非対照テストです。
有名なものに、NIQE(Naturalness Image Quality Evaluator)があります。
NIQEとは、モデルを学習させるために使用されるイメージデータベースから取得された特徴と測定したいイメージから計算されたNSS(Natural Scene Statistic)ベースの特徴の間の距離を測定する手法です。
この手法のメリットは、コンテンツに関わらず、どのようなストリームにも計測を適用できることです。
一方で、計測結果が解析対象のコンテンツによって大きくばらつく可能性があります。
Use a.any() or a.all()
サンプルコードはNIQEがコメントアウトされていました。
コメントを外して実行すると次のようなエラーが出ました。
1 |
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all() |
これは次のように修正します。
1 2 3 |
- if features == None: + if features is None: features = numpy.vstack(scale_features) |
結果
結果は次のようになります。
NIQEでは、劣化するほど値が大きくなるようです。
動画を静止画に切り分ける
実データを利用するためには、動画を静止画に分ける必要があります。
あるx秒からy秒までの画像を切り出す方法は次の通りです。
opencvをインストールする
Web から「opencv_python-4.4.0-cp38-cp38-win_amd64.whl」をダウンロードします。
インストールコマンドは次のようになります。
1 |
/c/Python38/Scripts/pip install opencv_python-4.4.0-cp38-cp38-win_amd64.whl |
ソースコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
import cv2 import os import matplotlib.pyplot as plt def save_frame_range_sec(video_path, start_sec, stop_sec, step_sec, dir_path, basename, ext='jpg'): cap = cv2.VideoCapture(video_path) if not cap.isOpened(): return os.makedirs(dir_path, exist_ok=True) base_path = os.path.join(dir_path, basename) digit = len(str(int(cap.get(cv2.CAP_PROP_FRAME_COUNT)))) fps = cap.get(cv2.CAP_PROP_FPS) # FPS = frame / time[sec]なので、time[sec] = frame / FPSとなる。 # フレーム番号は整数である必要があるためround()で丸める。指定した秒数との間には誤差が発生するので注意。 fps_inv = 1 / fps sec = start_sec while sec < stop_sec: n = round(fps * sec) cap.set(cv2.CAP_PROP_POS_FRAMES, n) ret, frame = cap.read() if ret: cv2.imwrite( '{}_{}_{:.2f}.{}'.format( base_path, str(n).zfill(digit), n * fps_inv, ext ), frame ) else: return sec += step_sec # 動画の16秒~20秒間を0.05秒感覚で抽出する save_frame_range_sec('200715-1318.mp4', 16, 20, 0.05, 'result_range_sec', 'sample_video_img') |
python save_frame_range_sec.py
まとめ
「伝送劣化」や「符号化劣化」に対する映像メディアの品質指標を測定するには十分有効のように感じました。
- (a)オリジナル画像
- (b)平均コントラストを改善
- (c)明るさを調整
- (d)ガウスノイズを付加
- (e)インパルスノイズを付加
- (f)JPEGで圧縮
- (g)ブラー
- (h)空間スケーリング(ズームアウト)
- (i)空間シフト(右へ移動)
- (j)空間シフト(左へ移動)
- (k)回転(反時計周り)
- (l)回転(時計回り)
ですが、・・・・本当は、こんな事が知りたかったんじゃないんだけどな~。
最近は「The Unreasonable Effectiveness of Deep Features as a Perceptual Metric」などもあります。