はい、承知いたしました。「やさしいOpenCV-Python入門:初心者でもわかる画像・動画処理」の詳細な説明を含む記事を、約5000語で作成し、直接表示します。
やさしいOpenCV-Python入門:初心者でもわかる画像・動画処理
はじめに:画像・動画処理の世界へようこそ!
現代社会では、スマートフォンで写真を撮ったり、動画を共有したりすることが日常になっています。これらの画像や動画の裏側では、様々な技術が使われており、その中核を担っているのが「画像処理」や「動画処理」といった分野です。
画像処理とは、コンピューターを使って画像に様々な操作を施す技術のことです。例えば、写真の色合いを変えたり、不要な部分を削除したり、写っている物体を認識したり、といったことが挙げられます。動画処理は、この画像処理を連続するフレーム(静止画)に対して行うことで実現されます。
これらの技術は、顔認識を使ったスマートフォンのロック解除、自動運転車の周囲環境認識、医療現場での画像診断支援、工場での製品検査など、私たちの身近なところから専門分野まで、非常に幅広い分野で活用されています。
「なんだか難しそう…」と感じた方もいるかもしれません。しかし、安心してください。この分野を学ぶための強力なツールがあり、Pythonという比較的学びやすいプログラミング言語と組み合わせることで、初心者の方でも十分に理解し、実践できるようになります。
その強力なツールこそが OpenCV (Open Source Computer Vision Library) です。OpenCVは、画像処理やコンピュータービジョンに関する様々な機能を提供する、非常に有名なオープンソースライブラリです。C++で書かれており高速ですが、Pythonを含む様々なプログラミング言語から利用できるようになっています。Pythonから使うことで、OpenCVの強力な機能を簡単に、そして効率的に活用できます。
この入門記事では、OpenCVとPythonを使って、画像や動画を読み込んだり、基本的な編集をしたり、写っているものを認識したり、といった基本的な操作から、少し進んだ内容までを、初心者の方にも分かりやすく解説していきます。
この記事の対象読者
- Pythonの基本的な文法(変数、リスト、関数など)を理解している方。
- 画像や動画処理に興味がある方。
- OpenCVを使って何かを作ってみたいと思っている方。
プログラミング自体が初めて、という方には少し難しいかもしれませんが、Pythonの基本的な学習と並行して進めることも可能です。
この記事を読み終える頃には、あなたはOpenCVとPythonを使って画像や動画を操作する基本的なスキルを身につけ、さらに奥深い画像・動画処理の世界を探求する足がかりを得ていることでしょう。さあ、一緒にOpenCV-Pythonの世界へ踏み出しましょう!
準備:OpenCVを使えるようにしよう
OpenCVを使うためには、まずPythonとその実行環境が必要です。そして、OpenCVライブラリ自体をインストールする必要があります。
1. Pythonのインストール(確認)
Pythonは公式ウェブサイトからダウンロードしてインストールできます。多くのOS(Windows, macOS, Linux)に対応しています。記事執筆時点ではPython 3.7以降を推奨します。
既にPythonがインストールされているか確認するには、ターミナルやコマンドプロンプトを開いて以下のコマンドを実行してください。
“`bash
python –version
または
python3 –version
“`
もしバージョン情報が表示されればインストールされています。表示されない場合は、公式サイトからダウンロード・インストールしてください。AnacondaなどのPythonディストリビューションを使うのも便利です。
2. 仮想環境の推奨
Pythonで開発を行う際には、「仮想環境」を使うことを強くお勧めします。仮想環境とは、プロジェクトごとに独立したPythonの実行環境を作る仕組みです。これを使うことで、プロジェクトAで使うライブラリのバージョンとプロジェクトBで使うライブラリのバージョンが異なっていても、互いに干渉せずに開発を進めることができます。
標準ライブラリの venv
を使うのが簡単です。プロジェクトを作成したいディレクトリで以下のコマンドを実行します。
bash
python -m venv myenv
これで myenv
という名前の仮想環境が作成されます。次に、その仮想環境を有効化します。
- Windowsの場合:
bash
myenv\Scripts\activate - macOS / Linuxの場合:
bash
source myenv/bin/activate
仮想環境が有効になると、ターミナルのプロンプトの前に (myenv)
のような表示が出ます。この状態でインストールしたライブラリは、この仮想環境の中にのみ存在します。
仮想環境を終了する際は deactivate
コマンドを実行します。
3. OpenCVのインストール
仮想環境を有効化した状態で、以下のコマンドを実行してOpenCVをインストールします。
bash
pip install opencv-python
OpenCVの主要な機能が含まれる opencv-python
パッケージをインストールします。もし有料のアルゴリズム(SIFT, SURFなど、特許やライセンスの問題でデフォルトでは含まれないもの)も使いたい場合は、代わりに opencv-contrib-python
をインストールしますが、入門レベルでは opencv-python
で十分です。
また、画像処理の結果を表示したり、ヒストグラムをグラフ化したりするのに便利な matplotlib
ライブラリも合わせてインストールしておくと良いでしょう。
bash
pip install matplotlib numpy
numpy
はOpenCVが画像を扱う際に内部的に使用するライブラリなので、明示的にインストールしておくと安心です。
これでOpenCVを使う準備が整いました。早速、最初のコードを書いてみましょう。
3. 基本的な画像処理:画像を読み込んで表示する
OpenCV-Pythonの最初のステップは、画像を読み込んで画面に表示することです。
3.1. 画像ファイルの読み込み
画像ファイルを読み込むには cv2.imread()
関数を使います。
“`python
import cv2
画像ファイルのパスを指定します
ここでは ‘input.jpg’ という名前のファイルが同じディレクトリにあると仮定します
image_path = ‘input.jpg’
画像を読み込みます
cv2.imread() は画像をNumPyの配列として読み込みます
第2引数は読み込み方法を指定します
cv2.IMREAD_COLOR: カラー画像として読み込む(デフォルト)
cv2.IMREAD_GRAYSCALE: グレースケール画像として読み込む
cv2.IMREAD_UNCHANGED: アルファチャンネル(透過情報)も含めて読み込む
img = cv2.imread(image_path, cv2.IMREAD_COLOR)
ファイルが見つからなかった場合や読み込みに失敗した場合、img は None になります
if img is None:
print(f”エラー: 画像ファイル ‘{image_path}’ が見つからないか、読み込めません。”)
else:
print(“画像を正常に読み込みました。”)
# ここに画像処理や表示のコードを書きます
“`
cv2.imread()
は、指定されたパスの画像をNumPyの配列(正確には numpy.ndarray
オブジェクト)として読み込みます。カラー画像の場合、配列の shape は (高さ, 幅, チャンネル数)
となります。チャンネル数は通常3で、BGRという順番で青(Blue)、緑(Green)、赤(Red)の色情報が格納されています。(一般的なRGBとは順番が逆なので注意が必要です。)グレースケール画像の場合は (高さ, 幅)
となり、チャンネル数は1です。
画像ファイルが存在しない、あるいは破損しているなどの理由で読み込みに失敗した場合、cv2.imread()
は None
を返します。そのため、画像を読み込んだ後は、必ず None
でないか確認することが重要です。
3.2. 画像の表示
読み込んだ画像を画面に表示するには cv2.imshow()
関数を使います。
“`python
上記の画像読み込みコードに続けて記述します
if img is not None:
# ウィンドウ名と画像データを指定して画像を表示します
# ウィンドウ名は複数表示する場合に区別するために使います
cv2.imshow(‘Loaded Image’, img)
# cv2.imshow() は画像を表示するウィンドウを作成しますが、
# プログラムをすぐに終了させるとウィンドウが表示されないか、すぐに消えてしまいます。
# cv2.waitKey() はキー入力があるまでプログラムの実行を待機させます。
# 引数に待機時間(ミリ秒)を指定できます。0を指定すると無限に待機します。
# 戻り値は押されたキーのASCIIコードです。
print("画像が表示されました。ウィンドウをクリックし、任意のキーを押すと閉じます。")
cv2.waitKey(0) # 何かキーが押されるまで待機
# 作成された全てのウィンドウを閉じます
cv2.destroyAllWindows()
“`
cv2.imshow('ウィンドウ名', 画像データ)
で指定したウィンドウ名を持つウィンドウに画像を表示します。
cv2.waitKey()
は、OpenCVのウィンドウシステムがイベント(キーボード入力やマウス操作など)を処理するための重要な関数です。これがないと、imshow
で表示されたウィンドウはすぐに消えてしまいます。引数に 0
を指定すると、ユーザーが何かキーを押すまでプログラムの実行を停止します。もし特定の時間(例えば5秒間)だけ表示したい場合は、cv2.waitKey(5000)
とミリ秒で時間を指定します。時間経過後もキー入力待機中でも、何らかのキーが押されるとそのキーのASCIIコードを返します。待機時間内に何もキーが押されなかった場合は -1
を返します。
cv2.destroyAllWindows()
は、OpenCVによって作成された全てのウィンドウを閉じます。プログラムの終了時に呼び出すのが一般的です。特定のウィンドウだけを閉じたい場合は cv2.destroyWindow('ウィンドウ名')
を使います。
ここまでのコードをまとめて実行してみましょう
適当な画像ファイル(例: input.jpg
)を用意し、以下のPythonコードと同じディレクトリに置きます。
“`python
import cv2
import numpy as np # OpenCVはNumPyを使うのでインポートしておくと良い
画像ファイルのパス
image_path = ‘input.jpg’
画像を読み込み(カラー)
img_color = cv2.imread(image_path, cv2.IMREAD_COLOR)
グレースケールで読み込み
img_gray = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
画像が正常に読み込めたか確認
if img_color is None:
print(f”エラー: 画像ファイル ‘{image_path}’ が見つからないか、読み込めません。”)
else:
# 画像を表示
cv2.imshow(‘Color Image’, img_color)
cv2.imshow(‘Grayscale Image’, img_gray) # グレースケール画像も表示
# 何かキーが押されるまで待機
print("画像ウィンドウが表示されました。ウィンドウをアクティブにして何かキーを押してください。")
cv2.waitKey(0)
# 全てのウィンドウを閉じる
cv2.destroyAllWindows()
print(“プログラムを終了します。”)
“`
このコードを実行すると、読み込んだカラー画像とグレースケール画像の2つのウィンドウが表示され、何かキーを押すとウィンドウが閉じてプログラムが終了します。
3.3. 画像の保存
処理した画像をファイルに保存するには cv2.imwrite()
関数を使います。
“`python
上記の画像読み込み・表示コードに続けて記述します
if img_color is not None:
# グレースケール画像をファイルに保存します
# 第1引数に保存先のファイルパス、第2引数に保存したい画像データを指定します
output_gray_path = ‘output_grayscale.jpg’
success = cv2.imwrite(output_gray_path, img_gray)
if success:
print(f"グレースケール画像を '{output_gray_path}' に保存しました。")
else:
print(f"エラー: グレースケール画像の保存に失敗しました。")
# カラー画像をPNG形式で保存することもできます
output_color_png_path = 'output_color.png'
success_png = cv2.imwrite(output_color_png_path, img_color)
if success_png:
print(f"カラー画像を '{output_color_png_path}' に保存しました。")
else:
print(f"エラー: カラー画像の保存に失敗しました。")
# cv2.waitKey() と cv2.destroyAllWindows() は必要に応じて最後にまとめて呼び出します
“`
cv2.imwrite('保存先パス', 画像データ)
で画像を保存できます。保存される画像の形式は、ファイル名の拡張子(例: .jpg
, .png
, .bmp
など)によって自動的に判断されます。関数が成功した場合は True
、失敗した場合は False
を返します。
3.4. 画像情報の取得
読み込んだ画像のサイズやチャンネル数などの情報を取得するには、NumPy配列の属性を利用します。
“`python
img_color = cv2.imread(image_path, cv2.IMREAD_COLOR) などで画像を読み込んだ後
if img_color is not None:
# 画像の形状(shape)を取得します
# カラー画像: (高さ, 幅, チャンネル数)
# グレースケール画像: (高さ, 幅)
print(“画像の形状 (shape):”, img_color.shape)
# 画像の高さと幅を取得します
height, width = img_color.shape[:2] # shapeの最初の2要素を取り出す
print("画像の高さ:", height)
print("画像の幅:", width)
# チャンネル数を取得します
# グレースケール画像の場合は len(shape) が 2 になるため、条件分岐が必要です
if len(img_color.shape) == 3:
channels = img_color.shape[2]
print("チャンネル数:", channels)
else:
print("チャンネル数: 1 (グレースケール画像)")
# 画像のデータ型(dtype)を取得します - 通常は uint8 (符号なし8ビット整数)
print("画像のデータ型:", img_color.dtype)
# 画像の総画素数(高さ * 幅 * チャンネル数)を取得します
print("総画素数:", img_color.size)
“`
画像の形状は img.shape
で取得でき、これはNumPy配列の shape と同じです。カラー画像なら (高さ, 幅, チャンネル数)
、グレースケール画像なら (高さ, 幅)
のタプルが返ります。画像の高さは img.shape[0]
、幅は img.shape[1]
で取得できます。チャンネル数はカラー画像の場合 img.shape[2]
ですが、グレースケール画像にはチャンネル数の情報が含まれない(shapeが2要素)ため、len(img.shape)
で形状の次元数を確認する必要があります。
img.dtype
は画像の各画素値のデータ型を示します。画像ファイルから読み込んだほとんどの場合、各チャンネルの値は0から255の整数で表現されるため、データ型は uint8
(符号なし8ビット整数)になります。
img.size
は画像の総画素数、つまり配列の全要素数を示します。カラー画像なら 高さ * 幅 * チャンネル数
、グレースケールなら 高さ * 幅
になります。
3.5. カラー画像の扱い(BGR形式)
OpenCVがカラー画像を扱う際の最も重要な注意点の一つは、色の順序が BGR (Blue, Green, Red) であることです。これは一般的な画像編集ソフトや、matplotlibなどの他のライブラリで使われるRGB (Red, Green, Blue) とは順番が逆です。
例えば、カラー画像の特定の画素 (y, x)
の値にアクセスすると、[B, G, R]
の順で値が取得されます。
“`python
img_color がカラー画像として読み込まれていると仮定
if img_color is not None:
# 特定の画素 (行インデックス y, 列インデックス x) の値にアクセス
# 例: 行100, 列150 の画素値を取得
y = 100
x = 150
# OpenCVのBGR順に注意
# pixel[0] -> Blue値
# pixel[1] -> Green値
# pixel[2] -> Red値
pixel = img_color[y, x]
print(f"画素 ({y}, {x}) の値 (BGR): {pixel}")
# 画素値を変更することもできます
# 例: その画素を青色 (BGRで [255, 0, 0]) にする
img_color[y, x] = [255, 0, 0] # B=255, G=0, R=0
# 変更後の画像を表示して確認
cv2.imshow('Modified Image', img_color)
print("画素の値を変更しました。ウィンドウを確認してください。")
cv2.waitKey(0)
cv2.destroyAllWindows()
“`
画素値へのアクセスや変更は、NumPy配列のスライスやインデックス指定の機能を使って行います。img[y, x]
で指定した座標(yは行、xは列)の画素の値を取得・設定できます。カラー画像の場合は img[y, x, channel]
のようにチャンネルを指定することも可能です。
注意点: NumPy配列のインデックスは [行, 列]
または [y, x]
の順です。画像処理で一般的に使われる座標系 (x, y)
とは順番が逆になることが多いので、混同しないように注意が必要です。画像の幅がx方向、高さがy方向と考えると、NumPy配列のshapeは (yの長さ, xの長さ)
に相当します。
3.6. グレースケール変換
カラー画像をグレースケールに変換するには、cv2.cvtColor()
関数を使います。cvtColor
は色空間の変換を行う汎用的な関数です。
“`python
img_color がカラー画像として読み込まれていると仮定
if img_color is not None:
# cv2.cvtColor() 関数を使って色空間を変換します
# 第1引数: 変換したい画像データ
# 第2引数: 変換方法を示すフラグ
# cv2.COLOR_BGR2GRAY: BGR形式からグレースケールへの変換
img_gray_converted = cv2.cvtColor(img_color, cv2.COLOR_BGR2GRAY)
cv2.imshow('Original Color', img_color)
cv2.imshow('Converted Grayscale', img_gray_converted)
print("グレースケールに変換しました。ウィンドウを確認してください。")
cv2.waitKey(0)
cv2.destroyAllWindows()
# 変換後の画像の形状を確認
print("変換後のグレースケール画像の形状:", img_gray_converted.shape)
# 結果は (高さ, 幅) となり、チャンネル数がなくなっていることがわかります
“`
cv2.cvtColor()
は非常に多くの色空間変換に対応しています。cv2.COLOR_BGR2GRAY
の他にも、RGB<=>HSV, BGR<=>HSV, BGR<=>YCrCbなど、様々な変換フラグが用意されています。
3.7. チャンネルの分割と結合
カラー画像は、通常B、G、Rの3つのチャンネルから構成されています。これらのチャンネルを個別の画像(グレースケール画像のようなもの)として分離したり、分離したチャンネルを組み合わせて再びカラー画像を生成したりすることができます。これには cv2.split()
と cv2.merge()
関数を使います。
“`python
img_color がカラー画像として読み込まれていると仮定
if img_color is not None:
# cv2.split() でチャンネルを分割します
# 戻り値はチャンネルごとの画像のリストです [Bチャンネル, Gチャンネル, Rチャンネル]
b_channel, g_channel, r_channel = cv2.split(img_color)
# 分割した各チャンネル画像を表示
cv2.imshow('Blue Channel', b_channel)
cv2.imshow('Green Channel', g_channel)
cv2.imshow('Red Channel', r_channel)
print("チャンネルを分割しました。各チャンネル画像を確認してください。")
cv2.waitKey(0) # ここで待機して各チャンネル画像を確認
# cv2.merge() でチャンネルを結合します
# 引数にはチャンネル画像のリストを渡します
# 例: 元と同じBGR順で結合
merged_bgr = cv2.merge([b_channel, g_channel, r_channel])
cv2.imshow('Merged BGR', merged_bgr)
# 例: RGB順に結合してみる(色が反転したように見えるはず)
merged_rgb = cv2.merge([r_channel, g_channel, b_channel])
cv2.imshow('Merged RGB (looks different)', merged_rgb)
print("チャンネルを結合しました。結合結果を確認してください。")
cv2.waitKey(0) # ここで待機して結合結果を確認
cv2.destroyAllWindows()
“`
cv2.split()
はカラー画像を各チャンネルのグレースケール画像(shape
が (高さ, 幅)
)のリストとして返します。cv2.merge()
は、同じサイズを持つ複数のグレースケール画像を結合して、多チャンネル画像を作成します。カラー画像を作成する場合は、3つのチャンネル画像をリストとして渡し、そのリスト内の順番がそのまま新しい多チャンネル画像のチャンネル順になります。OpenCVで表示する場合はBGR順にしないと色が正しく表示されないことに注意が必要です。
3.8. 画像のサイズ変更
画像のサイズを変更するには cv2.resize()
関数を使います。
“`python
img が読み込まれていると仮定
if img is not None:
# 元の画像のサイズを取得
height, width = img.shape[:2]
print(f”元の画像のサイズ: {width}x{height}”)
# サイズを半分に縮小
# 第1引数: 入力画像
# 第2引数: 出力画像のサイズ (幅, 高さ) のタプル
# 第3引数 (省略可): x方向のスケールファクタ (fx)
# 第4引数 (省略可): y方向のスケールファクタ (fy)
# 第5引数 (省略可): 補間方法 (interpolation)
img_half_size = cv2.resize(img, (width // 2, height // 2))
# スケールファクタを指定してサイズ変更(こちらの方が直感的かも)
# 出力サイズは None にして fx, fy を指定する
img_quarter_size = cv2.resize(img, None, fx=0.25, fy=0.25)
# サイズを大きくする(例えば元の2倍)
img_double_size = cv2.resize(img, None, fx=2.0, fy=2.0)
cv2.imshow('Original Size', img)
cv2.imshow('Half Size', img_half_size)
cv2.imshow('Quarter Size (using fx, fy)', img_quarter_size)
cv2.imshow('Double Size', img_double_size)
print("画像のサイズを変更しました。結果を確認してください。")
cv2.waitKey(0)
cv2.destroyAllWindows()
print(f"Half Size の形状: {img_half_size.shape}")
print(f"Quarter Size の形状: {img_quarter_size.shape}")
print(f"Double Size の形状: {img_double_size.shape}")
“`
cv2.resize()
には、新しいサイズを直接 (幅, 高さ)
のタプルで指定する方法と、元のサイズに対するスケールファクタ fx
, fy
を指定する方法があります。両方指定した場合は出力サイズが優先されます。どちらか一方を指定するのが一般的です。
また、サイズ変更時には画素の補間(Interpolation)が必要です。OpenCVはいくつかの補間方法をサポートしています。デフォルトでは cv2.INTER_LINEAR
(バイリニア補間)が使われます。
* cv2.INTER_NEAREST
: 最近傍補間 (最も高速、画質は劣る)
* cv2.INTER_LINEAR
: バイリニア補間 (デフォルト)
* cv2.INTER_CUBIC
: バイキュービック補間 (より滑らか、少し遅い)
* cv2.INTER_LANCZOS4
: Lanczos補間 (より高精度だが遅い)
画像を縮小する際は cv2.INTER_AREA
が推奨されることが多いです。
3.9. 画像の切り抜き
画像の特定の部分を切り抜く(クロップする)のは、NumPy配列のスライス機能を使って簡単に行えます。
“`python
img が読み込まれていると仮定
if img is not None:
# 切り抜きの範囲を指定します
# 例: 左上 (50, 100) から 右下 (250, 300) までを切り抜き
# NumPyのスライスは [開始行:終了行, 開始列:終了列] または [y_start:y_end, x_start:x_end]
x_start, y_start = 100, 50
x_end, y_end = 300, 250
# 範囲外を指定するとエラーになるので注意
# ここでは簡易的な例として範囲チェックは省略
cropped_img = img[y_start:y_end, x_start:x_end]
cv2.imshow('Original Image', img)
cv2.imshow('Cropped Image', cropped_img)
print(f"画像を ({x_start}, {y_start}) から ({x_end}, {y_end}) の範囲で切り抜きました。")
print("結果を確認してください。")
cv2.waitKey(0)
cv2.destroyAllWindows()
print(f"元の画像の形状: {img.shape}")
print(f"切り抜き後の画像の形状: {cropped_img.shape}") # 高さ=(y_end-y_start), 幅=(x_end-x_start) となる
“`
NumPy配列のスライス [y_start:y_end, x_start:x_end]
を使うことで、指定した範囲の画像データを簡単に取得できます。y_start
から y_end-1
までの行、x_start
から x_end-1
までの列が切り抜かれます。
3.10. 画像の回転・平行移動(アフィン変換)
画像を回転させたり、平行移動させたりといった幾何学的な変換は、アフィン変換を使って行います。アフィン変換は、直線や平行線を維持する変換です。
アフィン変換を行うには、まず変換行列(2×3のNumPy配列)を計算し、次にその行列を使って cv2.warpAffine()
関数を呼び出します。
平行移動:
“`python
img が読み込まれていると仮定
if img is not None:
height, width = img.shape[:2]
# 平行移動量を指定 (tx, ty)
# 例: 右に 50 画素、下に 30 画素移動
tx, ty = 50, 30
# 変換行列 M を作成
# [[1, 0, tx],
# [0, 1, ty]]
M_translation = np.float32([[1, 0, tx], [0, 1, ty]])
# cv2.warpAffine() で変換を実行
# 第1引数: 入力画像
# 第2引数: 変換行列 (float32型)
# 第3引数: 出力画像のサイズ (幅, 高さ) のタプル
# 第4引数 (省略可): 補間方法
# 第5引数 (省略可): 境界の扱い方
translated_img = cv2.warpAffine(img, M_translation, (width, height))
cv2.imshow('Original Image', img)
cv2.imshow('Translated Image', translated_img)
print(f"画像を右に {tx}、下に {ty} 画素平行移動しました。")
print("結果を確認してください。")
cv2.waitKey(0)
cv2.destroyAllWindows()
“`
平行移動の変換行列は非常にシンプルです。左上の2×2部分は単位行列 [[1, 0], [0, 1]]
で、右端の列 [tx, ty]
が移動量を示します。cv2.warpAffine
の出力サイズには、通常、変換前の画像のサイズを指定します。移動によって画像の一部が画面外に出る可能性があります。
回転:
画像を回転させるには、cv2.getRotationMatrix2D()
関数を使って回転行列を取得します。この関数は、回転の中心座標、回転角度、スケールファクタを指定できます。
“`python
img が読み込まれていると仮定
if img is not None:
height, width = img.shape[:2]
# 回転の中心座標 (通常は画像の中心)
center = (width // 2, height // 2)
# 回転角度 (度数). 例: 45度回転
angle = 45
# スケールファクタ (拡大縮小率). 1.0 で元のサイズ
scale = 1.0
# 回転行列 M を取得
M_rotation = cv2.getRotationMatrix2D(center, angle, scale)
# cv2.warpAffine() で変換を実行
# 回転によって画像が画面外に出る場合があるため、出力サイズを調整することが多いですが、
# ここでは簡単のため元のサイズを指定
rotated_img = cv2.warpAffine(img, M_rotation, (width, height))
cv2.imshow('Original Image', img)
cv2.imshow(f'Rotated by {angle} degrees', rotated_img)
print(f"画像を {angle} 度回転しました。結果を確認してください。")
print("中心座標:", center)
print("回転行列:\n", M_rotation) # 計算された回転行列を表示
cv2.waitKey(0)
cv2.destroyAllWindows()
# 注意: 回転によって画像が画面外に出てしまうため、
# 全ての画素を含むように出力サイズを調整する必要がある場合が多いです。
# これは少し複雑になるため、ここでは扱いません。
“`
cv2.getRotationMatrix2D(中心座標, 角度, スケール)
で回転行列が得られます。角度は度数で指定し、反時計回りが正の方向です。この回転行列を cv2.warpAffine()
に渡すことで、回転変換が実行されます。ただし、デフォルトの出力サイズ (width, height)
だと、回転によって画像全体が収まらないことがあります。全ての画素を収めるには、変換後の画像のサイズを計算し直して cv2.warpAffine
に渡す必要がありますが、これは少し応用的な内容になります。
4. 色空間と輝度値操作
画像処理において、色や明るさ(輝度)を調整することは非常に重要です。OpenCVでは様々な色空間を扱ったり、画素の輝度値を直接操作したりできます。
4.1. HSV色空間への変換と色によるフィルタリング
OpenCVが扱うデフォルトの色空間はBGRですが、特定の色を検出したり、色相、彩度、明度を個別に操作したりする際には、HSV (Hue, Saturation, Value) 色空間が非常に便利です。HSV色空間では、色相(Hue)が色の種類(赤、青、黄など)、彩度(Saturation)が色の鮮やかさ、明度(Value)が色の明るさを表します。これにより、「赤い物体だけを検出する」といった処理が容易になります。
BGRからHSVへの変換も cv2.cvtColor()
を使います。
“`python
img_color がカラー画像として読み込まれていると仮定
if img_color is not None:
# BGRからHSVに変換
img_hsv = cv2.cvtColor(img_color, cv2.COLOR_BGR2HSV)
cv2.imshow('Original BGR', img_color)
cv2.imshow('Converted HSV', img_hsv) # HSV画像を表示しても色は人間の目には分かりにくい
print("画像をBGRからHSVに変換しました。")
cv2.waitKey(0)
cv2.destroyAllWindows()
# 特定の色の範囲を指定してマスクを作成する
# 例: 青色の範囲を指定 (HSVのH, S, Vで下限と上限を指定)
# HSVの範囲は画像や照明条件によって異なるため、調整が必要です
# OpenCVのHUEは0-179の範囲に正規化されていることに注意 (多くの画像編集ソフトは0-360)
# SaturationとValueは0-255
lower_blue = np.array([100, 50, 50]) # 青の下限 (例)
upper_blue = np.array([140, 255, 255]) # 青の上限 (例)
# cv2.inRange() で指定した範囲内の画素を白(255)、範囲外を黒(0)とする二値画像を生成します
# これを「マスク」と呼びます
mask_blue = cv2.inRange(img_hsv, lower_blue, upper_blue)
# マスクを使って元の画像から青色部分だけを抽出する (bitwise_and 演算)
# マスクが白(1)の箇所の画素だけが抽出されます
blue_objects = cv2.bitwise_and(img_color, img_color, mask=mask_blue)
cv2.imshow('Mask for Blue Color', mask_blue) # マスク画像 (二値画像)
cv2.imshow('Extracted Blue Objects', blue_objects) # 青色部分だけが抽出された画像
print("青色のマスクを作成し、青色部分を抽出しました。")
cv2.waitKey(0)
cv2.destroyAllWindows()
“`
cv2.inRange(画像データ, 下限配列, 上限配列)
は、入力画像データの各要素が指定された下限と上限の範囲内にあるかどうかをチェックし、その結果を二値画像(マスク画像)として返します。範囲内の画素は255(白)、範囲外の画素は0(黒)になります。
cv2.bitwise_and(画像1, 画像2, mask=マスク画像)
は、画像1と画像2に対してビット単位のAND演算を行います。mask
引数を指定すると、マスク画像で値が0(黒)の箇所の結果は強制的に0になります。これにより、マスク画像の白い部分に対応する元の画像の画素だけを抽出したり、残したりすることができます。ここでは img_color
と img_color
自身に対してAND演算を行い、マスクを使って青色部分だけを残しています。
4.2. ヒストグラムの計算と表示
画像のヒストグラムは、画像全体の明るさや色の分布を示すグラフです。各輝度値(0〜255)または各色の値が画像中にどれだけ存在するかを示します。ヒストグラムは、画像のコントラスト調整や二値化処理、あるいは画像の比較などに利用されます。
ヒストグラムを計算するには cv2.calcHist()
関数を使います。
“`python
img がグレースケール画像として読み込まれていると仮定
if img is not None:
# グレースケール画像のヒストグラムを計算
# 第1引数: 入力画像 (リストで渡す必要がある)
# 第2引数: ヒストグラムを計算するチャンネルのインデックス (グレースケールは [0])
# 第3引数: マスク画像 (計算対象領域を指定する場合 None)
# 第4引数: ヒストグラムのビン数 (例: 256レベルなので [256])
# 第5引数: 輝度値の範囲 (例: 0から255までなので [0, 256]) – 256は含まれない終端値
hist = cv2.calcHist([img], [0], None, [256], [0, 256])
# ヒストグラムを表示するために Matplotlib を使うことが多い
import matplotlib.pyplot as plt
plt.figure() # 新しいフィギュアを作成
plt.title("Grayscale Histogram") # タイトルを設定
plt.xlabel("Bins") # x軸ラベル
plt.ylabel("# of Pixels") # y軸ラベル
plt.plot(hist) # ヒストグラムをプロット
plt.xlim([0, 256]) # x軸の表示範囲を設定 (0から255)
plt.grid(True) # グリッドを表示
plt.show() # グラフを表示
print("グレースケール画像のヒストグラムを計算・表示しました。")
カラー画像のヒストグラム
img_color がカラー画像として読み込まれていると仮定
if ‘img_color’ in locals() and img_color is not None: # img_color が定義され、かつ None でないか確認
# カラー画像のヒストグラムをチャンネルごとに計算
# B, G, R の各チャンネルに対して計算を行う
color = (‘b’,’g’,’r’) # 各チャンネルの色を指定 (BGRの順)
plt.figure()
plt.title(“Color Histogram”)
plt.xlabel(“Bins”)
plt.ylabel(“# of Pixels”)
for i, col in enumerate(color):
# 各チャンネル (i番目) のヒストグラムを計算
histr = cv2.calcHist([img_color],[i],None,[256],[0,256])
# そのチャンネルの色でヒストグラムをプロット
plt.plot(histr,color = col)
plt.xlim([0,256])
plt.grid(True)
plt.show()
print("カラー画像のヒストグラムをチャンネルごとに計算・表示しました。")
“`
cv2.calcHist()
関数は引数が少し複雑ですが、慣れれば大丈夫です。
* [img]
: 入力画像。リスト形式で渡します。複数画像を指定することも可能ですが、通常は1つです。
* [0]
(グレースケールの場合)、[i]
(カラーの場合): ヒストグラムを計算するチャンネルのインデックスをリストで指定します。グレースケールはチャンネルが1つなので [0]
、カラー(BGR)の場合はB=[0]
、G=[1]
、R=[2]
となります。
* None
: マスク画像。画像全体を対象とする場合は None
を指定します。画像の特定領域のヒストグラムを計算したい場合は、その領域を示すマスク画像を渡します。
* [256]
: ヒストグラムのビン数(bars in the histogram)。輝度値の範囲(0-255)をいくつの区間に分割するかを指定します。通常は256です。リストで指定します。
* [0, 256]
: 輝度値の範囲(range)。ヒストグラムを計算する画素値の下限と上限を指定します。0から255の範囲を計算したい場合は [0, 256]
と指定します(上限値は含まれません)。リストで指定します。
戻り値 hist
は、各ビンに属する画素の数を示すNumPy配列です。これを matplotlib
を使ってグラフ化することで、ヒストグラムを視覚的に確認できます。
4.3. ヒストグラム平坦化
画像のヒストグラムを平坦化すると、輝度値の分布が均一になり、コントラストが強調されます。特に全体的に暗かったり明るかったりする画像の視認性を向上させるのに有効です。これには cv2.equalizeHist()
関数を使います。
“`python
img_gray がグレースケール画像として読み込まれていると仮定
if img_gray is not None:
# グレースケール画像に対してヒストグラム平坦化を実行
# cv2.equalizeHist() はグレースケール画像に対してのみ適用できます
img_equalized = cv2.equalizeHist(img_gray)
cv2.imshow('Original Grayscale', img_gray)
cv2.imshow('Equalized Histogram', img_equalized)
print("ヒストグラム平坦化を実行しました。コントラストの変化を確認してください。")
cv2.waitKey(0)
cv2.destroyAllWindows()
# 平坦化後の画像のヒストグラムも計算して確認してみる
hist_equalized = cv2.calcHist([img_equalized], [0], None, [256], [0, 256])
import matplotlib.pyplot as plt
plt.figure()
plt.title("Equalized Grayscale Histogram")
plt.xlabel("Bins")
plt.ylabel("# of Pixels")
plt.plot(hist_equalized)
plt.xlim([0, 256])
plt.grid(True)
plt.show()
print("平坦化後のヒストグラムも表示しました。")
“`
cv2.equalizeHist(入力グレースケール画像)
は、入力画像をヒストグラム平坦化し、結果の画像(グレースケール)を返します。この処理はグレースケール画像に対してのみ行うことができるため、カラー画像に対して適用したい場合は、一度グレースケールに変換するか、あるいは各チャンネルを個別に平坦化して結合するなどの工夫が必要です(後者は色が不自然になることが多いです)。
5. フィルタリング:画像をぼかしたり、強調したり
フィルタリングは、画像の各画素値を、その周辺の画素値を使って計算し直すことで、画像に様々な効果を施す処理です。ノイズ除去、画像の平滑化(ぼかし)、輪郭検出、シャープ化など、非常に多岐にわたる処理に利用されます。
フィルタリングは通常、「カーネル」(または「フィルタ」)と呼ばれる小さな行列を使って、画像全体に「畳み込み演算」を行うことで実現されます。カーネルの中心を画像上の各画素に合わせ、カーネルの要素と画像上の対応する画素値を掛け合わせ、その合計値をカーネルの中心が合わさっていた画素の新しい値とする、というのが畳み込み演算の基本的な考え方です。
5.1. 平滑化フィルタ(ブラー)
画像を平滑化すると、細かいノイズを取り除いたり、画像をぼかしたりする効果があります。これは、各画素値をその周辺画素の平均値に置き換えることで実現されます。
平均フィルタ: cv2.blur()
または cv2.boxFilter()
最も単純な平滑化フィルタは、指定したカーネルサイズ(例えば3×3)の範囲内の画素値の平均を計算し、中心画素の新しい値とするものです。
“`python
img が読み込まれていると仮定
if img is not None:
# 平均フィルタを適用
# 第1引数: 入力画像
# 第2引数: カーネルサイズ (幅, 高さ) のタプル。例: (5, 5) で 5×5 のカーネル
img_blurred_avg = cv2.blur(img, (5, 5))
cv2.imshow('Original Image', img)
cv2.imshow('Blurred (Average)', img_blurred_avg)
print("平均フィルタを適用しました。画像がぼけていることを確認してください。")
cv2.waitKey(0)
cv2.destroyAllWindows()
# cv2.boxFilter() も同様の機能を提供しますが、より詳細な設定が可能です
# 例: 正規化しない (normalize=False)
# img_blurred_box = cv2.boxFilter(img, -1, (5,5), normalize=False)
“`
cv2.blur(画像データ, カーネルサイズ)
で平均フィルタを適用できます。カーネルサイズが大きいほど、強くぼかされます。
ガウシアンフィルタ: cv2.GaussianBlur()
平均フィルタよりも自然なぼかし効果が得られるのがガウシアンフィルタです。これは、カーネルの中心に近い画素ほど大きな重みをつけ、遠い画素ほど小さな重みをつけて平均を計算するフィルタです。重みはガウス分布(正規分布)に従って決定されます。
“`python
img が読み込まれていると仮定
if img is not None:
# ガウシアンフィルタを適用
# 第1引数: 入力画像
# 第2引数: カーネルサイズ (幅, 高さ) のタプル。幅と高さは正の奇数である必要があります
# 第3引数: x方向の標準偏差 (sigmaX)。0を指定するとカーネルサイズから自動計算
# 第4引数 (省略可): y方向の標準偏差 (sigmaY)。0を指定するとsigmaXと同じになる
img_blurred_gaussian = cv2.GaussianBlur(img, (5, 5), 0) # カーネルサイズ 5×5, sigmaX=0
cv2.imshow('Original Image', img)
cv2.imshow('Blurred (Gaussian)', img_blurred_gaussian)
print("ガウシアンフィルタを適用しました。平均フィルタよりも滑らかなぼかしを確認してください。")
cv2.waitKey(0)
cv2.destroyAllWindows()
# sigmaX を大きくすると、カーネルサイズが同じでもより強くぼかされます
# img_blurred_gaussian_large_sigma = cv2.GaussianBlur(img, (5, 5), 10)
# cv2.imshow('Blurred (Gaussian, large sigma)', img_blurred_gaussian_large_sigma)
# cv2.waitKey(0)
# cv2.destroyAllWindows()
“`
cv2.GaussianBlur(画像データ, カーネルサイズ, sigmaX, [sigmaY])
でガウシアンフィルタを適用します。カーネルサイズは必ず正の奇数にする必要があります。sigmaX
(x方向の標準偏差) はぼかしの強さを調整します。通常 0
を指定してカーネルサイズから自動計算させますが、明示的に指定することも可能です。sigmaY
は sigmaX
と同じにすることが多いです。
メディアンフィルタ: cv2.medianBlur()
メディアンフィルタは、カーネル内の画素値の中央値(メディアン)を計算し、中心画素の新しい値とするフィルタです。これは特に、「ソルト&ペッパーノイズ」(画像にランダムに白い点や黒い点が混じるノイズ)の除去に非常に効果的です。
“`python
img が読み込まれていると仮定
if img is not None:
# メディアンフィルタを適用
# 第1引数: 入力画像
# 第2引数: カーネルサイズ (正の奇数である必要があります)
# メディアンフィルタはカーネルが正方形である必要があり、サイズは単一の数値で指定します
img_blurred_median = cv2.medianBlur(img, 5) # カーネルサイズ 5×5
cv2.imshow('Original Image', img)
cv2.imshow('Blurred (Median)', img_blurred_median)
print("メディアンフィルタを適用しました。特にノイズの除去効果を確認してください。")
cv2.waitKey(0)
cv2.destroyAllWindows()
# ソルト&ペッパーノイズが乗った画像で試すと効果がよく分かります
# (ノイズ画像生成コードはここでは省略)
“`
cv2.medianBlur(画像データ, カーネルサイズ)
でメディアンフィルタを適用します。カーネルサイズは正の奇数で指定します(例: 3, 5, 7…)。ガウシアンフィルタや平均フィルタと異なり、メディアンフィルタは画像の境界線を比較的維持したままノイズを除去できる傾向があります。
5.2. モルフォロジー変換
モルフォロジー変換は、画像の形状に基づいた処理で、特に二値画像(白と黒の画像)のノイズ除去や、物体の輪郭を抽出・分離したりするのに使われます。基本的な操作は「膨張 (Dilation)」と「収縮 (Erosion)」です。これらの基本操作を組み合わせることで、「オープニング (Opening)」や「クロージング (Closing)」といった応用的な変換を行います。
モルフォロジー変換には、「構造要素(Structuring Element)」と呼ばれるカーネルのようなものが必要です。これは cv2.getStructuringElement()
関数で生成できます。
“`python
img が二値画像(例: グレースケール画像を閾値処理したもの)として読み込まれていると仮定
二値化処理の例:
if img is not None:
# まず画像をグレースケールに変換
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) if len(img.shape) == 3 else img.copy()
# 閾値処理で二値画像に変換 (例: 127より大きい値を255に、それ以下を0にする)
ret, img_binary = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY)
cv2.imshow('Binary Image', img_binary)
print("二値画像を作成しました。")
cv2.waitKey(0)
# cv2.destroyAllWindows() # まだ閉じない
# 構造要素を作成
# cv2.getStructuringElement(形状, サイズ, [アンカー位置])
# 形状: cv2.MORPH_RECT (矩形), cv2.MORPH_ELLIPSE (楕円), cv2.MORPH_CROSS (十字)
# サイズ: カーネルサイズ (幅, 高さ) のタプル
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5)) # 5x5の矩形構造要素
# 膨張 (Dilation)
# 画像の白い領域が構造要素の形に沿って膨らみます。ノイズ(小さな黒い点)を除去したり、欠けた部分を繋げたりする効果があります。
img_dilated = cv2.dilate(img_binary, kernel, iterations=1) # iterations は膨張を繰り返す回数
# 収縮 (Erosion)
# 画像の白い領域が構造要素の形に沿って縮小します。ノイズ(小さな白い点)を除去したり、オブジェクトを分離したりする効果があります。
img_eroded = cv2.erode(img_binary, kernel, iterations=1)
cv2.imshow('Dilated Image', img_dilated)
cv2.imshow('Eroded Image', img_eroded)
print("膨張と収縮を実行しました。結果を確認してください。")
cv2.waitKey(0) # ここで待機して膨張・収縮結果を確認
# オープニング (Opening)
# 収縮 -> 膨張 の順に行う処理。小さなノイズを除去し、オブジェクトの形状を維持します。
# cv2.morphologyEx(画像データ, 演算タイプ, 構造要素, [iterations])
# 演算タイプ: cv2.MORPH_OPEN
img_opening = cv2.morphologyEx(img_binary, cv2.MORPH_OPEN, kernel)
# クロージング (Closing)
# 膨張 -> 収縮 の順に行う処理。オブジェクト内の穴を埋めたり、近くにあるオブジェクトを繋げたりする効果があります。
# 演算タイプ: cv2.MORPH_CLOSE
img_closing = cv2.morphologyEx(img_binary, cv2.MORPH_CLOSE, kernel)
cv2.imshow('Opening (Erosion then Dilation)', img_opening)
cv2.imshow('Closing (Dilation then Erosion)', img_closing)
print("オープニングとクロージングを実行しました。結果を確認してください。")
cv2.waitKey(0) # ここで待機してオープニング・クロージング結果を確認
cv2.destroyAllWindows()
``
cv2.threshold()
**閾値処理 (cv2.threshold)**
モルフォロジー変換は主に二値画像に対して行われるため、まず関数を使ってグレースケール画像を二値画像に変換する例を挟みました。
cv2.threshold(入力グレースケール画像, 閾値, 閾値を超えた画素に設定する値, 閾値処理タイプ)を使います。
cv2.THRESH_BINARY` は基本的な二値化で、入力画素値が閾値より大きければ指定値(ここでは255)、そうでなければ0にします。戻り値の最初の要素は実際に使用された閾値(自動計算される閾値タイプの場合)、2番目の要素が二値画像です。
構造要素 (cv2.getStructuringElement)
モルフォロジー変換のカーネルのようなものです。形状(矩形、楕円、十字)とサイズを指定して作成します。
膨張 (cv2.dilate)
入力画像を構造要素で膨張させます。iterations
で繰り返し回数を指定できます。
収縮 (cv2.erode)
入力画像を構造要素で収縮させます。こちらも iterations
で繰り返し回数を指定できます。
オープニング・クロージング (cv2.morphologyEx)
cv2.morphologyEx()
は、膨張や収縮といった基本的なモルフォロジー演算を組み合わせた高度な演算を実行するための関数です。
* cv2.MORPH_OPEN
: 収縮の後に膨張を行います。孤立した小さな白い点ノイズを除去するのに役立ちます。
* cv2.MORPH_CLOSE
: 膨張の後に収縮を行います。オブジェクト内部の小さな黒い穴を埋めるのに役立ちます。
これらのモルフォロジー変換は、画像内の特定の形状を強調したり、不要な要素を取り除いたりするのに非常に有効です。
6. 特徴点検出と記述
画像中の「特徴点」とは、その点の周辺領域が周囲と比べて際立っており、回転や拡大縮小、照明の変化などがあっても比較的同じ点だと認識しやすい画素のことです。コーナーやエッジ、テクスチャのある領域などが特徴点になりやすいです。特徴点を検出し、その周辺領域を数値的に表現する(記述する)技術は、物体認識、画像のマッチング、パノラマ画像の合成、三次元復元など、多くのコンピュータービジョン応用において基盤となります。
6.1. エッジ検出
エッジ(輪郭)は、画素値が急激に変化する箇所です。物体の境界線などを表すため、重要な画像特徴の一つです。OpenCVにはいくつかのエッジ検出フィルタがあります。
Sobelフィルタ: cv2.Sobel()
Sobelフィルタは、画像の各画素における輝度値の勾配(変化率)を計算することでエッジを検出します。x方向とy方向の勾配を別々に計算し、それらを組み合わせてエッジの強さや方向を求めます。
“`python
img がグレースケール画像として読み込まれていると仮定
if img is not None:
# まずグレースケール画像であることを確認
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) if len(img.shape) == 3 else img.copy()
# Sobelフィルタを適用
# cv2.Sobel(入力画像, 出力画像のデータ型, x方向の階数dx, y方向の階数dy, [カーネルサイズ ksize])
# 出力データ型には、計算途中で負の値が出ても収まるように cv2.CV_64F などを指定することが多い
# dx=1, dy=0 でx方向の勾配、dx=0, dy=1 でy方向の勾配を計算
sobelx = cv2.Sobel(img_gray, cv2.CV_64F, 1, 0, ksize=5) # x方向の勾配
sobely = cv2.Sobel(img_gray, cv2.CV_64F, 0, 1, ksize=5) # y方向の勾配
# 勾配の絶対値を取る(負の値もエッジなので絶対値が必要)
abs_sobelx = cv2.convertScaleAbs(sobelx) # 絶対値を取って uint8型に変換
abs_sobely = cv2.convertScaleAbs(sobely)
# x方向とy方向の勾配を組み合わせてエッジ画像を生成
# cv2.addWeighted(画像1, 重み1, 画像2, 重み2, バイアス) で画像を線形結合
sobel_combined = cv2.addWeighted(abs_sobelx, 0.5, abs_sobely, 0.5, 0) # 単純な合計やcv2.addでも良い
cv2.imshow('Original Grayscale', img_gray)
cv2.imshow('Sobel X', abs_sobelx)
cv2.imshow('Sobel Y', abs_sobely)
cv2.imshow('Sobel Combined', sobel_combined)
print("Sobelフィルタによるエッジ検出を実行しました。")
cv2.waitKey(0)
cv2.destroyAllWindows()
“`
cv2.Sobel()
は、指定した方向(dx, dy)の輝度勾配を計算します。結果は負の値も含む可能性があるため、出力データ型には cv2.CV_64F
など浮動小数点型を指定し、その後 cv2.convertScaleAbs()
で絶対値を取り、表示可能な uint8
型に変換するのが一般的です。x方向とy方向の結果を組み合わせることで、より明確なエッジ画像を生成できます。
Cannyエッジ検出: cv2.Canny()
Cannyエッジ検出は、Sobelフィルタよりも高度なエッジ検出アルゴリズムです。ノイズ除去、勾配計算、非最大値抑制、ヒステリシス閾値処理という複数のステップを経て、細く滑らかなエッジを検出します。画像処理で最も広く使われているエッジ検出手法の一つです。
“`python
img がグレースケール画像として読み込まれていると仮定
if img is not None:
# まずグレースケール画像であることを確認
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) if len(img.shape) == 3 else img.copy()
# Cannyエッジ検出を適用
# cv2.Canny(入力画像, 下位閾値 threshold1, 上位閾値 threshold2, [アパーチャサイズ apertureSize], [L2勾配フラグ L2gradient])
# 下位閾値と上位閾値が重要です。
# 勾配の強さが上位閾値より大きいエッジは「強いエッジ」とみなされます。
# 勾配の強さが下位閾値より小さいエッジは無視されます。
# 勾配の強さが下位閾値と上位閾値の間にあるエッジは、強いエッジに連結している場合にのみエッジとみなされます(ヒステリシス)。
edges = cv2.Canny(img_gray, 100, 200) # 例: 下位閾値100, 上位閾値200
cv2.imshow('Original Grayscale', img_gray)
cv2.imshow('Canny Edges', edges)
print("Cannyエッジ検出を実行しました。")
print("閾値を変えると検出されるエッジが変わります。")
cv2.waitKey(0)
cv2.destroyAllWindows()
“`
cv2.Canny()
の最も重要なパラメータは、threshold1
と threshold2
の2つの閾値です。これらの値を調整することで、検出されるエッジの量を制御できます。通常、threshold1
は threshold2
の半分から3分の1程度の値に設定されます。apertureSize
はSobelフィルタのカーネルサイズに対応し、デフォルトは3です。
Cannyエッジ検出は、様々な応用において、物体の輪郭を抽出する前処理として非常によく利用されます。
6.2. コーナー検出
コーナーは、画像中の2つのエッジが交わる点であり、エッジよりもさらに特定しやすい特徴点となります。コーナー検出も、画像のマッチングや追跡などに利用されます。
Harrisコーナー検出: cv2.cornerHarris()
Harrisコーナー検出は、画像中の各点で、小さなウィンドウをあらゆる方向に移動させたときに、輝度値に大きな変化が見られる点(=コーナー)を検出するアルゴリズムです。
“`python
img がグレースケール画像として読み込まれていると仮定
if img is not None:
# まずグレースケール画像であることを確認
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) if len(img.shape) == 3 else img.copy()
# データ型を float32 に変換 (Harris検出の入力要件)
img_gray = np.float32(img_gray)
# Harrisコーナー検出を実行
# cv2.cornerHarris(入力画像, ブロックサイズ blockSize, Sobelのアパーチャサイズ ksize, Harris検出のパラメータ k)
# blockSize: コーナー検出に使用する近傍領域のサイズ(ウィンドウサイズ)
# ksize: Sobel微分フィルタのアパーチャサイズ
# k: Harris検出器の自由パラメータ(通常0.04〜0.06の範囲)
dst = cv2.cornerHarris(img_gray, 2, 3, 0.04)
# 検出結果を膨張させることで、コーナー点を強調して見やすくする (オプション)
# dst = cv2.dilate(dst, None)
# 検出されたコーナー点を元の画像に描画
# 検出結果 dst の値が大きいほどコーナーである可能性が高い
# ここでは、閾値 (例: dstの最大値の1%) より大きい箇所をコーナーとみなし、赤丸を描画
# 元画像 (カラー) をコピーして描画すると見やすい
img_corners = img.copy() if len(img.shape) == 3 else cv2.cvtColor(img_gray.astype(np.uint8), cv2.COLOR_GRAY2BGR) # 描画用にBGR画像を用意
# dst >= 0.01 * dst.max() は条件を満たす画素のインデックスのタプルを返す
# np.where(条件) は条件を満たす要素のインデックスを取得
# y, x = np.where(dst >= 0.01 * dst.max()) # y座標とx座標が別々の配列で返る
# 検出されたコーナー点に円を描画
# しきい値処理して画素値を0または255にする方法でも表示可能
# img_corners[dst > 0.01 * dst.max()] = [0, 0, 255] # 赤色 [B,G,R] でマーク (NumPyインデックスで直接書き込み)
# または、検出結果を正規化して表示することもできる
# dst_norm = cv2.normalize(dst, None, 0, 255, cv2.NORM_MINMAX, cv2.CV_8U)
# cv2.imshow('Harris Response', dst_norm)
# ここでは、thresholding で二値化して表示
# dst の値が大きいほどコーナーらしい
threshold = 0.01 * dst.max()
corner_img = np.zeros_like(img_gray, dtype=np.uint8)
corner_img[dst > threshold] = 255 # 閾値より大きい場所を白にする
cv2.imshow('Original Image', img) # 元画像 (カラーまたはグレースケール)
cv2.imshow('Harris Corners (Thresholded)', corner_img) # 検出されたコーナー (白点で表示)
print("Harrisコーナー検出を実行しました。")
print("検出されたコーナーは白点で表示されます。")
cv2.waitKey(0)
cv2.destroyAllWindows()
“`
cv2.cornerHarris()
は、コーナーらしさを示すスコア画像を返します。このスコア画像に対して閾値処理を行うことで、コーナー点を抽出できます。結果の dst
配列は浮動小数点型 (float32
) です。表示や閾値処理のためには、適切なスケーリングや型変換が必要です。
Shi-Tomasiコーナー検出: cv2.goodFeaturesToTrack()
Shi-Tomasiのコーナー検出器は、Harris検出器を改良したもので、より良い(追跡しやすい)コーナーを検出するとされています。cv2.goodFeaturesToTrack()
関数を使うと、自動的に最も「良い」コーナー点を指定した数だけ検出してくれます。
“`python
img がグレースケール画像として読み込まれていると仮定
if img is not None:
# まずグレースケール画像であることを確認
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) if len(img.shape) == 3 else img.copy()
# Shi-Tomasi コーナー検出 (良い特徴点を見つける)
# cv2.goodFeaturesToTrack(入力グレースケール画像, 検出するコーナーの最大数 maxCorners,
# コーナーの品質レベル qualityLevel, コーナー間の最小距離 minDistance)
# qualityLevel: コーナーの最小品質。最大品質 (Harrisスコアの最大値) に対する割合 (0から1の間)。
# minDistance: 検出されたコーナー間の最小ユークリッド距離。これより近いコーナーは排除される。
corners = cv2.goodFeaturesToTrack(img_gray, maxCorners=100, qualityLevel=0.01, minDistance=10)
# 検出されたコーナー点を元の画像に描画
img_corners = img.copy() if len(img.shape) == 3 else cv2.cvtColor(img_gray, cv2.COLOR_GRAY2BGR) # 描画用にBGR画像を用意
# 検出されたコーナーは NumPy 配列で返される (形状は (コーナー数, 1, 2))
if corners is not None:
for corner in corners:
x, y = corner[0] # コーナー座標は [x, y] の形式
# 検出された各コーナーに円を描画
cv2.circle(img_corners, (int(x), int(y)), 5, (0, 0, 255), -1) # 中心座標 (x,y), 半径 5, 色 (赤), 塗りつぶし
cv2.imshow('Original Image', img)
cv2.imshow('Shi-Tomasi Corners', img_corners)
print(f"{len(corners)} 個のShi-Tomasiコーナーを検出しました。")
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print("コーナーは検出されませんでした。")
“`
cv2.goodFeaturesToTrack()
は、指定したパラメータに基づいて、画像中で最も「良い」コーナー点の座標をNumPy配列で返します。検出されたコーナー点は、画像上に円などを描画して可視化するのが一般的です。
6.3. 特徴点検出器と記述子 (SIFT, SURF, ORB, AKAZEなど)
HarrisやShi-Tomasiはコーナー点の座標を検出しますが、その点の周辺領域の特徴を数値的に表現する「記述子」を計算する機能はありません。SIFT (Scale-Invariant Feature Transform) や SURF (Speeded Up Robust Features) といったアルゴリズムは、特徴点の検出とその記述子の計算を同時に行うことができます。これらの記述子は、画像の拡大縮小、回転、照明変化、ある程度の視点変化に対しても頑健であり、異なる画像間で同じ物体や場所に対応する特徴点をマッチングさせるのに非常に強力です。
SIFTとSURFは特許の問題があり、OpenCVのcontribモジュール(opencv-contrib-python
をインストールした場合)に含まれていることがあります。商用利用には注意が必要ですが、学習目的では利用されることが多いです。OpenCVには、特許フリーのORB (Oriented FAST and Rotated BRIEF) や AKAZE (Accelerated-KAZe) といった代替アルゴリズムも用意されています。
ここでは、比較的新しく、高性能で特許フリーの AKAZE 特徴点検出器と記述子を例に基本的な使い方を見てみましょう。
“`python
2つの画像間で特徴点をマッチングさせる例
img1 と img2 は比較したい画像として読み込まれていると仮定
AKAZE検出器を作成
akaze = cv2.AKAZE_create()
各画像から特徴点 (Keypoints) と記述子 (Descriptors) を検出・計算
kp は検出された特徴点のリスト
des は各特徴点に対応する記述子 (NumPy配列)
kp1, des1 = akaze.detectAndCompute(img1, None)
kp2, des2 = akaze.detectAndCompute(img2, None)
print(f”画像1で {len(kp1)} 個の特徴点を検出”)
print(f”画像2で {len(kp2)} 個の特徴点を検出”)
特徴点を画像に描画して確認
img_kp1 = cv2.drawKeypoints(img1, kp1, None, flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
img_kp2 = cv2.drawKeypoints(img2, kp2, None, flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
cv2.imshow(‘Keypoints in Image 1’, img_kp1)
cv2.imshow(‘Keypoints in Image 2’, img_kp2)
print(“検出された特徴点を表示しました。丸は特徴点の位置とスケール、線は向きを表します。”)
cv2.waitKey(0)
cv2.destroyAllWindows()
特徴量のマッチング
Brute-Force Matcher (BFMatcher) または FLANN Matcher を使用
BFMatcher は総当たりで最も近い記述子を探します
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True) # AKAZEの記述子はバイナリ記述子なので NORM_HAMMING を使う
マッチングを実行
des1の各記述子に対して、des2の中で最も類似度の高い記述子を見つける
crossCheck=True を指定すると、相互に最も近いペアのみをマッチング結果とします
matches = bf.match(des1, des2)
良いマッチング結果だけをソートまたはフィルタリング (オプション)
例: 距離が小さい(類似度が高い)順にソート
matches = sorted(matches, key = lambda x:x.distance)
マッチング結果を描画 (上位 N 件など)
cv2.drawMatches() または cv2.drawMatchesKnn()
drawMatches: 最も良いマッチング結果 N 件を描画
drawMatchesKnn: k個の最も近いマッチングを描画し、比率テストなどでフィルタリングすることが多い
img_matches = cv2.drawMatches(img1, kp1, img2, kp2, matches[:50], None, flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS) # 上位50件を描画
cv2.imshow(‘AKAZE Feature Matching’, img_matches)
print(f”検出された特徴点のうち、上位 {min(50, len(matches))} 件のマッチング結果を表示しました。”)
print(“2つの画像の間を結ぶ線がマッチングしている特徴点のペアを示します。”)
cv2.waitKey(0)
cv2.destroyAllWindows()
“`
特徴点検出とマッチングの基本的な流れは以下のようになります。
1. 特徴点検出器/記述子を生成: cv2.AKAZE_create()
, cv2.ORB_create()
, cv2.SIFT_create()
(contribの場合) など
2. 特徴点検出と記述子計算: detector.detectAndCompute(画像, マスク)
を呼び出します。
* kp
: 検出された特徴点のリスト。各要素は cv2.KeyPoint
オブジェクトで、位置、スケール、向きなどの情報を含みます。
* des
: 各特徴点に対応する記述子。通常NumPy配列です。
3. マッチング器を生成: cv2.BFMatcher()
(総当たりマッチング) または cv2.FlannBasedMatcher()
(FLANNによる高速マッチング) を使います。記述子のタイプに合わせて距離計算方法(cv2.NORM_HAMMING
や cv2.NORM_L2
など)を指定します。
4. マッチングを実行: matcher.match(記述子1, 記述子2)
または matcher.knnMatch(記述子1, 記述子2, k=k)
を呼び出します。
5. 結果のフィルタリング/ソート: マッチング結果(cv2.DMatch
オブジェクトのリスト)を距離などでフィルタリングまたはソートします。
6. 結果の描画: cv2.drawMatches()
や cv2.drawMatchesKnn()
を使って、元の画像とマッチング結果を可視化します。
特徴点のマッチングは、異なる画像間での位置合わせ(画像合成)、物体認識、画像検索など、様々な高度な応用で使われます。
7. 物体検出と顔検出
画像中に特定の物体(例えば顔、車、猫など)がどこにあるかを検出する技術は、顔認識システム、自動運転、防犯カメラ映像解析など、多くの実用的な応用に使われています。OpenCVは、古くから使われている手法である「カスケード分類器」を使った物体検出機能を提供しています。特に顔検出に広く利用されています。
7.1. カスケード分類器による顔検出
カスケード分類器は、Haar-like特徴やLBP特徴といった局所的な特徴を使って、画像から学習済みの物体(例: 顔)を検出する手法です。OpenCVは、様々な物体(顔、目、上半身、車など)に対して事前に学習されたカスケード分類器のXMLファイルを提供しています。
“`python
img_color がカラー画像として読み込まれていると仮定
if img_color is not None:
# まずグレースケール画像に変換 (カスケード分類器は通常グレースケール画像を必要とする)
img_gray = cv2.cvtColor(img_color, cv2.COLOR_BGR2GRAY)
# カスケード分類器を読み込む
# OpenCVが提供するXMLファイルは opencv/data/haarcascades ディレクトリにある
# 例: 正面顔検出器
# ファイルパスはOpenCVのインストール場所によって異なるので、適切なパスを指定してください
# Anaconda環境の場合: anaconda3/envs/your_env/lib/pythonX.X/site-packages/cv2/data/haarcascade_frontalface_default.xml
# または、GitHubからファイルをダウンロードしてローカルに置く: https://github.com/opencv/opencv/tree/master/data/haarcascades
cascade_path = cv2.data.haarcascades + 'haarcascade_frontalface_default.xml'
face_cascade = cv2.CascadeClassifier(cascade_path)
# 分類器が正しく読み込まれたか確認
if face_cascade.empty():
print(f"エラー: カスケードファイル '{cascade_path}' が読み込めません。パスを確認してください。")
else:
# 顔を検出
# detectMultiScale(画像, 縮小スケール factor, 最小隣接数 minNeighbors, [最小サイズ minSize], [最大サイズ maxSize])
# scaleFactor: 検出ウィンドウをどれだけ縮小しながらスキャンするか。1.1は10%ずつ小さくしていく。
# minNeighbors: 有効な検出とみなすために、候補矩形が持つべき最小の隣接数。値が大きいほど偽陽性が減るが、真陽性も見逃しやすくなる。
# minSize, maxSize: 検出する物体の最小・最大サイズ (幅, 高さ) のタプル。
faces = face_cascade.detectMultiScale(img_gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))
# 検出された顔の数を出力
print(f"{len(faces)} 個の顔を検出しました。")
# 検出された顔に矩形を描画
# faces は検出された各顔の矩形情報を含むリスト [x, y, w, h] (左上のx,y座標と幅w, 高さh)
img_faces = img_color.copy() # 描画用に元画像をコピー
for (x, y, w, h) in faces:
# 矩形を描画 cv2.rectangle(画像, 左上座標, 右下座標, 色, 太さ)
cv2.rectangle(img_faces, (x, y), (x+w, y+h), (255, 0, 0), 2) # 青色の矩形を太さ2で描画
cv2.imshow('Original Image', img_color)
cv2.imshow('Detected Faces', img_faces)
print("検出された顔に矩形を描画しました。結果を確認してください。")
cv2.waitKey(0)
cv2.destroyAllWindows()
“`
カスケード分類器を使った物体検出の基本的な流れは以下のようになります。
1. カスケード分類器ファイルを読み込む: cv2.CascadeClassifier(xmlファイルパス)
2. 検出対象の画像をグレースケールに変換: カスケード分類器は通常グレースケール画像を入力とします。
3. detectMultiScale()
を呼び出して物体を検出: 画像内を様々なサイズでスキャンし、検出候補を返します。scaleFactor
と minNeighbors
パラメータの調整が検出精度に影響します。
4. 検出結果の取得: detectMultiScale
は、検出された各物体の位置とサイズを示す矩形情報のリスト [x, y, w, h]
を返します。
5. 結果の表示: 検出された矩形を元の画像に描画します。
このカスケード分類器は比較的単純で高速な検出が可能ですが、角度や照明の変化に弱かったり、誤検出(偽陽性)や見逃し(偽陰性)が発生しやすいという側面もあります。近年では、より高精度な深層学習ベースの物体検出手法(SSD, YOLO, Faster R-CNNなど)が主流になっていますが、カスケード分類器は手軽に試せる有用な手法です。
8. 動画処理:動く画像への挑戦
ここまで画像(静止画)に対する処理を見てきましたが、OpenCVは動画の読み込み、表示、保存、そしてウェブカメラからの映像ストリームの処理にも対応しています。動画は連続するフレーム(静止画)の集まりなので、基本的な動画処理は、各フレームに対してこれまで学んだ画像処理を順番に適用していくことで実現できます。
8.1. 動画ファイルやウェブカメラからの入力
動画ファイルやウェブカメラからの映像を取得するには cv2.VideoCapture()
オブジェクトを使います。
“`python
import cv2
動画ファイルからの入力の場合
video_path = ‘input_video.mp4’
cap = cv2.VideoCapture(video_path)
ウェブカメラからの入力の場合 (通常デバイスIDは0から始まる)
cap = cv2.VideoCapture(0) # デフォルトのウェブカメラ
VideoCapture オブジェクトが正しく初期化されたか確認
if not cap.isOpened():
print(“エラー: 動画ファイルまたはウェブカメラを開けません。”)
exit()
フレームを1つずつ読み込み、処理・表示するループ
while True:
# フレームを読み込む
# ret は bool 型で、フレームが正しく読み込めたかどうかを示す (True/False)
# frame は読み込まれたフレーム(画像データ、NumPy配列)
ret, frame = cap.read()
# フレームが正しく読み込めなかった場合 (動画の終端など)
if not ret:
print("動画の終端に到達しました。またはフレームを読み込めませんでした。")
break # ループを終了
# --- ここに各フレームに対する画像処理を書く ---
# 例: フレームをグレースケールに変換
gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# -------------------------------------------
# 処理したフレームを表示
cv2.imshow('Video Frame (Grayscale)', gray_frame) # グレースケールフレームを表示
# キー入力を待ち、'q' キーが押されたらループを終了
# cv2.waitKey() の引数はフレーム間の表示間隔(ミリ秒)を制御します
# 例えば 30 fps の動画なら、1000ms / 30 ≈ 33ms を指定するとスムーズに見える
# ただし処理時間も考慮する必要がある
if cv2.waitKey(1) & 0xFF == ord('q'): # 1ms 待機して 'q' キーが押されたかチェック
break
キャプチャオブジェクトを解放
cap.release()
全てのウィンドウを閉じる
cv2.destroyAllWindows()
print(“動画処理を終了しました。”)
“`
cv2.VideoCapture(0)
はデフォルトのウェブカメラを、cv2.VideoCapture('動画ファイルパス')
は指定された動画ファイルを読み込むためのオブジェクトを作成します。
cap.read()
メソッドは、動画から次のフレームを1つ読み込みます。フレームが正常に読み込めた場合は ret
が True
となり、画像データが frame
に格納されます。動画の終端に達したり、読み込みに失敗したりした場合は ret
が False
になります。
while True:
ループの中で各フレームを読み込み、必要な画像処理を施し、cv2.imshow()
で表示するというのが動画処理の基本的な流れです。cv2.waitKey(1)
は、次のフレームに進む前に1ミリ秒待機し、その間にキー入力がないかチェックしています。ここで指定するミリ秒は、動画のフレームレートに合わせて調整すると、より滑らかな動画再生が実現できます。特に 'q'
キーが押されたらループを抜けるという処理は、動画再生を停止してプログラムを終了させるためによく使われます。& 0xFF == ord('q')
は、クロスプラットフォームでキー入力を正確に検出するための定型句です。
ループを抜ける際は、cap.release()
でキャプチャオブジェクトを解放し、cv2.destroyAllWindows()
でウィンドウを閉じます。
8.2. 動画の保存
処理した動画(あるいはウェブカメラからの映像)をファイルに保存するには cv2.VideoWriter()
オブジェクトを使います。
“`python
import cv2
import numpy as np
cap = cv2.VideoCapture(0) # ウェブカメラを開く (または動画ファイル)
if not cap.isOpened():
print(“エラー: カメラまたは動画を開けません。”)
exit()
保存する動画の設定を取得(元の動画と同じにするのが一般的)
フレーム幅
frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
フレーム高さ
frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
フレームレート (fps)
fps = cap.get(cv2.CAP_PROP_FPS)
コーデック(FourCC)
FourCCとは、動画や画像のデータ形式を指定する4文字のコードです
例: MJPG, XVID, DIVX, H264など
OSやインストールされているコーデックによって使用可能なものが異なります
Windowsでよく使われるもの: cv2.VideoWriter_fourcc(*’XVID’) または cv2.VideoWriter_fourcc(‘M’,’J’,’P’,’G’)
Linux/macOSでよく使われるもの: cv2.VideoWriter_fourcc(‘M’,’J’,’P’,’G’)
多くの環境で動作しやすい FourCC を選ぶ
fourcc = cv2.VideoWriter_fourcc(*’XVID’) # または ‘MJPG’
VideoWriter オブジェクトを作成
第1引数: 保存先のファイルパス
第2引数: コーデック (FourCC)
第3引数: フレームレート (fps)
第4引数: フレームサイズ (幅, 高さ) のタプル
第5引数 (省略可): isColor フラグ。Trueならカラー、Falseならグレースケール。デフォルトはTrue。
out = cv2.VideoWriter(‘output_video.avi’, fourcc, fps, (frame_width, frame_height))
保存したい動画がグレースケールの場合
out_gray = cv2.VideoWriter(‘output_gray.avi’, fourcc, fps, (frame_width, frame_height), isColor=False)
フレームを読み込み、処理・保存するループ
print(“動画の録画を開始します。’q’キーで停止します。”)
while cap.isOpened():
ret, frame = cap.read()
if not ret:
print(“フレームを読み込めませんでした。”)
break
# --- ここに各フレームに対する画像処理を書く ---
# 例: フレームをグレースケールに変換 (保存用)
gray_frame_for_save = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 表示用にはカラーのまま
# -------------------------------------------
# 処理したフレームを保存
# 保存する動画がカラーの場合
out.write(frame) # 元のカラーフレームを保存
# 保存する動画がグレースケールの場合
# グレースケールフレームを保存するには、VideoWriter作成時に isColor=False を指定し、
# 保存するフレームもグレースケール (shapeが (高さ, 幅)) である必要があります
# out_gray.write(gray_frame_for_save)
# 元のフレームを表示 (または処理後のフレームを表示)
cv2.imshow('Original Frame', frame)
# キー入力を待ち、'q' キーが押されたらループを終了
if cv2.waitKey(1) & 0xFF == ord('q'):
break
全てのオブジェクトを解放
cap.release()
out.release() # VideoWriter オブジェクトも解放が必要
out_gray.release() # グレースケール保存する場合
cv2.destroyAllWindows()
print(“動画の録画と処理を終了しました。”)
“`
動画を保存するには、まず cv2.VideoWriter()
オブジェクトを作成します。この際、保存先のファイル名、使用するコーデック(cv2.VideoWriter_fourcc()
で指定)、フレームレート、フレームサイズを指定する必要があります。コーデックは環境依存性が高いため、いくつかの候補を試す必要があるかもしれません。
cv2.VideoWriter
オブジェクトの write(フレーム)
メソッドを呼び出すことで、現在のフレームを動画ファイルに書き込むことができます。カラー動画として保存する場合は、write
に渡すフレームはカラー画像(チャンネル数3)である必要があり、VideoWriter作成時の isColor
フラグは True
(デフォルト) である必要があります。グレースケール動画として保存する場合は、isColor=False
を指定し、グレースケール画像(チャンネル数1)を write
に渡します。
ループが終了したら、VideoWriter
オブジェクトも release()
メソッドで解放することを忘れないでください。
9. 応用例とさらなる学習
OpenCVでできることは、これまで紹介した基本操作にとどまりません。ここでは、いくつかの応用例のヒントと、さらなる学習のための情報を提供します。
9.1. 簡単な応用例
- 顔にモザイクをかける:
カスケード分類器で顔を検出し、検出された矩形領域内の画素を、その領域を縮小・拡大した画像で置き換える(ピクセル化)ことでモザイク処理を実現できます。ウェブカメラ映像に適用すると、リアルタイム顔モザイク処理も可能です。 - 簡単な物体追跡:
動画中の特定の物体(例えば、色の範囲で指定した物体や、検出した特徴点)を、次のフレームでその物体がどこに移動したかを予測・検出することで追跡できます。OpenCVには、CAMShiftやMeanShiftといった追跡アルゴリズムも用意されています。 - 背景差分による動き検出:
静止している背景画像と現在のフレームとの差分を計算し、差分が大きい領域を「動きがあった領域」として検出することで、防犯カメラのような動き検出システムを構築できます。OpenCVには背景差分を行うためのクラス(cv2.createBackgroundSubtractorMOG2()
,cv2.createBackgroundSubtractorKNN()
など)があります。 - 輪郭検出:
cv2.findContours()
関数を使うと、画像中の物体の輪郭(閉じた曲線)を検出できます。検出された輪郭は、物体の形状解析や抽出に利用されます。これは特に二値画像に対して行われることが多いです。 - テンプレートマッチング:
小さな画像(テンプレート)が大きな画像中のどこにあるかを検出する手法です。cv2.matchTemplate()
関数を使います。特定のマークやアイコンが画像中に存在するかどうかを検出するのに利用できます。
9.2. 機械学習・深層学習との連携
最近のコンピュータービジョン分野の進展は、深層学習(ディープラーニング)の発展と密接に関わっています。OpenCVも、深層学習モデルを利用するための cv2.dnn
モジュールを提供しています。これにより、Caffe, TensorFlow, PyTorchなどのフレームワークで学習されたモデルをOpenCV内で実行し、高精度な物体検出(SSD, YOLOなど)、画像分類、セマンティックセグメンテーションといった処理を行うことができます。OpenCVを使った深層学習の利用は、応用範囲を大きく広げます。
9.3. さらなる学習のために
この記事はOpenCV-Pythonの基本的な部分に焦点を当てていますが、OpenCVには他にも多くの強力な機能があります。さらなる学習のために、以下のリソースを活用することをお勧めします。
- OpenCV公式ドキュメント:
OpenCVの機能に関する最も正確で詳細な情報源です。様々な関数の説明やチュートリアルが提供されています(英語が多いですが、非常に価値があります)。
https://docs.opencv.org/
Python用のチュートリアルも豊富です。
https://docs.opencv.org/4.x/d6/d00/tutorial_py_root.html - 書籍:
OpenCVに関する入門書や応用書が多数出版されています。自分のレベルや興味に合った書籍を見つけると体系的に学べます。 - オンラインコースやチュートリアル記事:
UdemyやCourseraといったプラットフォーム、あるいは個人のブログなどで、OpenCVに関する多くのコースや記事が見つかります。動画で学んだり、特定のトピックに絞って学んだりするのに便利です。 - GitHubなどのコードリポジトリ:
他の人が作成したOpenCVを使ったプロジェクトのコードを見ることで、実際の応用例や書き方を学ぶことができます。
画像・動画処理の世界は非常に広く奥深いですが、OpenCVを使えばその多くの扉を開くことができます。ぜひ、興味を持ったテーマについてさらに深く学んでみてください。
10. まとめ:OpenCV-Pythonの旅を終えて
この入門記事では、OpenCVとPythonを使って画像・動画処理を行うための基本的なステップをたどってきました。
- OpenCVライブラリのインストール方法
- 画像の読み込み、表示、保存といった基本操作
- 画像のサイズ変更や切り抜き
- 色空間の変換(BGRからHSVなど)や画素値の操作
- 平滑化、シャープ化、モルフォロジー変換といったフィルタリング処理
- エッジやコーナーといった特徴点の検出
- カスケード分類器を使った顔検出の基本
- 動画ファイルやウェブカメラからの入力、動画の保存方法
これらはOpenCVの機能のほんの一部にすぎませんが、これらの基本を理解することで、様々な画像・動画処理アプリケーションの基礎を築くことができます。
画像・動画処理は、単に画像を美しく見せるだけでなく、コンピューターに「画像の内容を理解させる」ための重要な技術です。今回学んだ内容を応用することで、あなた自身のアイデアに基づいた様々なシステムやツールを開発することが可能になります。
最初は難しく感じる部分もあったかもしれませんが、実際にコードを書いて、画像を処理し、その結果を目で確認することで、理解が深まったことと思います。エラーが出たり、思った通りの結果が得られなかったりすることもあるでしょう。しかし、それは学習プロセスの一部です。諦めずに試行錯誤を繰り返すことが、スキル習得の鍵となります。
この入門記事が、あなたのOpenCV-Pythonを使った画像・動画処理の世界への第一歩となり、さらなる探求のきっかけとなれば幸いです。
さあ、次はあなた自身のプロジェクトで、OpenCVの力を最大限に引き出してみてください!