来自:http://outofmemory.cn/code-snippet/2037/c-pojie-yanzhengma-example-code
验证码破解是一个很大的课题,也有很多种不同的方式,下面代码采用的方法是首先准备验证码图片的样本图片。然后将验证码图片做灰度处理,并根据字符间距切割验证码图片,最后将切割后的验证码小图和样本之间做余袨值比较,从而计算出验证码图片的字符。
如下是c#代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.IO;
using System.Configuration;
namespace ConsoleApplication4
{
/// <summary>
/// 根据样本做验证码破解
///
/// 需要在.config文件中的appSettings配置节中添加key为sampleOcr.sampleDir value设置为样本图片所在路径
/// 验证码:https://investorservice.cfmmc.com/ https://investorservice.cfmmc.com/veriCode.do?t=1335521167762&ip=202.99.16.22
///
/// outofmemory.cn 20120427
/// 100个样例准确数为88个,错误主要发生在389这三个字符的混淆上
/// </summary>
public abstract class SampleOcr
{
/// <summary>
/// 灰度中间值
/// </summary>
static int MiddleGrayValue = 200;
/// <summary>
/// 分割图片的差异容忍度
/// </summary>
static int ColorToleranceForSplit = 30;
/// <summary>
/// 样本字典
/// </summary>
static Dictionary<string, Bitmap> _samples;
/// <summary>
/// 破解验证码
/// </summary>
/// <param name="bm">验证码图片</param>
/// <returns>验证码文本</returns>
public static string Ocr(Bitmap bm)
{
//做灰度处理
GrayByPixels(bm);
bm = RemoveVerticalSpaceRegion(bm);
Bitmap[] splitBms = SplitBitmaps(bm);
char[] result = new char[splitBms.Length];
for (int i = 0; i < splitBms.Length; i++)
{
result[i] = OcrChar(splitBms[i]);
splitBms[i].Dispose();
}
return new string(result);
}
/// <summary>
/// 分割图片
/// </summary>
/// <param name="bm">图片</param>
/// <returns>分割后的图片对象</returns>
static Bitmap[] SplitBitmaps(Bitmap bm)
{
//找出垂直分割线
List<int> removeXs = new List<int>();
for (int x = 0; x < bm.Width; x++)
{
bool hasDiffPoint = false;
Color color = Color.White;
for (int y = 0; y < bm.Height; y++)
{
if (y == 0)
{
color = bm.GetPixel(x, y);
}
else
{
Color currentColor = bm.GetPixel(x, y);
int diff = CalculateColorDifference(currentColor, color);
if (diff > ColorToleranceForSplit)
{
hasDiffPoint = true;
break;
}
// color = currentColor;
}
}
if (!hasDiffPoint)
{
removeXs.Add(x);
}
}
//根据空白区域,计算各个字符的位图
List<Rectangle> charRects = new List<Rectangle>();
for (int i = 1; i < removeXs.Count; i++)
{
int diff = removeXs[i] - removeXs[i - 1];
if (diff > 5)
{
if (diff >= 20)
{
Rectangle rect = new Rectangle(removeXs[i - 1], 0, diff / 2, bm.Height);
charRects.Add(rect);
rect = new Rectangle(removeXs[i - 1] + diff / 2, 0, diff / 2, bm.Height);
charRects.Add(rect);
}
else
{
Rectangle rect = new Rectangle(removeXs[i - 1], 0, diff, bm.Height);
charRects.Add(rect);
}
}
}
int count = charRects.Count;
Bitmap[] charBms = new Bitmap[count];
int charBmIndex = 0;
foreach (Rectangle item in charRects)
{
Bitmap bmChar = bm.Clone(item, bm.PixelFormat);
charBms[charBmIndex] = bmChar;
charBmIndex += 1;
}
return charBms;
}
/// <summary>
/// 解析字符
/// </summary>
/// <param name="bm">分割后的小图</param>
/// <returns>字符</returns>
static char OcrChar(Bitmap bm)
{
Dictionary<string, Bitmap> samples = LoadSamples();
double diff = .0;
string mayBe = null;
foreach (string key in samples.Keys)
{
double diffRate = CalcImageDiffRate(samples[key], bm);
if (diffRate == 1)
return key[0];
if (diffRate > diff)
{
mayBe = key;
diff = diffRate;
}
}
if (mayBe == null) throw new ApplicationException();
return mayBe[0];
}
/// <summary>
/// 载入样本字典
/// </summary>
/// <returns>样本字典</returns>
private static Dictionary<string, Bitmap> LoadSamples()
{
if (_samples == null)
{
_samples = new Dictionary<string, Bitmap>();
string sampleDir = ConfigurationManager.AppSettings["sampleOcr.sampleDir"] ?? @"D:\SampleOcr\samples";
DirectoryInfo dirInfo = new DirectoryInfo(sampleDir);
FileInfo[] files = dirInfo.GetFiles("*.jpg");
foreach (FileInfo item in files)
{
Bitmap bm = (Bitmap)Bitmap.FromFile(item.FullName);
string key = Path.GetFileNameWithoutExtension(item.FullName);
_samples.Add(key, bm);
}
}
return _samples;
}
/// <summary>
/// 根据RGB,计算灰度值
/// </summary>
/// <param name="posClr">Color值</param>
/// <returns>灰度值,整型</returns>
static int GetGrayNumColor(System.Drawing.Color posClr)
{
return (posClr.R * 19595 + posClr.G * 38469 + posClr.B * 7472) >> 16;
}
/// <summary>
/// 灰度转换,逐点方式
/// </summary>
static void GrayByPixels(Bitmap bm)
{
for (int i = 0; i < bm.Height; i++)
{
for (int j = 0; j < bm.Width; j++)
{
int tmpValue = GetGrayNumColor(bm.GetPixel(j, i));
bm.SetPixel(j, i, Color.FromArgb(tmpValue, tmpValue, tmpValue));
}
}
}
/// <summary>
/// 删除垂直方向上的空白区域
/// </summary>
/// <param name="bm">源图片</param>
/// <returns>删除空白之后的图片</returns>
static Bitmap RemoveVerticalSpaceRegion(Bitmap bm)
{
int topSpaceHeight = 0;
for (int y = 0; y < bm.Height; y++)
{
bool hasDiffPoint = false;
Color color = Color.White;
for (int x = 0; x < bm.Width; x++)
{
if (x == 0)
{
color = bm.GetPixel(x, y);
}
else
{
Color currentColor = bm.GetPixel(x, y);
int diff = CalculateColorDifference(currentColor, color);
if (diff > ColorToleranceForSplit)
{
hasDiffPoint = true;
break;
}
}
}
if (hasDiffPoint)
{
break;
}
else
{
topSpaceHeight += 1;
}
}
int bottomSpaceHeight = 0;
for (int y = bm.Height - 1; y > 0; y--)
{
bool hasDiffPoint = false;
Color color = Color.White;
for (int x = 0; x < bm.Width; x++)
{
if (x == 0)
{
color = bm.GetPixel(x, y);
}
else
{
Color currentColor = bm.GetPixel(x, y);
int diff = CalculateColorDifference(currentColor, color);
if (diff > ColorToleranceForSplit)
{
hasDiffPoint = true;
break;
}
color = currentColor;
}
}
if (hasDiffPoint)
{
break;
}
else
{
bottomSpaceHeight += 1;
}
}
Rectangle rectValid = new Rectangle(0, topSpaceHeight, bm.Width, bm.Height - topSpaceHeight - bottomSpaceHeight);
Bitmap newBm = bm.Clone(rectValid, bm.PixelFormat);
bm.Dispose();
return newBm;
}
private static double CalcImageDiffRate(Bitmap bmSample, Bitmap bmCalc)
{
int[] eSample = new int[bmSample.Height];
int[] eCalc = new int[bmSample.Height];
for (int y = 0; y < bmSample.Height; y++)
{
eSample[y] = GetHorizontalValue(bmSample, y);
eCalc[y] = GetHorizontalValue(bmCalc, y);
}
return GetCosine(eSample, eCalc);
}
/// <summary>
/// 获得向量的cos值
/// </summary>
/// <param name="e1"></param>
/// <param name="e2"></param>
/// <returns></returns>
static double GetCosine(int[] e1, int[] e2)
{
double fenzi = 0;
for (int i = 0; i < e1.Length; i++)
{
fenzi += e1[i] * e2[i];
}
double fenmuLeft = 0;
double fenmuRight = 0;
for (int i = 0; i < e1.Length; i++)
{
fenmuLeft += e1[i] * e1[i];
fenmuRight += e2[i] * e2[i];
}
double fenmu = Math.Sqrt(fenmuLeft) * Math.Sqrt(fenmuRight);
if (fenmu == 0.0) return 0;
return fenzi / fenmu;
}
/// <summary>
/// 计算水平方向上的差异点数
/// </summary>
/// <param name="bm">位图</param>
/// <param name="y">y坐标值</param>
/// <returns>差异点数</returns>
private static int GetHorizontalValue(Bitmap bm, int y)
{
if (y >= bm.Height) return 0;
int val = 0;
for (int x = 0; x < bm.Width; x++)
{
Color color = bm.GetPixel(x, y);
int grayVal = GetColorGrayValue(color);
if (grayVal > MiddleGrayValue)
{
val |= (1 << x);
}
}
return val;
}
static int GetColorGrayValue(Color color)
{
return (int)(.299 * color.R + .587 * color.G + .114 * color.B);
}
/// <summary>
/// 计算颜色之间的差值,这个只是一个简单的计算,真正的色差计算很复杂
/// </summary>
/// <param name="colorA">A色</param>
/// <param name="colorB">B色</param>
/// <returns>差值</returns>
static int CalculateColorDifference(Color colorA, Color colorB)
{
int diff = GetColorGrayValue(colorA) - GetColorGrayValue(colorB);
return Math.Abs(diff);
}
}
}