alt text

由於要整合一個沒有 API 的服務,需要簡單地通過驗證碼。所以研究了一下如何處理文字型的驗證碼。我們會經過四個步驟:

  1. 圖片轉灰階
  2. 圖片轉二值化(黑白)
  3. 圖片按字元切割
  4. 模板比對

灰階的本質

首先先來認識一下,灰階的本質。什麼是灰?

人眼視覺生理的特性:只要三原色等量混合,色相就會互相抵消,只剩下「亮度」這個維度。所以灰階本質上就是 只保留亮度資訊,丟棄色相資訊 的結果。

所以當 R = G = B 的時候,顏色就只剩下亮度。

舉例:

原始像素: R=150, G=90, B=210,算平均: (150+90+210)/3 = 150

新像素: R=150, G=150, B=150 (三者相等 → 無色相,只剩亮度)

這就是求灰階的 平均法

所以查看灰階的色碼,會發現 RGB 的值都是相同的。從黑到白: 00 00 00 ~ FF FF FF

圖片轉灰階

底下的方法,把圖片的某一個像素轉換成灰階,當我們把全部的像素進行下列計算,就會得到一個灰階圖。

// 顏色的整數值 16711680,轉換成 HEX 就是 FF0000
$rgb = imagecolorat($im, $x, $y);
// 取得 RGB 值 0 ~ 255
$index = imagecolorsforindex($im, $rgb);

// 計算灰階值 (Grayscale)
$r = $index['red'];
$g = $index['green'];
$b = $index['blue'];
$gray = ($r + $g + $b) / 3;

圖片轉二值化(黑白)

這個步驟是為了區分圖片的前景與背景,由於黑白中間的分野值大約落在 128 左右,所以如果一個像素小於該值(視為黑點),我們設定為 1,反之設定為 0 (視為白點)。然後把這個像素值收集起來,就獲得 二值化 的結果。

$threshold = 128;
$binarization = '';
if ($gray <= $threshold) {
    $binarization = '1';
} else {
    $binarization = '0';
}

圖片按字元切割

因為這個例子圖片比較單純,剛好是 10x10 的圖片四張拼貼而成。 透過上述的灰階 + 二值化,然後切割字元,會取得每一個字元剛好 100 個零與一的組合。

<?php

$im = imagecreatefrompng('img.png');
$height = imagesy($im); // 應該是 10px
$width = imagesx($im); // 應該是 40px
$digitWidth = $width / 4; // 4個數字,每個數字寬度 10px

$digits = [];
$binarization = '';
$x = 0;
$y = 0;
$offset = 0;
for ($i = 0; $i < 4; $i++) {
    $offset = $i * $digitWidth;
    for ($y = 0; $y < $height; $y++) {
        for ($x = 0; $x < $digitWidth; $x++) {
            // 顏色的整數值 16711680,轉換成 HEX 就是 FF0000
            $rgb = imagecolorat($im, $x + $offset, $y);
            // 取得 RGB 值 0 ~ 255
            $index = imagecolorsforindex($im, $rgb);

            // 計算灰階值 (Grayscale)
            $r = $index['red'];
            $g = $index['green'];
            $b = $index['blue'];
            $gray = ($r + $g + $b) / 3;


            $threshold = 128;
            if ($gray <= $threshold) {
                $binarization .= '1';
            } else {
                $binarization .= '0';
            }
        }
    }
    $digits[] = $binarization;
    $binarization = '';
}

模板比對

接著就是多跑幾遍,取得每一個數字的組合作為模板。最後只要取出的數字跟模板匹配,就能夠辨識出數字了。