Pythonを使用した画像処理の紹介
Pythonでの画像処理の紹介
エピソード2:画像の強化、パート3:ヒストグラム操作
画像処理シリーズの第2エピソードの第3部へようこそ!このシリーズの前の部分では、フーリエ変換とホワイトバランスの技術について説明しましたが、今回は別の興味深い技術であるヒストグラム操作について探求します。
私のような人は、ヒストグラムがどのように操作されるのか疑問に思うかもしれません。ヒストグラムは単に画像のピクセル値の分布を示すグラフではないですか?実は、ヒストグラムを操作することによって、画像のコントラストと明るさを調整することができます。これにより、画像の視覚的な見栄えを大幅に向上させることができます。
では、ヒストグラム操作の世界に飛び込んで、さまざまなヒストグラム操作のテクニックを使用して画像のコントラストと明るさを向上させる方法を発見しましょう。これらのテクニックは、低光画像のオブジェクトの可視性を向上させるために使用したり、画像の詳細を向上させたり、過度または不十分に露出した画像を修正したりするために使用することができます。
まずは、関連するPythonライブラリをインポートしましょう:
- Pythonを使った感情分析(Sentiment Analysis)のFlair
- ベイリー・カクスマー、ウォータールー大学の博士課程候補 – インタビューシリーズ
- ジョシュ・フィースト、CogitoのCEO兼共同創業者 – インタビューシリーズ
ステップ1:ライブラリをインポートし、画像を読み込んで表示する
import numpy as npimport matplotlib.pyplot as pltfrom skimage.color import rgb2grayfrom skimage.exposure import histogram, cumulative_distributionfrom skimage import filtersfrom skimage.color import rgb2hsv, rgb2gray, rgb2yuvfrom skimage import color, exposure, transformfrom skimage.exposure import histogram, cumulative_distribution
# 画像を読み込んでアルファチャンネル(透明度)を削除するdark_image = imread('plasma_ball.png')[:,:,:3]# 画像を表示plt.figure(figsize=(10, 10))plt.title('オリジナル画像:プラズマボール')plt.imshow(dark_image)plt.show()
ステップ2:チャンネルの統計情報をチェックし、画像のヒストグラムをプロットする
def calc_color_overcast(image): # 各チャンネルのカラーオーバーキャストを計算 red_channel = image[:, :, 0] green_channel = image[:, :, 1] blue_channel = image[:, :, 2] # 結果を格納するためのデータフレームを作成 channel_stats = pd.DataFrame(columns=['平均', '標準偏差', '最小値', '中央値', 'P_80', 'P_90', 'P_99', '最大値']) # 各カラーチャンネルの統計を計算し、格納する for channel, name in zip([red_channel, green_channel, blue_channel], ['赤', '緑', '青']): mean = np.mean(channel) std = np.std(channel) minimum = np.min(channel) median = np.median(channel) p_80 = np.percentile(channel, 80) p_90 = np.percentile(channel, 90) p_99 = np.percentile(channel, 99) maximum = np.max(channel) channel_stats.loc[name] = [mean, std, minimum, median, p_80, p_90, p_99, maximum] return channel_stats
# これは前回のエピソードパート2の同じ関数です(チェックしてみてください!)calc_color_overcast(dark_image)
# ヒストグラムのプロットdark_image_intensity = img_as_ubyte(rgb2gray(dark_image))freq, bins = histogram(dark_image_intensity)plt.step(bins, freq*1.0/freq.sum())plt.xlabel('強度値')plt.ylabel('ピクセルの割合');
顕著な曇りは見られませんが、ピクセルの平均強度は非常に低く、画像の暗さと露出不足の可視化が確認されます。ヒストグラムは、ほとんどのピクセルが低い強度値を持っていることを示しています。低いピクセル強度値は、画像のほとんどのピクセルが非常に暗いか黒いことを意味します。画像のコントラストを改善するために、さまざまなヒストグラム操作技術を画像に適用することができます。
ステップ3: さまざまなヒストグラム操作技術の探索
ヒストグラム操作のいくつかの技術に取り組む前に、ヒストグラム均等化という一般的に使用されるヒストグラム操作技術を理解しましょう。
ヒストグラム均等化は、画像のピクセル強度を均等化してヒストグラムをより一様にする技術です。非均等なピクセル強度分布は、低いコントラストと詳細性を持つ画像を引き起こし、画像内のオブジェクトや特徴を区別するのが難しくなります。ピクセル強度分布をより均等にするための一つの方法は、画像の累積分布関数(CDF)を線形にすることです。これは、線形なCDFは画像内で各ピクセル強度値が同じ確率で発生することを意味します。一方、非線形なCDFは、特定のピクセル強度値が他よりも頻繁に発生することを意味し、非均等なピクセル強度分布を引き起こします。CDFを線形にすることで、ピクセル強度分布をより均等にし、画像のコントラストを改善することができます。
def plot_cdf(image): """ 画像の累積分布関数をプロットする。 パラメーター: image (ndarray): 入力画像。 """ # 必要に応じて画像をグレースケールに変換する if len(image.shape) == 3: image = rgb2gray(image[:,:,:3]) # 累積分布関数を計算する intensity = np.round(image * 255).astype(np.uint8) freq, bins = cumulative_distribution(intensity) # 実際のCDFと目標のCDFをプロットする target_bins = np.arange(256) target_freq = np.linspace(0, 1, len(target_bins)) plt.step(bins, freq, c='b', label='実際のCDF') plt.plot(target_bins, target_freq, c='r', label='目標のCDF') # 例のルックアップをプロットする example_intensity = 50 example_target = np.interp(freq[example_intensity], target_freq, target_bins) plt.plot([example_intensity, example_intensity, target_bins[-11], target_bins[-11]], [0, freq[example_intensity], freq[example_intensity], 0], 'k--', label=f'例のルックアップ ({example_intensity} -> {example_target:.0f})') # プロットをカスタマイズする plt.legend() plt.xlim(0, 255) plt.ylim(0, 1) plt.xlabel('強度値') plt.ylabel('ピクセルの累積割合') plt.title('累積分布関数') return freq, bins, target_freq, target_bins
dark_image = imread('plasma_ball.png')freq, bins, target_freq, target_bins = plot_cdf(dark_image);
このコードは、暗い画像の累積分布関数(CDF)を計算し、線形分布に基づく目標のCDFを定義します。それから、暗い画像の実際のCDFを青色で、目標のCDF(線形)を赤色でプロットします。また、例のルックアップの強度値もプロットされており、これにより、例では実際のCDFが50であり、それを230にターゲットしたいことが示されます。
# 強度値を実際の値50から目標値230に変換するサンプルdark_image_230 = dark_image_intensity.copy()dark_image_230[dark_image_230==50] = 230plt.figure(figsize=(10,10))plt.imshow(dark_image_230,cmap='gray');
目標のCDFを取得した後、次のステップは元のピクセルの強度を置き換えるために使用される強度値を計算することです。これは、補間を使用してルックアップテーブルを作成することで行われます。
# 全ての実際の値を目標値に置き換えた結果を表示new_vals = np.interp(freq, target_freq, target_bins)dark_image_eq = img_as_ubyte(new_vals[img_as_ubyte(rgb2gray(dark_image[:,:,:3]))].astype(int))plt.figure(figsize=(10,10))plt.imshow(dark_image_eq, cmap='gray');
np.interp()
関数は、実際のCDFと目標のCDFの間を補間して元のピクセルの強度値を置き換えるための強度値を計算します。その結果得られた強度値は、NumPyのインデックスを使用して元のピクセルの強度を置き換えるために使用されます。最後に、等化された画像はimshow()を使用してgrayで表示されます。
今、私たちは最も基本的なヒストグラムの操作を示しましたので、異なるタイプのCDFとテクニックを試し、どのテクニックが特定の画像に適しているかを見てみましょう:
def custom_rgb_adjustment(image, target_freq): target_bins = np.arange(256) freq_bins = [cumulative_distribution(image[:, :, i]) for i in range(3)] adjusted_channels = [] # 最小周波数で周波数をパッド padded_freqs = [] for i in range(len(freq_bins)): if len(freq_bins[i][0]) < 256: frequencies = list(freq_bins[i][0]) min_pad = [min(frequencies)] * (256 - len(frequencies)) frequencies = min_pad + frequencies else: frequencies = freq_bins[i][0] padded_freqs.append(np.array(frequencies)) for n in range(3): interpolation = np.interp(padded_freqs[n], target_freq, target_bins) adjusted_channel = img_as_ubyte(interpolation[image[:, :, n]].astype(int)) adjusted_channels.append([adjusted_channel]) adjusted_image = np.dstack((adjusted_channels[0][0], adjusted_channels[1][0], adjusted_channels[2][0])) return adjusted_image
# 線形target_bins = np.arange(256)# シグモイドdef sigmoid_cdf(x, a=1): return (1 + np.tanh(a * x)) / 2# 指数関数def exponential_cdf(x, alpha=1): return 1 - np.exp(-alpha * x)# べき関数def power_law_cdf(x, alpha=1): return x ** alpha# その他のテクニックdef adaptive_histogram_equalization(image, clip_limit=0.03, tile_size=(8, 8)): clahe = exposure.equalize_adapthist( image, clip_limit=clip_limit, nbins=256, kernel_size=(tile_size[0], tile_size[1])) return clahedef gamma_correction(image, gamma=1.0): corrected_image = exposure.adjust_gamma(image, gamma) return corrected_imagedef contrast_stretching_percentile(image, lower_percentile=5, upper_percentile=95): in_range = tuple(np.percentile(image, (lower_percentile, upper_percentile))) stretched_image = exposure.rescale_intensity(image, in_range) return stretched_imagedef unsharp_masking(image, radius=5, amount=1.0): blurred_image = filters.gaussian(image, sigma=radius, multichannel=True) sharpened_image = (image + (image - blurred_image) * amount).clip(0, 1) return sharpened_imagedef equalize_hist_rgb(image): equalized_image = exposure.equalize_hist(image) return equalized_imagedef equalize_hist_hsv(image): hsv_image = color.rgb2hsv(image[:,:,:3]) hsv_image[:, :, 2] = exposure.equalize_hist(hsv_image[:, :, 2]) hsv_adjusted = color.hsv2rgb(hsv_image) return hsv_adjusteddef equalize_hist_yuv(image): yuv_image = color.rgb2yuv(image[:,:,:3]) yuv_image[:, :, 0] = exposure.equalize_hist(yuv_image[:, :, 0]) yuv_adjusted = color.yuv2rgb(yuv_image) return yuv_adjusted
# 各テクニックを変数に保存linear = custom_rgb_adjustment(dark_image, np.linspace(0, 1, len(target_bins)))sigmoid = custom_rgb_adjustment(dark_image, sigmoid_cdf((target_bins - 128) / 64, a=1))exponential = custom_rgb_adjustment(dark_image, exponential_cdf(target_bins / 255, alpha=3))power = custom_rgb_adjustment(dark_image, power_law_cdf(target_bins / 255, alpha=2))clahe_image = adaptive_histogram_equalization( dark_image, clip_limit=0.09, tile_size=(50, 50))gamma_corrected_image = gamma_correction(dark_image, gamma=0.4)sharpened_image = unsharp_masking(dark_image, radius=10, amount=-0.98)cs_image = contrast_stretching_percentile(dark_image, 0, 70)equalized_rgb = equalize_hist_rgb(dark_image)equalized_hsv = equalize_hist_hsv(dark_image)equalized_yuv = equalize_hist_yuv(dark_image)
# グラフ作成fig, axes = plt.subplots(3, 4, figsize=(20, 20))# オリジナル画像axes[0, 0].imshow(dark_image)axes[0, 0].set_title('オリジナル画像', fontsize=20)# ヒストグラム平坦化:RGB調整axes[0, 1].imshow(equalized_rgb)axes[0, 1].set_title('RGB調整', fontsize=20)# HSV調整axes[0, 2].imshow(equalized_hsv)axes[0, 2].set_title('HSV調整', fontsize=20)# YUV調整axes[0, 3].imshow(equalized_yuv)axes[0, 3].set_title('YUV調整', fontsize=20)# 線形CDFaxes[1, 0].imshow(linear)axes[1, 0].set_title('線形', fontsize=20)# シグモイドCDFaxes[1, 1].imshow(sigmoid)axes[1, 1].set_title('シグモイド', fontsize=20)# 指数関数CDFaxes[1, 2].imshow(exponential)axes[1, 2].set_title('指数関数', fontsize=20)# べき関数CDFaxes[1, 3].imshow(power)axes[1, 3].set_title('べき関数', fontsize=20)# コントラストストレッチングaxes[2, 0].imshow(cs_image)axes[2, 0].set_title('コントラストストレッチング', fontsize=20)# 適応的ヒストグラム平坦化(CLAHE)axes[2, 1].imshow(clahe_image)axes[2, 1].set_title('適応的ヒストグラム平坦化', fontsize=20)# ガン
RGBで画像を修正するための方法/技術はたくさんありますが、ほとんどの場合、パラメータの手動調整が必要です。Output #7は、さまざまなヒストグラム操作技術を使用して生成された修正画像のプロットを表示しています。
HSVの調整、指数関数、コントラストストレッチ、アンシャープマスキングはすべて満足のいくものと思われます。ただし、結果は使用される元の画像に基づいて異なる場合があります。特定の画像に応じて、さまざまなパラメータの値を試して、目的の画像品質を実現するために実験することができます。
ヒストグラム操作技術は、画像のコントラストと全体的な外観を大幅に向上させることができます。ただし、使用方法には注意が必要です。過度に使用すると、アーティファクトが導入され、不自然な外観になる場合があります。Output #7で使用されるいくつかの技術(例:粒状の背景と過剰強調されたエッジを持つ適応的なヒストグラム均等化)から明らかです。
上記の暗い画像とは対照的に、同じパラメータ値でコードを実行して明るい画像にも試しました。ここで何が起こったかを観察しましょう:
お気づきかもしれませんが、暗い画像にうまく機能したほとんどの技術は、明るい画像にはうまく機能しませんでした。HSVの調整、指数関数、アンシャープマスキングなどの技術は、画像にアーティファクトやノイズを追加しました。これは、これらの技術が画像内の既存の明るさを強化または増幅する可能性があるためであり、これにより過曝光やアーティファクトやノイズの追加が生じる可能性があります。
ただし、コントラストストレッチは、予想どおり一部の部分を元の画像よりも明るくするものの、コントラストストレッチは画像内のピクセル値の範囲を拡大して全体的なコントラストを高めるため、明るい画像と暗い画像の両方に使用できる柔軟な解決策を提供します。
結論
このエピソードでは、画像処理の世界に深く入り込み、さまざまな画像の強化技術を探求しました。フーリエ変換(Part 1)、ホワイトバランスアルゴリズム(Part 2)、およびヒストグラム操作技術(Part 3、このPart)と、関連するPythonコードをskimageライブラリを使用してカバーしました。
最終的に、最適な画像の強化技術の選択は、特定の画像と出力品質の要件に依存します。最良の方法として、複数の画像の強化技術を試し、異なるパラメータの値を調整して、目的の画像品質を達成するようにしてください。さまざまな画像の強化技術の影響についてより良い理解を得るのに役立つことを願っています。
画像処理へのこのエキサイティングな旅を続けるにつれ、まだ学びや探求すべきことはたくさんあります。Pythonシリーズのイメージ処理入門の次回のインストールメントでは、さらに高度な技術や応用について説明しますので、お楽しみに!
We will continue to update VoAGI; if you have any questions or suggestions, please contact us!
Was this article helpful?
93 out of 132 found this helpful
Related articles