1. はじめに
ウェブセキュリティにおいて、人間の操作を証明するために CAPTCHA(完全自動化されたパブリック・ターバック・コンピュータ区別テスト)が広く利用されている。自動化スクリプトによるアクセス制限が主流となる中、これらの認証コードを機械的に解析する技術の研究は、セキュリティ対策の強度を評価する上で重要な課題となっている。
本記事では、従来の画像処理パイプラインと深層学習モデルを組み合わせた CAPTCHA 解読システムの構築方法について解説する。
2. システムアーキテクチャ概観
CAPTCHA の種類には、計算問題、ドラッグ&ドロップ、音声入力などがあるが、ここでは最も基礎的な「文字列画像認識」に焦点を当てる。高い精度を実現するため、画像の前処理フェーズと認識フェーズを明確に分離する。
主要な前処理ステップは以下の通りである:
- グレースケール変換
- 二値化処理
- ノイズ除去(点・線)
- キャラクターセグメンテーション
これにより、OCR エンジンへの入力品質が向上し、認識精度が向上する。
3. 画像前処理の実装
OpenCV を用いた基本的な画像操作から始め、カスタムロジックを組み合わせて複雑な背景ノイズを排除する。
3.1 グレースケールと適応的閾値処理
カラー画像をグレースケールに変換した後、ローカルな照明変動を補正するために適応的閾値法を採用する。
import cv2
import numpy as np
def preprocess_image(image_path):
img = cv2.imread(image_path, cv2.IMREAD_COLOR)
# 色彩情報を除去して輝度情報のみに変換
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# ガウシアン適応二値化でテキスト部分を強調
binary_img = cv2.adaptiveThreshold(
gray_img,
255,
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY,
block_size=21,
C=10
)
return binary_img
3.2 フレームとノイズの除去
画像周囲の余計な枠線や、文字と混在するスパイラル線のようなノイズを除去する必要がある。
枠線除去は配列のスライス操作を用いて効率的に行い、ノイズ除去には近傍ピクセルの判定を利用する。
def remove_frame_and_noise(binary_img):
height, width = binary_img.shape
# 上下左右の境界部分を白画素にリセット(フレーム除去)
binary_img[:2, :] = 255
binary_img[-2:, :] = 255
binary_img[:, :2] = 255
binary_img[:, -2:] = 255
# スキャン線でノイズ削除
for y in range(1, height - 1):
for x in range(1, width - 1):
neighbor_white_count = 0
# 上下左右の近傍を確認
offsets = [(0, 1), (0, -1), (1, 0), (-1, 0)]
for dx, dy in offsets:
if binary_img[y + dy, x + dx] > 240:
neighbor_white_count += 1
# 多数決論理:周囲の白が多い場合はノイズとみなす
if neighbor_white_count >= 3:
binary_img[y, x] = 255
return binary_img
3.3 キャラクターの分割
文字が連結している場合、個々の文字として認識させるために切り出す必要がある。ここでは輪郭探索アルゴリズムを使用する方が、手動での BFS 実装よりも安定している。
import cv2
def segment_characters(cleaned_img):
# 輪郭を検出
contours, _ = cv2.findContours(
cleaned_img,
cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE
)
char_segments = []
h, w = cleaned_img.shape
for contour in contours:
x, y, c_width, c_height = cv2.boundingRect(contour)
# 小さな点や大きな領域(結合した文字の可能性)のフィルタリング
if 10 < c_width < 40 and 10 < c_height < 40:
roi = cleaned_img[y:y+c_height, x:x+c_width]
# 文字幅が極端に広い場合は再分割のロジックを追加可能
char_segments.append((x, y, c_width, c_height))
return sorted(char_segments, key=lambda k: k[0])
4. OCR による文字認識
Tesseract OCR エンジンを呼び出し、事前に分割された画像に対して文字列抽出を行う。単一文字ごとの認識モード `-psm 10` を指定することで、セグメントされた文字に対する精度を高めることができる。
from pytesseract import image_to_string
from PIL import Image
import os
def recognize_captcha(segmented_images_dir, base_name):
recognized_text = ""
file_list = [f for f in os.listdir(segmented_images_dir) if base_name in f]
file_list.sort(key=lambda x: int(x.split("-")[-1].split(".")[0]))
for idx, filename in enumerate(file_list):
filepath = os.path.join(segmented_images_dir, filename)
try:
# 英語のみをターゲットとし、一行ずつ処理
text = image_to_string(
Image.open(filepath),
lang='eng',
config='--psm 10'
)
recognized_text += text.strip()
except Exception as e:
print(f"Error processing {filename}: {e}")
return recognized_text
5. 深層学習アプローチとデータセット生成
従来の画像処理だけでは複雑なノイズに対応できない場合、CNN(畳み込みニューラルネットワーク)を用いた学習モデルが有効である。
モデルの学習には大量のラベル付きデータが必要となるため、プログラムによる CAPTCHA 画像の自動生成スクリプトが必要になる。
5.1 合成データの作成
以下の Python スクリプトは、ランダムな文字列を選択し、波形や色調変化を付与してトレーニング用のデータを生成する例である。
import random
import string
from PIL import Image, ImageDraw, ImageFont, ImageFilter
def generate_training_data(output_dir, count=1000):
characters = string.ascii_letters + string.digits
width, height = 120, 40
for i in range(count):
# ランダムな文字列を選択(長さ 4 文字固定など)
text_len = random.randint(4, 5)
captcha_text = "".join(random.sample(characters, text_len))
img = Image.new('RGB', (width, height), color=(255, 255, 255))
draw = ImageDraw.Draw(img)
# ランダムなフォントサイズと位置
font_size = random.randint(20, 30)
# フォント読み込み(環境依存のため簡易記述)
# font = ImageFont.truetype("arial.ttf", font_size)
offset_x = 10
for char in captcha_text:
# ランダムな回転と色付け
# 実際の生成ロジックではここで歪みを追加する
pass
# 保存
filename = os.path.join(output_dir, f"{captcha_text}_{i}.png")
# img.save(filename)
# 注意:実際の運用では distortions (ゆがみ)の処理クラスを実装する必要があります
5.2 モデル学習戦略
TensorFlow または Keras 等のフレームワークを使用して、ConvNet を構築する。画像をグリッド単位に分割し、各セグメントに対して分類タスクを行うアトミックな学習を行うことで、全体としての識別精度(Accuracy)を 75% 以上、単一文字レベルでは 98% 以上の到達を目指す。
データセットのサイズは少なくとも 10 万枚程度を推奨し、バッチ勾配降下法によって損失関数が収束するまでトレーニングを継続する。