Python OpenCV完全ガイド – 画像処理・コンピュータビジョンの決定版

 

OpenCV(Open Source Computer Vision Library)は、コンピュータビジョンと機械学習のための最も人気の高いライブラリです。画像・動画処理、物体検出、顔認識、機械学習など、幅広い用途で活用されています。この記事では、Python OpenCVの基本的な使い方から高度なテクニックまで、実践的なサンプルコードとともに詳しく解説します。

OpenCVとは

OpenCVは、Intel社が開発したオープンソースのコンピュータビジョンライブラリです。C++で書かれていますが、Python、Java、MATLABなど多くの言語でバインディングが提供されています。リアルタイム処理に最適化されており、産業用途から学術研究まで広く使用されています。

主な特徴

  • 高速処理: C++で実装された最適化されたアルゴリズム
  • 豊富な機能: 2500以上の最適化されたアルゴリズム
  • クロスプラットフォーム: Windows、Linux、macOS、モバイル対応
  • 多言語サポート: Python、C++、Java等でのバインディング

インストールと基本設定

ライブラリのインストール

pip install opencv-python opencv-contrib-python

基本的なインポート

import cv2
import numpy as np
import matplotlib.pyplot as plt

インストール確認

print(f"OpenCV version: {cv2.__version__}")

基本的な画像操作

画像の読み込み

# カラー画像として読み込み
img = cv2.imread('image.jpg', cv2.IMREAD_COLOR)
# グレースケールとして読み込み
gray_img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE)

画像の表示

cv2.imshow('Original Image', img)
cv2.waitKey(0)  # キー入力待ち
cv2.destroyAllWindows()

画像の保存

cv2.imwrite('output.jpg', img)
print("画像を保存しました")

画像情報の取得

height, width, channels = img.shape
print(f"サイズ: {width}x{height}, チャンネル数: {channels}")
print(f"データ型: {img.dtype}")

色空間の変換

BGR から RGB への変換

# OpenCVはBGR、MatplotlibはRGBを使用
rgb_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
plt.imshow(rgb_img)
plt.show()

グレースケール変換

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv2.imshow('Grayscale', gray)
cv2.waitKey(0)

HSV色空間への変換

hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

LAB色空間への変換

lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)

基本的な画像処理

画像のリサイズ

# サイズ指定
resized = cv2.resize(img, (800, 600))

# スケール指定
scaled = cv2.resize(img, None, fx=0.5, fy=0.5)

画像の回転

height, width = img.shape[:2]
center = (width // 2, height // 2)
rotation_matrix = cv2.getRotationMatrix2D(center, 45, 1.0)
rotated = cv2.warpAffine(img, rotation_matrix, (width, height))

画像の反転

# 水平反転
flipped_h = cv2.flip(img, 1)
# 垂直反転
flipped_v = cv2.flip(img, 0)
# 水平・垂直反転
flipped_both = cv2.flip(img, -1)

画像のクロップ

# y:y+h, x:x+w の順序
cropped = img[100:300, 50:250]

フィルタリング

ぼかし(Blur)

# ガウシアンぼかし
blurred = cv2.GaussianBlur(img, (15, 15), 0)

# 平均ぼかし
avg_blur = cv2.blur(img, (15, 15))

# メディアンフィルタ
median_blur = cv2.medianBlur(img, 15)

エッジ検出

# Cannyエッジ検出
edges = cv2.Canny(gray, 100, 200)

# Sobelフィルタ
sobel_x = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
sobel_y = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)

シャープニング

kernel = np.array([[-1,-1,-1], [-1,9,-1], [-1,-1,-1]])
sharpened = cv2.filter2D(img, -1, kernel)

ノイズ除去

# Non-local Means Denoising
denoised = cv2.fastNlMeansDenoisingColored(img, None, 10, 10, 7, 21)

閾値処理

二値化

# 固定閾値
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)

# 適応的閾値
adaptive = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
                                cv2.THRESH_BINARY, 11, 2)

Otsu法による自動閾値

ret_otsu, otsu = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
print(f"Otsu閾値: {ret_otsu}")

モルフォロジー演算

膨張と収縮

kernel = np.ones((5,5), np.uint8)

# 膨張
dilated = cv2.dilate(binary, kernel, iterations=1)

# 収縮
eroded = cv2.erode(binary, kernel, iterations=1)

オープニングとクロージング

# オープニング(ノイズ除去)
opening = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)

# クロージング(穴埋め)
closing = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)

グラディエント

gradient = cv2.morphologyEx(binary, cv2.MORPH_GRADIENT, kernel)

輪郭検出

輪郭の検出

contours, hierarchy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
print(f"検出された輪郭数: {len(contours)}")

輪郭の描画

contour_img = img.copy()
cv2.drawContours(contour_img, contours, -1, (0, 255, 0), 2)

輪郭の特徴量

for i, contour in enumerate(contours):
    area = cv2.contourArea(contour)
    perimeter = cv2.arcLength(contour, True)
    
    # 外接矩形
    x, y, w, h = cv2.boundingRect(contour)
    cv2.rectangle(img, (x, y), (x+w, y+h), (255, 0, 0), 2)
    
    print(f"輪郭{i}: 面積={area:.2f}, 周囲長={perimeter:.2f}")

輪郭の近似

for contour in contours:
    epsilon = 0.02 * cv2.arcLength(contour, True)
    approx = cv2.approxPolyDP(contour, epsilon, True)
    print(f"近似後の頂点数: {len(approx)}")

特徴点検出

コーナー検出(Harris)

corners = cv2.cornerHarris(gray, 2, 3, 0.04)
img[corners > 0.01 * corners.max()] = [0, 0, 255]

Shi-Tomasi法

corners = cv2.goodFeaturesToTrack(gray, 100, 0.01, 10)
for corner in corners:
    x, y = corner.ravel()
    cv2.circle(img, (int(x), int(y)), 3, (255, 0, 0), -1)

SIFT特徴点

sift = cv2.SIFT_create()
keypoints, descriptors = sift.detectAndCompute(gray, None)
sift_img = cv2.drawKeypoints(img, keypoints, None)

ORB特徴点

orb = cv2.ORB_create()
keypoints, descriptors = orb.detectAndCompute(gray, None)
orb_img = cv2.drawKeypoints(img, keypoints, None, color=(0, 255, 0))

テンプレートマッチング

基本的なテンプレートマッチング

template = cv2.imread('template.jpg', 0)
result = cv2.matchTemplate(gray, template, cv2.TM_CCOEFF_NORMED)

min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
top_left = max_loc
h, w = template.shape
bottom_right = (top_left[0] + w, top_left[1] + h)

cv2.rectangle(img, top_left, bottom_right, (0, 255, 0), 2)

複数マッチングの検出

threshold = 0.8
locations = np.where(result >= threshold)

for pt in zip(*locations[::-1]):
    cv2.rectangle(img, pt, (pt[0] + w, pt[1] + h), (0, 255, 0), 2)

物体検出

Haar Cascade分類器

# 顔検出
face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
faces = face_cascade.detectMultiScale(gray, 1.3, 5)

for (x, y, w, h) in faces:
    cv2.rectangle(img, (x, y), (x+w, y+h), (255, 0, 0), 2)

目の検出

eye_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_eye.xml')
eyes = eye_cascade.detectMultiScale(gray, 1.1, 3)

for (x, y, w, h) in eyes:
    cv2.rectangle(img, (x, y), (x+w, y+h), (0, 255, 0), 2)

動画処理

動画の読み込み

cap = cv2.VideoCapture('video.mp4')
# Webカメラの場合: cap = cv2.VideoCapture(0)

while True:
    ret, frame = cap.read()
    if not ret:
        break
    
    cv2.imshow('Video', frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()

動画の保存

cap = cv2.VideoCapture(0)
fourcc = cv2.VideoWriter_fourcc(*'XVID')
out = cv2.VideoWriter('output.avi', fourcc, 20.0, (640, 480))

while True:
    ret, frame = cap.read()
    if not ret:
        break
    
    out.write(frame)
    cv2.imshow('Recording', frame)
    
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

cap.release()
out.release()
cv2.destroyAllWindows()

背景差分

backSub = cv2.createBackgroundSubtractorMOG2()

cap = cv2.VideoCapture('video.mp4')
while True:
    ret, frame = cap.read()
    if not ret:
        break
    
    fgMask = backSub.apply(frame)
    cv2.imshow('Foreground Mask', fgMask)
    
    if cv2.waitKey(30) & 0xFF == ord('q'):
        break

cap.release()

実践的なアプリケーション

1. 文書スキャナー

def document_scanner(img):
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    blurred = cv2.GaussianBlur(gray, (5, 5), 0)
    edged = cv2.Canny(blurred, 75, 200)
    
    contours, _ = cv2.findContours(edged, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
    contours = sorted(contours, key=cv2.contourArea, reverse=True)[:5]
    
    for contour in contours:
        epsilon = 0.02 * cv2.arcLength(contour, True)
        approx = cv2.approxPolyDP(contour, epsilon, True)
        
        if len(approx) == 4:
            screen_contour = approx
            break
    
    return screen_contour

2. 色の範囲検出

def detect_color_range(img, lower_color, upper_color):
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    mask = cv2.inRange(hsv, lower_color, upper_color)
    result = cv2.bitwise_and(img, img, mask=mask)
    return result, mask

# 青色の検出例
lower_blue = np.array([100, 50, 50])
upper_blue = np.array([130, 255, 255])
blue_result, blue_mask = detect_color_range(img, lower_blue, upper_blue)

3. QRコード検出

def detect_qr_code(img):
    qr_detector = cv2.QRCodeDetector()
    data, points, _ = qr_detector.detectAndDecode(img)
    
    if points is not None:
        points = points[0].astype(int)
        cv2.polylines(img, [points], True, (0, 255, 0), 2)
        
        if data:
            print(f"QRコードデータ: {data}")
    
    return img, data

4. 車線検出

def detect_lanes(img):
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    blur = cv2.GaussianBlur(gray, (5, 5), 0)
    edges = cv2.Canny(blur, 50, 150)
    
    # 関心領域の設定
    height, width = edges.shape
    mask = np.zeros_like(edges)
    polygon = np.array([[(0, height), (width//2, height//2), (width, height)]])
    cv2.fillPoly(mask, polygon, 255)
    masked_edges = cv2.bitwise_and(edges, mask)
    
    # ハフ変換で直線検出
    lines = cv2.HoughLinesP(masked_edges, 1, np.pi/180, 50, minLineLength=100, maxLineGap=50)
    
    if lines is not None:
        for line in lines:
            x1, y1, x2, y2 = line[0]
            cv2.line(img, (x1, y1), (x2, y2), (0, 255, 0), 3)
    
    return img

5. 光学文字認識(OCR)準備

def preprocess_for_ocr(img):
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    
    # ノイズ除去
    denoised = cv2.fastNlMeansDenoising(gray)
    
    # 二値化
    _, binary = cv2.threshold(denoised, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
    
    # モルフォロジー演算
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
    processed = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)
    
    return processed

画像の幾何学変換

アフィン変換

# 3点対応によるアフィン変換
pts1 = np.float32([[50, 50], [200, 50], [50, 200]])
pts2 = np.float32([[10, 100], [200, 50], [100, 250]])

matrix = cv2.getAffineTransform(pts1, pts2)
transformed = cv2.warpAffine(img, matrix, (img.shape[1], img.shape[0]))

射影変換(ホモグラフィ)

# 4点対応による射影変換
pts1 = np.float32([[56, 65], [368, 52], [28, 387], [389, 390]])
pts2 = np.float32([[0, 0], [300, 0], [0, 300], [300, 300]])

matrix = cv2.getPerspectiveTransform(pts1, pts2)
warped = cv2.warpPerspective(img, matrix, (300, 300))

ヒストグラム処理

ヒストグラムの計算と表示

def plot_histogram(img):
    colors = ('b', 'g', 'r')
    plt.figure(figsize=(10, 6))
    
    for i, color in enumerate(colors):
        hist = cv2.calcHist([img], [i], None, [256], [0, 256])
        plt.plot(hist, color=color, label=f'{color.upper()} channel')
    
    plt.xlabel('Pixel Value')
    plt.ylabel('Frequency')
    plt.legend()
    plt.show()

ヒストグラム均等化

# グレースケール画像の均等化
equalized = cv2.equalizeHist(gray)

# カラー画像の均等化(YUV色空間を使用)
yuv = cv2.cvtColor(img, cv2.COLOR_BGR2YUV)
yuv[:,:,0] = cv2.equalizeHist(yuv[:,:,0])
equalized_color = cv2.cvtColor(yuv, cv2.COLOR_YUV2BGR)

CLAHE(適応ヒストグラム均等化)

clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
cl1 = clahe.apply(gray)

機械学習との連携

K-Means クラスタリング

def kmeans_segmentation(img, k=3):
    data = img.reshape((-1, 3))
    data = np.float32(data)
    
    criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 20, 1.0)
    _, labels, centers = cv2.kmeans(data, k, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)
    
    centers = np.uint8(centers)
    segmented = centers[labels.flatten()]
    segmented = segmented.reshape(img.shape)
    
    return segmented

HOG特徴量

def extract_hog_features(img):
    hog = cv2.HOGDescriptor()
    features = hog.compute(gray)
    return features.flatten()

パフォーマンス最適化

並列処理

import multiprocessing as mp

def process_image(img_path):
    img = cv2.imread(img_path)
    # 画像処理
    processed = cv2.GaussianBlur(img, (15, 15), 0)
    cv2.imwrite(f'processed_{img_path}', processed)

# 複数画像の並列処理
if __name__ == '__main__':
    image_paths = ['img1.jpg', 'img2.jpg', 'img3.jpg']
    with mp.Pool(processes=4) as pool:
        pool.map(process_image, image_paths)

NumPy最適化

# 効率的な画素値操作
img_optimized = np.where(img > 128, 255, 0).astype(np.uint8)

# ベクトル化された演算
result = cv2.addWeighted(img1, 0.7, img2, 0.3, 0)

GPU処理(OpenCV CUDA)

# GPU対応版(opencv-contrib-pythonが必要)
try:
    gpu_img = cv2.cuda_GpuMat()
    gpu_img.upload(img)
    gpu_result = cv2.cuda.bilateralFilter(gpu_img, -1, 50, 50)
    result = gpu_result.download()
except:
    print("CUDA対応OpenCVが必要です")

デバッグとトラブルシューティング

画像情報の詳細表示

def debug_image_info(img, name="Image"):
    print(f"=== {name} ===")
    print(f"Shape: {img.shape}")
    print(f"Data type: {img.dtype}")
    print(f"Min value: {img.min()}")
    print(f"Max value: {img.max()}")
    print(f"Mean value: {img.mean():.2f}")
    print(f"Memory size: {img.nbytes} bytes")

ステップバイステップの可視化

def visualize_processing_steps(img):
    fig, axes = plt.subplots(2, 3, figsize=(15, 10))
    
    # 元画像
    axes[0,0].imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
    axes[0,0].set_title('Original')
    
    # グレースケール
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    axes[0,1].imshow(gray, cmap='gray')
    axes[0,1].set_title('Grayscale')
    
    # ぼかし
    blurred = cv2.GaussianBlur(gray, (15, 15), 0)
    axes[0,2].imshow(blurred, cmap='gray')
    axes[0,2].set_title('Blurred')
    
    # エッジ検出
    edges = cv2.Canny(blurred, 50, 150)
    axes[1,0].imshow(edges, cmap='gray')
    axes[1,0].set_title('Edges')
    
    # 二値化
    _, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
    axes[1,1].imshow(binary, cmap='gray')
    axes[1,1].set_title('Binary')
    
    # モルフォロジー
    kernel = np.ones((5,5), np.uint8)
    morphed = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)
    axes[1,2].imshow(morphed, cmap='gray')
    axes[1,2].set_title('Morphology')
    
    for ax in axes.flat:
        ax.axis('off')
    
    plt.tight_layout()
    plt.show()

エラーハンドリング

安全な画像読み込み

def safe_imread(path, flags=cv2.IMREAD_COLOR):
    try:
        img = cv2.imread(path, flags)
        if img is None:
            raise ValueError(f"画像を読み込めません: {path}")
        return img
    except Exception as e:
        print(f"エラー: {e}")
        return None

処理結果の検証

def validate_processing_result(original, processed, operation_name):
    if processed is None:
        print(f"警告: {operation_name}の結果がNoneです")
        return False
    
    if processed.size == 0:
        print(f"警告: {operation_name}の結果が空です")
        return False
    
    print(f"{operation_name}が正常に完了しました")
    return True

実用的なプロジェクト例

コンピュータビジョンクラス

class ImageProcessor:
    def __init__(self):
        self.face_cascade = cv2.CascadeClassifier(
            cv2.data.haarcascades + 'haarcascade_frontalface_default.xml'
        )
    
    def detect_faces(self, img):
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        faces = self.face_cascade.detectMultiScale(gray, 1.3, 5)
        
        for (x, y, w, h) in faces:
            cv2.rectangle(img, (x, y), (x+w, y+h), (255, 0, 0), 2)
        
        return img, len(faces)
    
    def enhance_image(self, img):
        # ノイズ除去
        denoised = cv2.fastNlMeansDenoisingColored(img)
        
        # シャープニング
        kernel = np.array([[-1,-1,-1], [-1,9,-1], [-1,-1,-1]])
        sharpened = cv2.filter2D(denoised, -1, kernel)
        
        return sharpened

リアルタイム物体追跡

class ObjectTracker:
    def __init__(self):
        self.tracker = cv2.TrackerCSRT_create()
        self.initialized = False
    
    def initialize(self, frame, bbox):
        self.initialized = self.tracker.init(frame, bbox)
        return self.initialized
    
    def update(self, frame):
        if not self.initialized:
            return False, None
        
        success, bbox = self.tracker.update(frame)
        return success, bbox
    
    def track_in_video(self, video_path):
        cap = cv2.VideoCapture(video_path)
        
        # 最初のフレームでROIを選択
        ret, frame = cap.read()
        bbox = cv2.selectROI("Select Object", frame, False)
        cv2.destroyWindow("Select Object")
        
        self.initialize(frame, bbox)
        
        while True:
            ret, frame = cap.read()
            if not ret:
                break
            
            success, bbox = self.update(frame)
            
            if success:
                x, y, w, h = [int(i) for i in bbox]
                cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2)
            
            cv2.imshow('Tracking', frame)
            if cv2.waitKey(1) & 0xFF == ord('q'):
                break
        
        cap.release()
        cv2.destroyAllWindows()

まとめ

OpenCVは、コンピュータビジョンと画像処理のための非常に強力で包括的なライブラリです。基本的な画像操作から高度な機械学習アルゴリズムまで、幅広い機能を提供しています。

主なポイント:

  • 豊富な機能: 2500以上の最適化されたアルゴリズム
  • 高速処理: C++で実装された最適化されたコア
  • 実用性: 産業用途からの学術研究まで対応
  • 柔軟性: 画像処理、動画処理、機械学習の統合
  • 拡張性: GPU処理、並列処理、他ライブラリとの連携

OpenCVの強力な機能を活用することで、顔認識、物体検出、文書スキャン、品質検査など、様々なコンピュータビジョンアプリケーションを開発できます。継続的な学習と実践を通じて、より高度なビジョンシステムの構築を目指しましょう。

参考リンク

適切な学習とプロジェクト経験を積むことで、OpenCVを使った高度なコンピュータビジョンシステムの開発が可能になります。

■プロンプトだけでオリジナルアプリを開発・公開してみた!!

■AI時代の第一歩!「AI駆動開発コース」はじめました!

テックジム東京本校で先行開始。

■テックジム東京本校

「武田塾」のプログラミング版といえば「テックジム」。
講義動画なし、教科書なし。「進捗管理とコーチング」で効率学習。
より早く、より安く、しかも対面型のプログラミングスクールです。

<短期講習>5日で5万円の「Pythonミニキャンプ」開催中。

<月1開催>放送作家による映像ディレクター養成講座

<オンライン無料>ゼロから始めるPython爆速講座