A Casual Guide to Breaking CAPTCHA Text Recognition

由於要整合一個沒有 API 的服務,需要簡單地通過驗證碼。所以研究了一下如何處理文字型的驗證碼。我們會經過四個步驟:
- 圖片轉灰階
- 圖片轉二值化(黑白)
- 圖片按字元切割
- 模板比對
灰階的本質
首先先來認識一下,灰階的本質。什麼是灰?
人眼視覺生理的特性:只要三原色等量混合,色相就會互相抵消,只剩下「亮度」這個維度。所以灰階本質上就是 只保留亮度資訊,丟棄色相資訊 的結果。
所以當 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 = '';
}
模板比對
接著就是多跑幾遍,取得每一個數字的組合作為模板。最後只要取出的數字跟模板匹配,就能夠辨識出數字了。