tech.sinayaka.com

pythonで文字captcha画像の読み取り

2023-02-15
2026-07-08
7分
1234語
Python botpythonopencv
目次

    よくある文字形式のcaptcha画像の読み取りを「OpenCV」と「Tesseract OCR」で試してみました。
    今回扱う画像はこちらです。

    captcha
    captcha

    試したい場合はダウンロードしてプログラムと同じディレクトリに置いてください。

    事前準備

    windowsの場合は事前にTesseractOCRの exeファイルをインストールする必要があります。 初回はJupyterNotebook上で下記コードを実行してライブラリをインストールしておきます。

    !pip install opencv-python pyocr tesseract

    正常にインストールできていれば下記コードで画像が表示されるはずです。

    import cv2
    import matplotlib.pyplot as plt
    
    img = cv2.imread('captcha.png')
    plt.title('orginal')
    plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
    plt.show()
    original
    original

    2値化

    文字認識しやすいように白黒のみの画像に変換します。

    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    plt.title('img_gray')
    plt.imshow(cv2.cvtColor(img_gray, cv2.COLOR_BGR2RGB))
    plt.show()
    
    ret, img_binary = cv2.threshold(img_gray, 0, 255,  cv2.THRESH_BINARY + cv2.THRESH_TRIANGLE)
    plt.title('img_binary THRESH_TRIANGLE')
    plt.imshow(cv2.cvtColor(img_binary, cv2.COLOR_BGR2RGB))
    plt.show()
    
    ret, img_binary = cv2.threshold(img_gray, 0, 255,  cv2.THRESH_BINARY + cv2.THRESH_OTSU)
    plt.title('img_binary THRESH_OTSU')
    plt.imshow(cv2.cvtColor(img_binary, cv2.COLOR_BGR2RGB))
    plt.show()
    binary
    binary

    大津の2値化のほうがよさげです(# 名前もカッコイイ)

    ゴミ除去

    ハフ変換を用いて画像内の線分を検出し、検出した線分を黒く塗ることで線を除去します。
    小さいゴミは後で除去できるので「細い線」で「砕き」ます。

    import numpy as np
    
    output =img_binary.copy()
    color_output = cv2.cvtColor(output, cv2.COLOR_GRAY2BGR)
    for a in range(6) :
        lines = cv2.HoughLinesP(output, rho=0.6, theta=np.pi/360, threshold=10, minLineLength=21-a, maxLineGap=1.0)
        if lines is not None :
            for line in lines:
                x1, y1, x2, y2 = line[0]
                cv2.line(output, (x1, y1), (x2, y2), (0, 0, 0), 1)
                cv2.line(color_output, (x1, y1), (x2, y2), (0, 255, 0), 1)
            break
    
    plt.title('color_output')
    plt.imshow(cv2.cvtColor(color_output, cv2.COLOR_BGR2RGB))
    plt.show()
    
    plt.title('removed_line')
    plt.imshow(cv2.cvtColor(output, cv2.COLOR_BGR2RGB))
    plt.show()
    removed_line
    removed_line

    エッジ検出を行い、小さい領域は除去します。
    その後、検出したエッジにマスクをかけ、その領域以外を除去します。

    # エッジ検出
    contours, hierarchy = cv2.findContours(output, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    
    # ちいさい領域削除 the region of interest (ROI)
    roi = [obj for obj in contours if not cv2.contourArea(obj)<=12]
    
    # 検出枠表示用に入力画像をカラーデータに変換する
    output_color = cv2.cvtColor(output, cv2.COLOR_GRAY2BGR)
    for contour in roi:
        (x, y, w, h) = cv2.boundingRect(contour)
        
        region = output_color[y:y+h, x:x+w]
        
        # 最小外接矩形を求める
        rect = cv2.minAreaRect(contour)
        
        # 矩形の4つの角の座標を求める
        box = cv2.boxPoints(rect)
        box = np.int0(box)
        
        # 矩形を描画する
        cv2.drawContours(output_color, [box], 0, (0, 255, 0), 1)
        
    plt.title(f'roi length={len(roi)}')
    plt.imshow(cv2.cvtColor(output_color, cv2.COLOR_BGR2RGB))
    plt.show()
    
    # 興味ある部分のみ白マスク
    mask = np.zeros(output.shape[:2], dtype=np.uint8)
    cv2.fillPoly(mask, roi, 255)
    
    # 白マスクしたとこだけ残す
    output = cv2.bitwise_and(output, output, mask=mask)
    
    plt.title('result')
    plt.imshow(cv2.cvtColor(output, cv2.COLOR_BGR2RGB))
    plt.show()
    result
    result

    OCRでテキスト読み取り

    テキストを読み取るための下準備ができましたのでOCRで読み取ってみます。
    事前に検出する文字が決まっている場合はwhitelistを指定すると検出しやすいです。
    OCRインストール先のconfigフォルダにalphabetというファイル名で保存しておきます。

    tessedit_char_whitelist ABCDEFGHIJKLMNOPQRSTUVWXYZ

    「tesseract_layout」は「10」の「画像を1つの文字として扱う」と指定しておきます。

    import pyocr
    from PIL import Image
    
    # ndarrayをpillowのImageに変換
    pil_img = Image.fromarray(cv2.cvtColor(output, cv2.COLOR_BGR2RGB))
    
    # OCRエンジンを取得(事前にOSでインストールしておく必要がある)
    pyocr.tesseract.TESSERACT_CMD = 'C:\\Program Files\\Tesseract-OCR\\tesseract.exe'
    engines = pyocr.get_available_tools()
    engine = engines[0]
    
    builder = pyocr.builders.TextBuilder(tesseract_layout=10)
    builder.tesseract_configs.append("alphabet")
    
    # 画像の文字を読み込む
    text = engine.image_to_string(pil_img, lang="eng", builder=builder)
    
    print(f'"{text}"')
    OCR
    OCR

    一応傾き補正もしてみましたが、あまり結果が変わらないので取り入れませんでした。

    adjust_skew = output.copy()
    contours, hierarchy = cv2.findContours(adjust_skew, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    
    # 検出枠表示用に入力画像をカラーデータに変換する
    output_color = cv2.cvtColor(output, cv2.COLOR_GRAY2BGR)
    
    for contour in contours:
        (x, y, w, h) = cv2.boundingRect(contour)
        
        # ある程度横長のものは無視する
        if w >= 35 : continue
        region = adjust_skew[y:y+h, x:x+w]
    
        # 最小外接矩形を求める
        rect = cv2.minAreaRect(contour)
    
        # 矩形の4つの角の座標を求める
        box = cv2.boxPoints(rect)
        box = np.int0(box)
        
        # 矩形を描画する
        cv2.drawContours(output_color, [box], 0, (0, 255, 0), 1)
        
        # 矩形の傾きを計算する
        angle = rect[2]
        
        if angle > 45:
            angle = -(angle - 90)
    
        else :
            angle = -angle
        center = (w / 2, h / 2)
    
        # 回転のための変換行列の生成 
        # cv2.getRotationMatrix2D(入力画像の回転中心, 回転角度 単位は度 - 正の値:反時計回り, 等方性スケーリング係数 - 拡大縮小の倍率)
        M = cv2.getRotationMatrix2D(center, -angle, 1.0)
    
        # v2.warpAffine(元の画像, cv2.getRotationMatrix2Dで生成した2*3の変換行列, 出力する画像サイズ(縦の高さ, 横の幅))
        rotated_region = cv2.warpAffine(region, M, (w, h), flags=cv2.INTER_CUBIC, borderMode=cv2.BORDER_REPLICATE)
        adjust_skew[y:y+h, x:x+w] = rotated_region
        
    plt.title('before')
    plt.imshow(cv2.cvtColor(output_color, cv2.COLOR_BGR2RGB))
    plt.show()
    
    contours, hierarchy = cv2.findContours(adjust_skew, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    # 検出枠表示用に入力画像をカラーデータに変換する
    output_color = cv2.cvtColor(adjust_skew, cv2.COLOR_GRAY2BGR)
    for contour in contours:
        (x, y, w, h) = cv2.boundingRect(contour)
        region = output_color[y:y+h, x:x+w]
        rect = cv2.minAreaRect(contour)
        box = cv2.boxPoints(rect)
        box = np.int0(box)
        cv2.drawContours(output_color, [box], 0, (0, 255, 0), 1)
    
    plt.title('after')
    plt.imshow(cv2.cvtColor(output_color, cv2.COLOR_BGR2RGB))
    plt.show()
    skew
    skew

    おわりに

    今回は比較的簡単な文字captcha画像を扱いましたが、ノイズが文字にかかった場合などは誤認識が多発します。
    OCRの部分を機械学習データを自作することで、より精度を上げることができると思います。




    Copyright 2026
    サイトマップ