Darkside(https対応しました)

<< 前のページ | 次のページ >>

2022年7月19日(火) 21:05

不穏な警告

 numpy を cupy に書き直したが、不穏な警告が出る。

UserWarning: cuFFT plan cache is disabled on CUDA 11.1 due to a known bug, so performance may be degraded. The bug is fixed on CUDA 11.2+.
cache = get_plan_cache()

 をいをい、GPU 有効化した OpenCV は import 出来ずに苦しみまくり、ようやく少し古いバージョンの決め打ちで使えるようになったってのに!
 こともあろうか、CUDA 11.1 がバグでアウトだと!?

 最小二乗法の GPU 対応化がうまく行かず、その直前までの処理で実行時間を測ってみる。何と、正味の位相限定相関法だけで1.75秒も掛かっている。cupy を使うことで、numpy より遅くなっている。もちろん OpenCV 関数との差は広がるばかり。これでは、最小二乗法を GPU 対応できたとしても無意味。
 新しい CUDA で OpenCV をビルドし直したら、速くなるのだろうか?
 それ以前の問題として、新しい CUDA でビルドし直した OpenCV は動くのだろうか?

 ひたすら前途多難である。なぜ CUDA に phaseCorrelate 関数が無いんだよ!
 現状は、phaseCorrelate 関数が余りに遅いので GPU を有効にしようと頑張ったけど、意味ありませんでした・・・である。いや、ここは冷静に考えてみよう。余りに酷い目に遭いまくってるせいでキレちゃったが、さすがにおかしい。位相限定相関法の正味の処理は、それほど長いコードではない。

img_f1 = np.fft.fft2(img_g1)
img_f2 = np.fft.fft2(img_g2).conjugate()
img_f = img_f1 * img_f2
img_n = img_f / np.abs(img_f)
img_p = np.fft.ifft2(img_n).real
img_p = np.fft.fftshift(img_p)

 この部分を cupy に置換したら実行時間が1倍半を超えるなんてのは、幾ら何でもおかし過ぎる。かなり大きな行列であり、GPU への転送オーバーヘッドを考慮しても変だ。ここは CUDA11.1 のバグをまずは疑うべきだ。
 CUDA11.7 を使用するよう(そこだけ)パラメーターを変えて OpenCV をビルドし直す。エラー無くインストールできた。cupy も 11.7 対応を入れ直す。結果は・・・1.47秒に高速化された!
 駄目じゃん、まだ numpy より遅いじゃん。

written by higashino [Virtual Reality] [この記事のURL] [コメントを書く] [コメント(0)] [TB(0)]

この記事へのトラックバックPingURL

2022年7月18日(月) 21:29

サブピクセル推定

 位相限定相関法は非常に有用であることが判明しているものの、とにかく計算時間が長い。
 GPU を使えば大きな時間短縮効果が期待できるものの、GPU に対応したプログラムは容易に作れない。それが現状である。OpenCV だと GPU 非対応なら位相限定相関法は関数1つ呼ぶだけで完了するものの、GPU 対応版は無い。そこで、GPU 非対応 OpenCV にも関数が存在しなかった昔のプログラムから、サブピクセルを求めるための最小二乗法を流用させて貰う。scipy をインストールしなければならない。 

import numpy as np
import cv2
from scipy.optimize import leastsq
import time
# ステッチ中心
LX = 2114
LY = 2023
# 判定範囲 w や h の 1/2
LD2 = 1440
fitting_shape = (9, 9)
def pocfunc_model(alpha, delta1, delta2, r, u):
    N1, N2 = r.shape
    V1, V2 = list(map(lambda x: 2 * x + 1, u))
    return lambda n1, n2: alpha / (N1 * N2) * np.sin((n1 + delta1) * V1 / N1 * np.pi) * np.sin((n2 + delta2) * V2 / N2 * np.pi)\
                                            / (np.sin((n1 + delta1) * np.pi / N1) * np.sin((n2 + delta2) * np.pi / N2))
img1 = cv2.imread('103093.png')
img2 = cv2.imread('103094.png')
img_p1 = img1[int(LY-LD2):int(LY+LD2),int(LX-LD2):int(LX+LD2)]
img_p2 = img2[int(LY-LD2):int(LY+LD2),int(LX-LD2):int(LX+LD2)]
img_g1 = np.asarray(cv2.cvtColor(img_p1, cv2.COLOR_BGR2GRAY), 'float')
img_g2 = np.asarray(cv2.cvtColor(img_p2, cv2.COLOR_BGR2GRAY), 'float')
tt1 = time.perf_counter()
(x, y), e = cv2.phaseCorrelate(img_g1, img_g2)
tt2 = time.perf_counter()
print(tt2-tt1)
print(e, 'Y=' + str(y), 'x=' + str(x))
tt1 = time.perf_counter()
img_f1 = np.fft.fft2(img_g1)
img_f2 = np.fft.fft2(img_g2).conjugate()
img_f = img_f1 * img_f2
img_n = img_f / np.abs(img_f)
img_p = np.fft.ifft2(img_n).real
img_p = np.fft.fftshift(img_p)
m = np.floor(list(map(lambda x: x / 2.0, img_g1.shape)))
u = list(map(lambda x: x / 2.0, m))
max_pos = np.argmax(img_p)
peak = (int(max_pos / img_g1.shape[1]), max_pos % img_g1.shape[1])
max_peak = img_p[peak[0], peak[1]]
mf = np.floor(list(map(lambda x: x / 2.0, fitting_shape)))
fitting_area = img_p[int(peak[0] - mf[0]) : int(peak[0] + mf[0] + 1), int(peak[1] - mf[1]) : int(peak[1] + mf[1] + 1)]
cv2.imwrite('out.png', img_p, [int(cv2.IMWRITE_PNG_COMPRESSION), 1])
p0 = [0.5, -(peak[0] - m[0]) - 0.02, -(peak[1] - m[1]) - 0.02]
y, x = np.mgrid[-mf[0]:mf[0] + 1, -mf[1]:mf[1] + 1]
y = y + peak[0] - m[0]
x = x + peak[1] - m[1]
errorfunction = lambda p: np.ravel(pocfunc_model(p[0], p[1], p[2], img_p, u)(y, x) - fitting_area)
plsq = leastsq(errorfunction, p0)
tt2 = time.perf_counter()
print(tt2-tt1)
print(plsq[0][0], 'Y=' + str(plsq[0][1]), 'x=' + str(plsq[0][2]))

 まだ GPU は使用しておらず、OpenCV の関数一発と計算時間を比較。
 10年前の旧機だと、

新しいパソコンだと、

 いずれにしろ関数1つで済む OpenCV が3倍以上も速い。numpy を GPU 対応にできたとしても、この差を逆転できないようでは意味がない。ズレ量はほぼ同じだがサブピクセルの値は違う。これは、そもそもサブピクセルの推定をどうやっているのか?という問題があり今は保留である。
 クローズドループによる収束を試したとき、どちらが収束し易いか?が勝負となる。もちろんそれ以前に、速さを逆転できなければ全く意味は無くなる。

written by higashino [Virtual Reality] [この記事のURL] [コメントを書く] [コメント(0)] [TB(0)]

この記事へのトラックバックPingURL

2022年7月17日(日) 21:03

ディスクリートな位相限定相関法

 試験用画像として、拡大を考慮しないと収束しなかったフレームを使用。

 1フレーム差の2枚について、位相限定相関法を使ってみる。

 オリジナルは 4096×4096 で、中心付近の 2880×2880 を切り出して使う。

 かなりすったもんだの末に、何とか相関グラフ画像らしきものが得られた。見える絵にするため、値を1000倍している。

 左上隅を (0,0) とした場合に(3,0) にピークが来ていて、たぶん正しい。

 だがこの瞬間に、思い切り厄介な問題に気付く。
 まず、ズレ量をサブピクセルまで求めようとすれば、どのような計算が良いのか?
 次に、符号。ズレ量がマイナスの場合は、どのような画像になるのか?

 試しに2枚の画像を入れ替えたところ、右上隅にピークが出現した。なるほど、0〜127 が正で 128〜255 が負、みたいな感じか。

 しかし、サブピクセルの求め方は何とかしなければならない。

 プログラム自体は単純だが、このままでは実用にならない。

import numpy as np
import cv2
# ステッチ中心
LX = 2114
LY = 2023
# 判定範囲 w や h の 1/2
LD2 = 1440
img1 = cv2.imread('103093.png')
img2 = cv2.imread('103094.png')
img_p1 = img1[int(LY-LD2):int(LY+LD2),int(LX-LD2):int(LX+LD2)]
img_p2 = img2[int(LY-LD2):int(LY+LD2),int(LX-LD2):int(LX+LD2)]
img_g1 = np.asarray(cv2.cvtColor(img_p1, cv2.COLOR_BGR2GRAY), 'float')
img_g2 = np.asarray(cv2.cvtColor(img_p2, cv2.COLOR_BGR2GRAY), 'float')
img_f1 = np.fft.fft2(img_g1)
img_f2 = np.fft.fft2(img_g2).conjugate()
img_f = img_f1 * img_f2
img_n = img_f / np.abs(img_f)
img_p = np.fft.ifft2(img_n).real
print(img_p)
img_p = img_p * 1000
cv2.imwrite('out.png', img_p, [int(cv2.IMWRITE_PNG_COMP/RESSION), 1])

written by higashino [Virtual Reality] [この記事のURL] [コメントを書く] [コメント(0)] [TB(0)]

この記事へのトラックバックPingURL

2022年7月16日(土) 21:06

難題山積

 撮影後のソフトウェア手振れ補正で、9割近くのフレームは収束する。収束してくれないのは1割強である。
 その1割強に対して最終2回の平均を取る方式で、動画全体を作り直してみた。予想された通り、ほぼ変化無し。区別を付けるのは困難だ。なら、もうこれでいいか・・・と思っていたが、重大なことを試し忘れていたことに気付いた。それは、拡大率である。

 回転不変位相限定相関法では、XY平面のズレと回転だけでなく、拡大率も推定できる。しかし動画の手ぶれ補正においては、拡大率は明らかに1固定である。マクロ撮影でカメラが前後に動く場合などは拡大率まで追随させても良いが、余りに特殊用途過ぎる。
 かくして、拡大率という概念が頭から完全に抜け落ちていたのだった。

 だが、収束させる過程においては拡大率も考慮すべきではないか?
 最終的に手ぶれ補正処理を行う際には拡大率1固定でも良いが、クローズドループ中では拡大率も管理するのがベターでは?
 そう考えて、試してみた。どうしても収束してくれないフレームが、しっかり収束した。正解だった!

 こうなると、GPU による高速化はどうしても試したい。1分のクリップを処理するのに1日掛かるようでは、試行錯誤もままならない。実用性も、失われる。
 手ぶれ量推測処理は画像出力がなく、ほぼ画像処理だけである。それだけ GPU の恩恵も大きいはずだ。
 CUDA への移植を進めようとしたが、いきなりフーリエ変換に直面。そうだ、numpy の関数でフーリエ変換しているが、これ CUDA に出来るのか?
 調べると、CuPy を使えばできるらしい。numpy を GPU(CUDA)対応にしたものが、CuPy だ。

 Python + OpenCV による画像処理はお手軽だが、GPU を使って高速化を図ると突然面倒になる。と言うか情報収集に苦労するようになる。そりゃあ GPU 対応の有無で OpenCV インストールの難易度が何次元も違うからなぁ・・・

 すぐに行き詰まった。numpy は良いとして、phaseCorrelate や warpPolar に対応する CUDA 関数が見つからないのだ。

 折角なので、phaseCorrelate を使わず CUDA で(回転不変ではない)位相限定相関法のプログラムを作ることろから研究してみよう。長い時間を要しそうなので、その間に GPU 使わない版(拡大率も利用して収束させる)を動かしておこう。
 動かし始めてすぐに、収束しないフレームが発生。これ最後まで走らせてみないと、拡大を考慮した効果は分からない。

written by higashino [Virtual Reality] [この記事のURL] [コメントを書く] [コメント(0)] [TB(0)]

この記事へのトラックバックPingURL

2022年7月15日(金) 21:53

画像保存の高速化

 繰り返し処理で時間が掛かるのは、仕方ない。ムカつくのは、png 圧縮がやたら遅いこと。しかし、imwrite には CUDA 関数が存在しないようなのだ。png のエンコードを GPU で高速化するには、どうすれば良いのだろう?
 調べると、どうやら方法は無さそうなのだ。

 だが、そもそもなぜ png 出力かと言うと、DaVinci Resolve で読み込めて OpenCV で書き出せる高画質な映像形式が他に存在しないからだ。mp4 でも高画質なパラメーターで書き出せるなら、それで良い。そして、OpenCV には、cudacodec なるものが存在し、DaVinci Resolve がやっているようなグラボによる mp4 エンコードができるらしい。
 処理時間を要しても画質を最優先したい静止画ステッチなどは、現状のまま png 出力で良い。だが、動画に関しては高画質 mp4 出力が可能かどうかを調べる価値がある。

 ところが調べ始めて、最近の成功例が無いことに気付く。最近はデフォルトで GPU を使ってくれるという説も流れているが、それは解決にならない。なぜなら、mp4 エンコード自体はずっと前に成功しているのであって、問題は高画質な mp4 エンコードができないこと。fps やサイズなど基本的なパラメーターしか指定できず、画質に関係するパラメーターを設定できないのだ。
 泥沼の香りが強烈。

 ここで、単純な解決策を発見。png ではなく jpg で書き出せば良い。
 imwrite では jpeg 画質の指定が可能であり、最高画質の100にすると不可逆圧縮とはいえかなり画質が上がる。png の半分ぐらいのサイズで、エンコードは遥かに速い。フォトショップで最高画質設定した jpeg と、ほぼサイズは同じ。たぶん、色情報が省略されていない。
 ステッチ結果の出力を最高画質 jpeg に変えると、ほぼ倍速になった。DaVinci Resolve でも連番 jpeg は読み込める。速度と品質のバランスで、これがベストだろう。

 さてここで、png を GPU で速くすることはできなかったが jpeg はどうだろう?

 NVIDIA が公式で nvJpeg を出している。そのものズバリで、GPU をフル活用して jpeg のエンコードやデコードを高速化する。人工知能の学習で膨大な量の jpeg を読むという状況を想定して作られたらしい。だが、問題は使い方が恐ろしく複雑なこと。特に Python からどのように使うかに関しては、まるで資料がない。これも、泥沼が確定的。
 プランBとして、GPU は使わないが高速化可能な libjpeg turbo を試してみる。OpenCV をコンパイルしないといけないが、コンパイルに成功したばかり。cmake にパラメーターを追加して、コンパイルし直す。

 1分間あたりの処理フレーム数は、png だと約17枚。jpeg だと約35枚。jpeg turbo だと約45枚。劇的ではないがプログラムそのままで済むことを考えると、かなり意義がある。生成される jpeg のサイズも、完全に同一だ。ここから映像処理を GPU 対応にすると、更に処理枚数が向上すると期待できる。

written by higashino [Virtual Reality] [この記事のURL] [コメントを書く] [コメント(0)] [TB(0)]

この記事へのトラックバックPingURL

<< 前のページ | 次のページ >>

Darkside(https対応しました)

Generated by MySketch GE 1.4.1

Remodelling origin is MySketch 2.7.4