2022/5/6 HPトップとサービス一覧を一新しました!トップの新着情報にご注目ください!

7-33. PWM調光LEDで撮影した写真のちらつき(フリッカー)を打ち消してみた

やること

以前、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)

成功ですね!

おわりに

まあ、露光時間等の撮影条件で解決できたんですけどね。

タイトルとURLをコピーしました