opencv について
OpenCVは、画像・動画・カメラを扱うコンピュータービジョンおよび画像処理ライブラリです。基本的な変換から検出、追跡、3D再構成までのアルゴリズムを備え、Pythonからも利用できます。
主な機能
- 01画像・動画の入出力
- 02画像変換とフィルタリング
- 03エッジ・輪郭検出
- 04特徴検出とマッチング
- 05物体検出と追跡
- 06カメラキャリブレーションと3D再構成
活用シーン
- 01画像の前処理と色空間の変換
- 02動画フレームでの動体検出や追跡
- 03顔、QRコード、画像特徴の検出
- 04パノラマ合成や3Dシーンの再構成
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ tondevrel/ scientific-agent-skills --skill opencvインストール後、Claude Code で/opencvを入力して読み込まれたか確認します。
詳細
OpenCV - コンピュータービジョンと画像処理
OpenCV(Open Source Computer Vision Library)は、コンピュータービジョンタスクの事実上の標準ライブラリです。画像の読み込みなどの基本的な操作から、顔認識や3D再構築などの高度なタスクまで、リアルタイムの画像・動画処理向けに最適化された2500以上のアルゴリズムを提供します。
使用する場面
- ファイルやカメラから画像・動画を読み込み、書き込み、表示する場合。
- 画像の前処理(サイズ変更、切り抜き、回転、色変換)。
- エッジ検出(Canny、Sobel)と輪郭検出。
- 特徴検出とマッチング(SIFT、ORB、AKAZE)。
- 物体検出(Haar Cascades、HOG、YOLO/SSD向けDNNモジュール)。
- 顔検出と認識。
- 画像セグメンテーション(しきい値処理、ウォーターシェッド、GrabCut)。
- 動画解析(動き検出、物体追跡、オプティカルフロー)。
- カメラキャリブレーションと3D再構築。
- 画像のスティッチングとパノラマの作成。
- 高速なパフォーマンスが必要なリアルタイムアプリケーション。
リファレンスドキュメント
公式ドキュメント: https://docs.opencv.org/4.x/
GitHub: https://github.com/opencv/opencv
チュートリアル: https://docs.opencv.org/4.x/d9/df8/tutorial_root.html
検索パターン: cv2.imread, cv2.cvtColor, cv2.Canny, cv2.findContours, cv2.VideoCapture
基本原則
NumPy 配列としての画像
OpenCV は画像を、形状が (高さ、幅、チャンネル数) の NumPy 配列として表現します。これにより、NumPy の操作やその他の科学計算用 Python ライブラリとシームレスに連携できます。
BGR 色空間(RGB ではありません!)
OpenCV はデフォルトで RGB ではなく BGR(Blue-Green-Red)を使用します。画像を表示したり、他のライブラリと連携したりする際には、この点を忘れないことが重要です。
インプレース操作とコピー操作
多くの OpenCV 関数は、パフォーマンス向上のために画像をインプレースで変更します。効率的なコードを書くには、コピーが作成されるタイミングを理解することが不可欠です。
Python から呼び出す C++ のパフォーマンス
OpenCV は最適化された C++ で記述されているため、Python から呼び出しても非常に高速です。OpenCV のベクトル化された操作が使える場合は、Python のループを避けてください。
クイックリファレンス
インストール
# Basic OpenCV
pip install opencv-python
# With contrib modules (SIFT, SURF, etc.)
pip install opencv-contrib-python
# Headless (no GUI, for servers)
pip install opencv-python-headless
標準的なインポート
import cv2
import numpy as np
import matplotlib.pyplot as plt
基本パターン - 読み込み、処理、表示
import cv2
# 1. Read image
img = cv2.imread('image.jpg')
# 2. Process (convert to grayscale)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 3. Display
cv2.imshow('Grayscale', gray)
cv2.waitKey(0) # Wait for key press
cv2.destroyAllWindows()
基本パターン - 動画処理
import cv2
# 1. Open video capture
cap = cv2.VideoCapture(0) # 0 = default camera, or 'video.mp4'
while True:
# 2. Read frame
ret, frame = cap.read()
if not ret:
break
# 3. Process frame
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 4. Display
cv2.imshow('Video', gray)
# 5. Exit on 'q' key
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 6. Cleanup
cap.release()
cv2.destroyAllWindows()
重要なルール
✅ 推奨事項
- 画像の読み込みを確認する - ファイルエラーを検出できるよう、
cv2.imread()の後に必ずimg is not Noneを確認してください。 - 色変換には cv2.cvtColor() を使う - チャンネルを手動で並べ替えず、用意されている変換コードを使用してください。
- リソースを解放する - 動画やウィンドウの使用後は、必ず
cap.release()とcv2.destroyAllWindows()を呼び出してください。 - 変更前にコピーする - 元の画像を保持する必要がある場合は、
img.copy()を使用してください。 - 適切なデータ型を使う - 表示には画像を uint8(0-255)のまま使用し、数学的な操作には float32(0-1)に変換してください。
- VideoCapture を検証する - フレームを読み込む前に
cap.isOpened()を確認してください。 - Matplotlib には BGR2RGB を使う - matplotlib で表示する場合は、BGR を RGB に変換してください。
- 操作をベクトル化する - ピクセルを処理する Python ループではなく、OpenCV の組み込み関数を使用してください。
❌ 禁止事項
- RGB だと思い込まない - OpenCV はデフォルトで BGR を使用します。matplotlib や PIL で使う場合は RGB に変換してください。
- waitKey() を忘れない -
cv2.waitKey()がないと、ウィンドウが正しく表示されません。 - PIL と OpenCV を直接混在させない - 両者の間で明示的に変換してください(OpenCV は BGR、PIL は RGB を使用します)。
- 動画をメモリ上で処理しない - 大きな動画によるメモリの問題を避けるため、フレームごとに処理してください。
- ピクセル処理に Python のループを使わない - ベクトル化された操作と比べて100倍遅くなります。
- パスをハードコードしない - クロスプラットフォームの互換性のために、
os.path.join()またはpathlibを使用してください。
アンチパターン(絶対に避ける)
import cv2
import numpy as np
# ❌ BAD: Not checking if image loaded
img = cv2.imread('image.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # Crashes if file doesn't exist!
# ✅ GOOD: Always validate
img = cv2.imread('image.jpg')
if img is None:
raise FileNotFoundError("Image not found")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# ❌ BAD: Using Python loops for pixel manipulation
for i in range(img.shape[0]):
for j in range(img.shape[1]):
img[i, j] = img[i, j] * 0.5 # Extremely slow!
# ✅ GOOD: Vectorized NumPy operations
img = (img * 0.5).astype(np.uint8)
# ❌ BAD: Displaying BGR image with matplotlib
plt.imshow(img) # Colors will be wrong!
# ✅ GOOD: Convert to RGB first
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
plt.imshow(img_rgb)
# ❌ BAD: Not releasing video capture
cap = cv2.VideoCapture('video.mp4')
while cap.read()[0]:
pass
# Memory leak! Camera still locked!
# ✅ GOOD: Always release
cap = cv2.VideoCapture('video.mp4')
try:
while cap.read()[0]:
pass
finally:
cap.release()
画像の入出力と表示
画像の読み込みと書き込み
import cv2
# Read image (returns None if failed)
img = cv2.imread('image.jpg')
# Read as grayscale
gray = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE)
# Read with alpha channel
img_alpha = cv2.imread('image.png', cv2.IMREAD_UNCHANGED)
# Write image
cv2.imwrite('output.jpg', img)
# Write with quality (JPEG: 0-100, PNG: 0-9 compression)
cv2.imwrite('output.jpg', img, [cv2.IMWRITE_JPEG_QUALITY, 95])
cv2.imwrite('output.png', img, [cv2.IMWRITE_PNG_COMPRESSION, 9])
# Check if image loaded
if img is None:
print("Error: Could not load image")
else:
print(f"Image shape: {img.shape}") # (height, width, channels)
画像の表示
import cv2
# Display image in window
cv2.imshow('Window Name', img)
cv2.waitKey(0) # Wait indefinitely for key press
cv2.destroyAllWindows()
# Display for specific duration (milliseconds)
cv2.imshow('Image', img)
cv2.waitKey(3000) # Wait 3 seconds
cv2.destroyAllWindows()
# Display multiple images
cv2.imshow('Original', img)
cv2.imshow('Gray', gray)
cv2.waitKey(0)
cv2.destroyAllWindows()
# Display with matplotlib (convert BGR to RGB!)
import matplotlib.pyplot as plt
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
plt.imshow(img_rgb)
plt.axis('off')
plt.show()
動画のキャプチャ
import cv2
# Open camera (0 = default, 1 = second camera, etc.)
cap = cv2.VideoCapture(0)
# Open video file
cap = cv2.VideoCapture('video.mp4')
# Check if opened successfully
if not cap.isOpened():
print("Error: Could not open video")
exit()
# Get video properties
fps = cap.get(cv2.CAP_PROP_FPS)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
print(f"Video: {width}x{height} @ {fps} fps, {total_frames} frames")
# Read and process frames
while True:
ret, frame = cap.read()
if not ret:
print("End of video or error")
break
# Process frame here
cv2.imshow('Frame', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
動画の書き込み
import cv2
cap = cv2.VideoCapture('input.mp4')
# Get video properties
fps = int(cap.get(cv2.CAP_PROP_FPS))
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
# Create VideoWriter
fourcc = cv2.VideoWriter_fourcc(*'mp4v') # or 'XVID', 'MJPG'
out = cv2.VideoWriter('output.mp4', fourcc, fps, (width, height))
while True:
ret, frame = cap.read()
if not ret:
break
# Process frame
processed = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
processed = cv2.cvtColor(processed, cv2.COLOR_GRAY2BGR) # Convert back to 3-channel
# Write frame
out.write(processed)
cap.release()
out.release()
cv2.destroyAllWindows()
画像変換
サイズ変更と切り抜き
import cv2
img = cv2.imread('image.jpg')
# Resize to specific dimensions
resized = cv2.resize(img, (800, 600)) # (width, height)
# Resize by scale factor
scaled = cv2.resize(img, None, fx=0.5, fy=0.5) # 50% of original
# Resize with interpolation methods
resized_linear = cv2.resize(img, (800, 600), interpolation=cv2.INTER_LINEAR) # Default
resized_cubic = cv2.resize(img, (800, 600), interpolation=cv2.INTER_CUBIC) # Better quality
resized_area = cv2.resize(img, (400, 300), interpolation=cv2.INTER_AREA) # Best for shrinking
# Crop (using NumPy slicing)
height, width = img.shape[:2]
cropped = img[100:400, 200:600] # [y1:y2, x1:x2]
# Center crop
crop_size = 300
center_x, center_y = width // 2, height // 2
x1 = center_x - crop_size // 2
y1 = center_y - crop_size // 2
center_cropped = img[y1:y1+crop_size, x1:x1+crop_size]
回転と反転
import cv2
# Flip horizontally
flipped_h = cv2.flip(img, 1)
# Flip vertically
flipped_v = cv2.flip(img, 0)
# Flip both
flipped_both = cv2.flip(img, -1)
# Rotate 90 degrees clockwise
rotated_90 = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE)
# Rotate 180 degrees
rotated_180 = cv2.rotate(img, cv2.ROTATE_180)
# Rotate 90 degrees counter-clockwise
rotated_90_ccw = cv2.rotate(img, cv2.ROTATE_90_COUNTERCLOCKWISE)
# Rotate by arbitrary angle (around center)
height, width = img.shape[:2]
center = (width // 2, height // 2)
angle = 45 # degrees
# Get rotation matrix
M = cv2.getRotationMatrix2D(center, angle, scale=1.0)
# Apply rotation
rotated = cv2.warpAffine(img, M, (width, height))
# Rotate and scale
M_scaled = cv2.getRotationMatrix2D(center, 30, scale=0.8)
rotated_scaled = cv2.warpAffine(img, M_scaled, (width, height))
色空間の変換
import cv2
img = cv2.imread('image.jpg')
# BGR to RGB (for matplotlib)
rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# BGR to Grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# BGR to HSV (useful for color-based segmentation)
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# BGR to LAB
lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
# Grayscale to BGR (add color channels)
gray_bgr = cv2.cvtColor(gray, cv2.COLOR_GRAY2BGR)
# Extract individual channels
b, g, r = cv2.split(img)
# Merge channels
merged = cv2.merge([b, g, r])
画像のフィルタリングと強調
ぼかしと平滑化
import cv2
# Gaussian blur (reduce noise)
blurred = cv2.GaussianBlur(img, (5, 5), 0) # (kernel_size, sigma)
# Median blur (good for salt-and-pepper noise)
median = cv2.medianBlur(img, 5) # kernel_size must be odd
# Bilateral filter (edge-preserving smoothing)
bilateral = cv2.bilateralFilter(img, 9, 75, 75) # (d, sigmaColor, sigmaSpace)
# Average blur
avg_blur = cv2.blur(img, (5, 5))
# Box filter
box = cv2.boxFilter(img, -1, (5, 5))
エッジ検出
import cv2
# Convert to grayscale first
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Canny edge detection
edges = cv2.Canny(gray, threshold1=50, threshold2=150)
# Sobel edge detection (gradient in x and y)
sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3) # X gradient
sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3) # Y gradient
sobel = cv2.magnitude(sobelx, sobely)
# Laplacian edge detection
laplacian = cv2.Laplacian(gray, cv2.CV_64F)
# Scharr (more accurate than Sobel for small kernels)
scharrx = cv2.Scharr(gray, cv2.CV_64F, 1, 0)
scharry = cv2.Scharr(gray, cv2.CV_64F, 0, 1)
モルフォロジー演算
import cv2
import numpy as np
# Define kernel
kernel = np.ones((5, 5), np.uint8)
# Erosion (shrink white regions)
eroded = cv2.erode(img, kernel, iterations=1)
# Dilation (expand white regions)
dilated = cv2.dilate(img, kernel, iterations=1)
# Opening (erosion followed by dilation) - removes noise
opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
# Closing (dilation followed by erosion) - closes gaps
closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
# Gradient (difference between dilation and erosion) - outlines
gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel)
# Top hat (difference between input and opening) - bright spots
tophat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)
# Black hat (difference between closing and input) - dark spots
blackhat = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel)
しきい値処理
import cv2
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Simple threshold
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# Binary inverse
ret, thresh_inv = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY_INV)
# Truncate
ret, thresh_trunc = cv2.threshold(gray, 127, 255, cv2.THRESH_TRUNC)
# To zero
ret, thresh_tozero = cv2.threshold(gray, 127, 255, cv2.THRESH_TOZERO)
# Otsu's thresholding (automatic threshold calculation)
ret, thresh_otsu = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
# Adaptive thresholding (different threshold for different regions)
adaptive_mean = cv2.adaptiveThreshold(
gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 11, 2
)
adaptive_gaussian = cv2.adaptiveThreshold(
gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2
)
輪郭と形状の検出
輪郭の検出と描画
import cv2
# Convert to grayscale and threshold
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# Find contours
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Draw all contours
img_contours = img.copy()
cv2.drawContours(img_contours, contours, -1, (0, 255, 0), 2) # -1 = all contours
# Draw specific contour
cv2.drawContours(img_contours, contours, 0, (255, 0, 0), 3) # First contour
# Iterate through contours
for i, contour in enumerate(contours):
# Calculate area
area = cv2.contourArea(contour)
# Calculate perimeter
perimeter = cv2.arcLength(contour, True)
# Filter by area
if area > 1000:
cv2.drawContours(img_contours, [contour], -1, (0, 0, 255), 2)
# Get bounding rectangle
x, y, w, h = cv2.boundingRect(contour)
cv2.rectangle(img_contours, (x, y), (x+w, y+h), (255, 0, 0), 2)
形状の近似
import cv2
for contour in contours:
# Approximate contour to polygon
epsilon = 0.02 * cv2.arcLength(contour, True)
approx = cv2.approxPolyDP(contour, epsilon, True)
# Number of vertices
n_vertices = len(approx)
# Classify shape
if n_vertices == 3:
shape = "Triangle"
elif n_vertices == 4:
# Check if rectangle or square
x, y, w, h = cv2.boundingRect(approx)
aspect_ratio = float(w) / h
shape = "Square" if 0.95 <= aspect_ratio <= 1.05 else "Rectangle"
elif n_vertices > 4:
shape = "Circle" if n_vertices > 10 else "Polygon"
# Draw and label
cv2.drawContours(img, [approx], -1, (0, 255, 0), 2)
x, y = approx[0][0]
cv2.putText(img, shape, (x, y), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)
輪郭の特徴量
import cv2
import numpy as np
for contour in contours:
# Moments (for center of mass)
M = cv2.moments(contour)
if M['m00'] != 0:
cx = int(M['m10'] / M['m00'])
cy = int(M['m01'] / M['m00'])
cv2.circle(img, (cx, cy), 5, (255, 0, 0), -1)
# Minimum enclosing circle
(x, y), radius = cv2.minEnclosingCircle(contour)
center = (int(x), int(y))
radius = int(radius)
cv2.circle(img, center, radius, (0, 255, 0), 2)
# Fit ellipse (requires at least 5 points)
if len(contour) >= 5:
ellipse = cv2.fitEllipse(contour)
cv2.ellipse(img, ellipse, (255, 0, 255), 2)
# Convex hull
hull = cv2.convexHull(contour)
cv2.drawContours(img, [hull], -1, (0, 255, 255), 2)
# Solidity (contour area / convex hull area)
hull_area = cv2.contourArea(hull)
contour_area = cv2.contourArea(contour)
solidity = contour_area / hull_area if hull_area > 0 else 0
特徴点の検出とマッチング
ORB(Oriented FAST and Rotated BRIEF)
import cv2
img1 = cv2.imread('image1.jpg', cv2.IMREAD_GRAYSCALE)
img2 = cv2.imread('image2.jpg', cv2.IMREAD_GRAYSCALE)
# Create ORB detector
orb = cv2.ORB_create(nfeatures=1000)
# Detect keypoints and compute descriptors
kp1, des1 = orb.detectAndCompute(img1, None)
kp2, des2 = orb.detectAndCompute(img2, None)
# Draw keypoints
img1_kp = cv2.drawKeypoints(img1, kp1, None, color=(0, 255, 0))
# Match descriptors using BFMatcher
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
matches = bf.match(des1, des2)
# Sort matches by distance (best first)
matches = sorted(matches, key=lambda x: x.distance)
# Draw top matches
img_matches = cv2.drawMatches(
img1, kp1, img2, kp2, matches[:50],
None, flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS
)
cv2.imshow('Matches', img_matches)
cv2.waitKey(0)
SIFT(Scale-Invariant Feature Transform)
import cv2
# Note: SIFT is in opencv-contrib-python, not opencv-python
img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE)
# Create SIFT detector
sift = cv2.SIFT_create()
# Detect keypoints and compute descriptors
keypoints, descriptors = sift.detectAndCompute(img, None)
# Draw keypoints
img_kp = cv2.drawKeypoints(
img, keypoints, None,
flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS
)
print(f"Number of keypoints: {len(keypoints)}")
FLANN による特徴点マッチング
import cv2
import numpy as np
# Detect features
sift = cv2.SIFT_create()
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)
# FLANN parameters
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)
search_params = dict(checks=50)
flann = cv2.FlannBasedMatcher(index_params, search_params)
matches = flann.knnMatch(des1, des2, k=2)
# Lowe's ratio test
good_matches = []
for m, n in matches:
if m.distance < 0.7 * n.distance:
good_matches.append(m)
print(f"Good matches: {len(good_matches)}")
# Draw good matches
img_matches = cv2.drawMatches(
img1, kp1, img2, kp2, good_matches, None,
flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS
)
オブジェクト検出
Haar Cascade(顔検出)
import cv2
# Load pre-trained Haar Cascade
face_cascade = cv2.CascadeClassifier(
cv2.data.haarcascades + 'haarcascade_frontalface_default.xml'
)
eye_cascade = cv2.CascadeClassifier(
cv2.data.haarcascades + 'haarcascade_eye.xml'
)
img = cv2.imread('people.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Detect faces
faces = face_cascade.detectMultiScale(
gray,
scaleFactor=1.1,
minNeighbors=5,
minSize=(30, 30)
)
# Draw rectangles around faces
for (x, y, w, h) in faces:
cv2.rectangle(img, (x, y), (x+w, y+h), (255, 0, 0), 2)
# Detect eyes in face region
roi_gray = gray[y:y+h, x:x+w]
roi_color = img[y:y+h, x:x+w]
eyes = eye_cascade.detectMultiScale(roi_gray)
for (ex, ey, ew, eh) in eyes:
cv2.rectangle(roi_color, (ex, ey), (ex+ew, ey+eh), (0, 255, 0), 2)
cv2.imshow('Faces', img)
cv2.waitKey(0)
テンプレートマッチング
import cv2
img = cv2.imread('image.jpg')
template = cv2.imread('template.jpg')
# Convert to grayscale
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
template_gray = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
h, w = template_gray.shape
# Template matching
result = cv2.matchTemplate(img_gray, template_gray, cv2.TM_CCOEFF_NORMED)
# Find locations above threshold
threshold = 0.8
locations = np.where(result >= threshold)
# Draw rectangles
for pt in zip(*locations[::-1]):
cv2.rectangle(img, pt, (pt[0] + w, pt[1] + h), (0, 255, 0), 2)
cv2.imshow('Matches', img)
cv2.waitKey(0)
実践的なワークフロー
1. ドキュメントスキャナー(射影変換)
import cv2
import numpy as np
def order_points(pts):
"""Order points: top-left, top-right, bottom-right, bottom-left."""
rect = np.zeros((4, 2), dtype="float32")
s = pts.sum(axis=1)
rect[0] = pts[np.argmin(s)] # Top-left
rect[2] = pts[np.argmax(s)] # Bottom-right
diff = np.diff(pts, axis=1)
rect[1] = pts[np.argmin(diff)] # Top-right
rect[3] = pts[np.argmax(diff)] # Bottom-left
return rect
def four_point_transform(image, pts):
"""Apply perspective transform to get bird's eye view."""
rect = order_points(pts)
(tl, tr, br, bl) = rect
# Compute width and height
widthA = np.linalg.norm(br - bl)
widthB = np.linalg.norm(tr - tl)
maxWidth = max(int(widthA), int(widthB))
heightA = np.linalg.norm(tr - br)
heightB = np.linalg.norm(tl - bl)
maxHeight = max(int(heightA), int(heightB))
# Destination points
dst = np.array([
[0, 0],
[maxWidth - 1, 0],
[maxWidth - 1, maxHeight - 1],
[0, maxHeight - 1]
], dtype="float32")
# Perspective transform
M = cv2.getPerspectiveTransform(rect, dst)
warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))
return warped
# Usage
img = cv2.imread('document.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray, 50, 150)
# Find contours
contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = sorted(contours, key=cv2.contourArea, reverse=True)
# Find document contour (assume largest quadrilateral)
for contour in contours:
peri = cv2.arcLength(contour, True)
approx = cv2.approxPolyDP(contour, 0.02 * peri, True)
if len(approx) == 4:
pts = approx.reshape(4, 2)
scanned = four_point_transform(img, pts)
cv2.imshow('Scanned', scanned)
cv2.waitKey(0)
break
2. 動体検知
import cv2
def detect_motion(video_path):
"""Detect motion in video using frame differencing."""
cap = cv2.VideoCapture(video_path)
ret, frame1 = cap.read()
ret, frame2 = cap.read()
while cap.isOpened():
# Compute difference between frames
diff = cv2.absdiff(frame1, frame2)
gray = cv2.cvtColor(diff, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (5, 5), 0)
_, thresh = cv2.threshold(blur, 20, 255, cv2.THRESH_BINARY)
# Dilate to fill gaps
dilated = cv2.dilate(thresh, None, iterations=3)
# Find contours
contours, _ = cv2.findContours(dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Draw bounding boxes
for contour in contours:
if cv2.contourArea(contour) < 500:
continue
x, y, w, h = cv2.boundingRect(contour)
cv2.rectangle(frame1, (x, y), (x+w, y+h), (0, 255, 0), 2)
cv2.putText(frame1, "Motion", (x, y-10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
cv2.imshow('Motion Detection', frame1)
# Update frames
frame1 = frame2
ret, frame2 = cap.read()
if not ret or cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
# Usage
# detect_motion('video.mp4')
3. 色ベースのオブジェクト追跡
import cv2
import numpy as np
def track_colored_object(video_path, lower_color, upper_color):
"""Track object by color in HSV space."""
cap = cv2.VideoCapture(video_path)
while True:
ret, frame = cap.read()
if not ret:
break
# Convert to HSV
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
# Create mask for color
mask = cv2.inRange(hsv, lower_color, upper_color)
# Remove noise
mask = cv2.erode(mask, None, iterations=2)
mask = cv2.dilate(mask, None, iterations=2)
# Find contours
contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
if contours:
# Find largest contour
largest = max(contours, key=cv2.contourArea)
# Get center and radius
((x, y), radius) = cv2.minEnclosingCircle(largest)
if radius > 10:
# Draw circle and center
cv2.circle(frame, (int(x), int(y)), int(radius), (0, 255, 0), 2)
cv2.circle(frame, (int(x), int(y)), 5, (0, 0, 255), -1)
cv2.imshow('Tracking', frame)
cv2.imshow('Mask', mask)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
# Usage: Track red object
# lower_red = np.array([0, 100, 100])
# upper_red = np.array([10, 255, 255])
# track_colored_object(0, lower_red, upper_red)
4. QR コード検出
import cv2
def detect_qr_code(image_path):
"""Detect and decode QR codes."""
img = cv2.imread(image_path)
# Initialize QR code detector
detector = cv2.QRCodeDetector()
# Detect and decode
data, bbox, straight_qrcode = detector.detectAndDecode(img)
if bbox is not None:
# Draw bounding box
n_lines = len(bbox)
for i in range(n_lines):
point1 = tuple(bbox[i][0].astype(int))
point2 = tuple(bbox[(i+1) % n_lines][0].astype(int))
cv2.line(img, point1, point2, (0, 255, 0), 3)
# Display decoded data
if data:
print(f"QR Code data: {data}")
cv2.putText(img, data, (50, 50),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
cv2.imshow('QR Code', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
# Usage
# detect_qr_code('qrcode.jpg')
5. 画像スティッチング(パノラマ)
import cv2
def create_panorama(images):
"""Stitch multiple images into panorama."""
# Create stitcher
stitcher = cv2.Stitcher_create()
# Stitch images
status, pano = stitcher.stitch(images)
if status == cv2.Stitcher_OK:
print("Panorama created successfully")
return pano
else:
print(f"Error: {status}")
return None
# Usage
img1 = cv2.imread('image1.jpg')
img2 = cv2.imread('image2.jpg')
img3 = cv2.imread('image3.jpg')
panorama = create_panorama([img1, img2, img3])
if panorama is not None:
cv2.imshow('Panorama', panorama)
cv2.waitKey(0)
パフォーマンスの最適化
GPU アクセラレーションを使用する
import cv2
# Check CUDA availability
print(f"CUDA devices: {cv2.cuda.getCudaEnabledDeviceCount()}")
# Upload to GPU
gpu_img = cv2.cuda_GpuMat()
gpu_img.upload(img)
# GPU operations (must use cv2.cuda module)
gpu_gray = cv2.cuda.cvtColor(gpu_img, cv2.COLOR_BGR2GRAY)
# Download from GPU
result = gpu_gray.download()
演算をベクトル化する
# ❌ SLOW: Python loops
for i in range(height):
for j in range(width):
img[i, j] = img[i, j] * 0.5
# ✅ FAST: NumPy vectorization
img = (img * 0.5).astype(np.uint8)
# ✅ FAST: OpenCV built-in functions
img = cv2.convertScaleAbs(img, alpha=0.5, beta=0)
動画でのマルチスレッド処理
import cv2
from threading import Thread
from queue import Queue
class VideoCapture:
"""Threaded video capture for better performance."""
def __init__(self, src):
self.cap = cv2.VideoCapture(src)
self.q = Queue(maxsize=128)
self.stopped = False
def start(self):
Thread(target=self._reader, daemon=True).start()
return self
def _reader(self):
while not self.stopped:
ret, frame = self.cap.read()
if not ret:
self.stop()
break
self.q.put(frame)
def read(self):
return self.q.get()
def stop(self):
self.stopped = True
self.cap.release()
# Usage
cap = VideoCapture(0).start()
while True:
frame = cap.read()
# Process frame...
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.stop()
よくある落とし穴と解決策
「BGR と RGB」の色の混同
OpenCV は BGR を使用しますが、ほとんどのほかのライブラリは RGB を使用します。
# ❌ Problem: Colors look wrong in matplotlib
img = cv2.imread('image.jpg')
plt.imshow(img) # Blue and red are swapped!
# ✅ Solution: Convert to RGB
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
plt.imshow(img_rgb)
# ✅ Alternative: Use OpenCV's imshow
cv2.imshow('Correct Colors', img)
cv2.waitKey(0)
「ウィンドウが閉じない」問題
適切にキーを処理しないと、ウィンドウが開いたままになります。
# ❌ Problem: Window frozen
cv2.imshow('Image', img)
# Program hangs!
# ✅ Solution: Always use waitKey
cv2.imshow('Image', img)
cv2.waitKey(0) # Wait for key press
cv2.destroyAllWindows()
「ビデオキャプチャが解放されない」問題
適切に解放しないと、カメラがロックされたままになります。
# ❌ Problem: Camera locked after crash
cap = cv2.VideoCapture(0)
# ... code crashes ...
# Camera still locked!
# ✅ Solution: Use try-finally
cap = cv2.VideoCapture(0)
try:
while True:
ret, frame = cap.read()
# ... process ...
finally:
cap.release()
cv2.destroyAllWindows()
「画像の変更」に関する混同
インプレースで変更する演算もあれば、新しい画像を返す演算もあります。
# In-place modification
cv2.rectangle(img, (10, 10), (100, 100), (0, 255, 0), 2) # Modifies img
# Returns new image
blurred = cv2.GaussianBlur(img, (5, 5), 0) # img unchanged
# ✅ Always use .copy() if you need original
img_copy = img.copy()
cv2.rectangle(img_copy, (10, 10), (100, 100), (0, 255, 0), 2)
「輪郭の階層構造」に関する誤解
findContours は、取得モードに応じて異なる構造を返します。
# External contours only (most common)
contours, hierarchy = cv2.findContours(
thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
)
# All contours with full hierarchy
contours, hierarchy = cv2.findContours(
thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE
)
# ⚠️ hierarchy structure: [Next, Previous, First_Child, Parent]
# Most use cases only need RETR_EXTERNAL
OpenCV はコンピュータービジョンの万能ツールです。最適化されたアルゴリズムを豊富に備えたライブラリと、Python の使いやすさを組み合わせることで、シンプルな画像処理から複雑なリアルタイムビジョンシステムまで、あらゆる用途に最適なツールとなっています。これらの基礎を習得すれば、どのようなコンピュータービジョンの課題にも取り組むための土台が身につきます。