前言
本文分析一套針對 QQ 早期驗證碼(49×20 像素、4 字元)的辨識系統,採用像素特徵向量比對法:將字元二值化後,按行計算黑色像素位模式組成 20 維向量,與預建特徵庫比對,差異行數 < 4 視為匹配。程式碼包含特徵庫建立、序列化存取、字元切割與辨識完整流程。
核心資料結構
// 特徵庫容量:100,000 筆
public static int DataCount = 0;
public static ushort[,] FeatureVectors = new ushort[100000, 20]; // 每筆 20 行特徵
public static byte[,] CharSize = new byte[100000, 2]; // 寬、高
public static byte[] CharLabels = new byte[100000]; // 對應字元 (ASCII)
// 待辨識暫存
public ushort[] CurrentVector = new ushort[20];
public byte CurrentWidth, CurrentHeight;
關鍵演算法流程
1. 二值化與字元切割
- 亮度
< 0.999 視為前景(黑)。
- 水平投影尋找字元邊界:連續有前景像素的欄位區間。
- 垂直投影裁剪上下邊界,取得有效區域。
- 寬度 > 16 像素視為噪聲/黏連,捨棄。
2. 特徵向量產生(行位模式)
// 每行 16 位元對應 16 欄像素,LSB 對應左邊
ushort[] bitMask = { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768 };
for (int row = y1; row <= y2; row++)
{
ushort pattern = 0;
for (int col = x1; col < x2; col++)
if (binary[col, row]) pattern |= bitMask[col - x1];
CurrentVector[row - y1] = pattern;
}
3. 特徵庫比對
public string MatchCharacter()
{
for (int i = 0; i < DataCount; i++)
{
if (CharSize[i, 0] != CurrentWidth || CharSize[i, 1] != CurrentHeight) continue;
int diffRows = 0;
for (int r = 0; r < 20; r++)
if (FeatureVectors[i, r] != CurrentVector[r]) diffRows++;
if (diffRows < 4) // 容差閾值
return ((char)CharLabels[i]).ToString();
}
return "";
}
4. 特徵庫序列化(二進位格式)
| 欄位 | 長度 | 說明 |
| 筆數 | 4 bytes (int, Big-Endian) | |
| 每筆特徵 | 20 × 2 bytes (ushort, Big-Endian) | 20 行向量 |
| 寬度 | 1 byte | |
| 高度 | 1 byte | |
| 字元標籤 | 1 byte | ASCII code |
完整類別架構
namespace QQ
{
public class CaptchaRecognizer
{
public CaptchaRecognizer(Bitmap captchaImage) { _image = captcha; }
// 靜態工具方法:int/ushort ↔ byte[] (Big-Endian)
public static void IntToBytes(int val, byte[] buf) { ... }
public static void UShortToBytes(ushort val, byte[] buf) { ... }
public static int BytesToInt(byte[] buf) { ... }
public static ushort BytesToUShort(byte[] buf) { ... }
// 特徵庫持久化
public static void SaveLibrary(string path) { ... }
public static void LoadLibrary(string path) { ... }
// 核心流程
public void ExtractFeatures() { /* 切割 + 向量化 + 加入庫(標籤=空白) */ }
public string Recognize() { /* 切割 + 向量化 + 比對拼接 */ }
// 私有輔助
private bool ExistsInLibrary() { ... }
private void AddToLibrary() { ... }
}
}
效能與準確率分析
| 指標 | 數值/說明 |
| 單字元比對時間 | ~100,000 × 20 次整數比較 ≈ 2M 操作,現代 CPU < 5ms |
| 辨識率 | 特徵庫 8 萬+ 筆可達 80%+,15 萬筆可更高 |
| 記憶體佔用 | 約 100,000 × (40+2+1) ≈ 4.3 MB |
| 多執行緒適合度 | 高(純邏輯運算、無共享寫入) |
限制與改進方向
- 固定尺寸假設:僅支援 49×20、4 字元、等寬切割,通用性低。
- 簡單閾值二值化:對雜訊、干擾線、字元黏連抗性弱。
- 特徵向量維度固定:高度固定 20 行,字元縮放會失效。
- 改進建議:
- 引入投影切割 + 連通元件分析處理黏連字元。
- 使用關鍵點特徵(SIFT/ORB 簡化版)或 CNN 深度學習模型。
- 採用動態時間規整 (DTW) 處理字元寬度變化。
- 特徵庫採用 KD-Tree 或 LSH 加速最近鄰搜尋。
法律與道德提醒
驗證碼辨識技術僅供學術研究與安全測試使用。繞過驗證碼進行自動化註冊、爬蟲濫用、暴力破解等行為可能違反《電腦犯罪條例》或網站服務條款,請遵守法律法規與機器人協議 (robots.txt)。
- 上一篇文章: 走進 C#(三):語言演進脈絡、核心特性與編譯器入門
- 下一篇文章: C# 呼叫 Windows API 列舉區域網路主機 (WNetEnumResource 實作)
Copyright ©
程式碼之家 All Rights Reserved