やること
以前、ArduinoのPWM制御でLEDの調光を行いました。
実はこんな感じの撮影装置に組み込んだのですが、デューティ比1.0未満(255/255未満)で撮影するとちらつき(フリッカー)が発生します。
デューティ比0.5(127/255)↓
デューティ比0.125(31/255)↓
パルスが疎であるほど強いフリッカーが発生するようです。このフリッカーは撮影時の露光時間やなんかで低減することができるのですが、撮影後に補正するにはどうしたら良いか、試してみました。
グレースケール画像の場合
まずはグレースケールで試してみます。画像を読み込みます。
import cv2
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
def show(img):
plt.imshow(img, vmin=0, vmax=255)
plt.gray()
plt.show()
#画像読み込み
img = cv2.imread('7-33_pwm32_l.png', 0)
print(img.shape)
show(img)
(1080, 1920)
この赤い部分を切り取ります。
#縦の帯
crop = img[:, 400:500]
show(crop)
波を取得してグラフで見てみます。
def show_wave(wave, fitwave=[]):
plt.plot(wave, '-k')
if len(fitwave) > 0:
plt.plot(fitwave, '-r')
plt.show()
#波
wave = np.mean(crop, axis=1)
print(wave.shape)
show_wave(wave)
(1080,)
第1段階として大きなうねりを二次関数で近似します。
#二次関数
def func1(x, a, b, c):
return a*x**2 + b*x + c
#近似
(a, b, c), _ = curve_fit(func1, np.arange(len(wave)), wave)
print(a, b, c)
show_wave(wave, func1(np.arange(len(wave)), a, b, c))
-9.39485609391473e-06 0.007566672426938949 181.27256750738803
近似した二次関数を減算します。
#二次関数の減算
wave -= func1(np.arange(len(wave)), a, b, c)
show_wave(wave)
第2段階として小さな波をsin波で近似します。大雑把に初期値を与えてあげないとうまく近似できなかったので、画像から大まかな周期を読み取って入力しました。
#sin波
def func2(x, d, e, f):
return d*np.sin(e*(x+f))
#近似
(d, e, f), _ = curve_fit(func2, np.arange(len(wave)), wave, p0=(1, 2*np.pi/55, 0)) #係数eはおよそ2π/T (T:周期)
print(d, e, f)
show_wave(wave, func2(np.arange(len(wave)), d, e, f))
1.6077261042650683 0.1112577811474343 18.372644330945693
近似したsin波を減算します。
#sin波の減算
wave -= func2(np.arange(len(wave)), d, e, f)
show_wave(wave)
元の画像から差し引くべき「うねりとsin波を合成した画像」を作って確認します。輝度値100の下駄を履かせて可視化しています。
#二次関数とsin波の画像
wave_img = func1(np.arange(len(wave)), a, b, 0) + func2(np.arange(len(wave)), d, e, f) #切片は0に
wave_img = np.repeat(wave_img[:, None], 1920, axis=1)
print(wave_img.shape)
show(wave_img + 100) #一時的に明るくして確認
(1080, 1920)
最後に、元の画像からそれを減算します。float型とuint8型の運用に注意します。
#画像から二次関数とsin波を減算
img = (img.astype(float) - wave_img).astype('uint8')
show(img)
いい感じですね!
カラー画像の場合
以上のコードを関数aaa()にして、カラー画像の各チャンネルを処理するとこうなりました。
#画像読み込み
img = cv2.imread('7-33_pwm32_l.png')
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
#各チャンネルを処理
img[:, :, 0] = aaa(img[:, :, 0])
img[:, :, 1] = aaa(img[:, :, 1])
img[:, :, 2] = aaa(img[:, :, 2])
show(img)
成功ですね!
おわりに
まあ、露光時間等の撮影条件で解決できたんですけどね。