如何识别高级的验证码

 

来源:转载 作者:佚名 时间:2008-09-15 09:31:25

分享到:

    ==Ph4nt0m Security Team==
                      Issue 0x02, Phile #0x09 of 0x0A
|=---------------------------------------------------------------------------=|
|=-----------------------=[  如何识别高级的验证码  ]=------------------------=|
|=---------------------------------------------------------------------------=|
|=---------------------------------------------------------------------------=|
|=----------------------=[      By moonblue333    ]=------------------------=|
|=-------------------=[  <moonblue333_at_hotmail.com>  ]=--------------------=|
|=---------------------------------------------------------------------------=|
一、验证码的基本知识
    1. 验证码的主要目的是强制人机交互来抵御机器自动化攻击的。
    2. 大部分的验证码设计者并不得要领,不了解图像处理,机器视觉,模式识别,人工智能
的基本概念。
    3. 利用验证码,可以发财,当然要犯罪:比如招商银行密码只有6位,验证码形同虚设,计
算机很快就能破解一个有钱的账户,很多帐户是可以网上交易的。
    4. 也有设计的比较好的,比如Yahoo,Google,Microsoft等。而国内Tencent的中文验证
码虽然难,但算不上好。
二、人工智能,模式识别,机器视觉,图像处理的基本知识
    1)主要流程:
    比如我们要从一副图片中,识别出验证码;比如我们要从一副图片中,检测并识别出一张
人脸。 大概有哪些步骤呢?
    1.图像采集:验证码呢,就直接通过HTTP抓HTML,然后分析出图片的url,然后下载保存就
可以了。 如果是人脸检测识别,一般要通过视屏采集设备,采集回来,通过A/D转操作,存为
数字图片或者视频频。
    2.预处理:检测是正确的图像格式,转换到合适的格式,压缩,剪切出ROI,去除噪音,灰度
化,转换色彩空间这些。
    3.检测:车牌检测识别系统要先找到车牌的大概位置,人脸检测系统要找出图片中所有
的人脸(包括疑似人脸);验证码识别呢,主要是找出文字所在的主要区域。
    4.前处理:人脸检测和识别,会对人脸在识别前作一些校正,比如面内面外的旋转,扭曲
等。我这里的验证码识别,“一般”要做文字的切割
    5.训练:通过各种模式识别,机器学习算法,来挑选和训练合适数量的训练集。不是训练
的样本越多越好。过学习,泛化能力差的问题可能在这里出现。这一步不是必须的,有些识
别算法是不需要训练的。
    6.识别:输入待识别的处理后的图片,转换成分类器需要的输入格式,然后通过输出的类
和置信度,来判断大概可能是哪个字母。识别本质上就是分类。
    2)关键概念:
    图像处理:一般指针对数字图像的某种数学处理。比如投影,钝化,锐化,细化,边缘检测,
二值化,压缩,各种数据变换等等。
    1.二值化:一般图片都是彩色的,按照逼真程度,可能很多级别。为了降低计算复杂度,
方便后续的处理,如果在不损失关键信息的情况下,能将图片处理成黑白两种颜色,那就最好
不过了。
    2.细化:找出图像的骨架,图像线条可能是很宽的,通过细化将宽度将为1,某些地方可能
大于1。不同的细化算法,可能有不同的差异,比如是否更靠近线条中间,比如是否保持联通
行等。
    3.边缘检测:主要是理解边缘的概念。边缘实际上是图像中图像像素属性变化剧烈的地
方。可能通过一个固定的门限值来判断,也可能是自适应的。门限可能是图像全局的,也可
能是局部的。不能说那个就一定好,不过大部分时候,自适应的局部的门限可能要好点。被
分析的,可能是颜色,也可能是灰度图像的灰度。
    机器视觉:利用计算机来模式实现人的视觉。 比如物体检测,定位,识别。按照对图像
理解的层次的差别,分高阶和低阶的理解。
    模式识别:对事物或者现象的某种表示方式(数值,文字,我们这里主要想说的是数值),
通过一些处理和分析,来描述,归类,理解,解释这些事物,现象及其某种抽象。
    人工智能:这种概念比较宽,上面这些都属于人工智能这个大的方向。简单点不要过分
学院派的理解就是,把人类的很“智能”的东西给模拟出来协助生物的人来处理问题,特别是
在计算机里面。
三、常见的验证码的破解分析
    以http://libcaca.zoy.org/wiki/PWNtcha这里PWNtcha项目中的资料为例分析,各种验
证码的破解。(方法很多,仅仅从我个人乍看之下觉得可行的方法来分析)
    1)Authimage


    使用的反破解技巧:
    1.不连续的点组成字符
    2.有一定程度的倾斜
    设计不好的地方:
    1.通过纵横的直方图投影,可以找到字幕区域
    2.通过Hough变换,适当的参数,可以找到近似的横线,可以做倾斜矫正
    3.字符串的倾斜式面内的,没有太多的破解难度
    4.字母宽度一定,大小一定
    2)Clubic


    使用的反破解技巧:
    1.字符是手写体
    设计不好的地方:
    1.检测切割阶段没有任何技术含量,属于设计的比较丑的
    2.只有数字,而且手写体变化不大
    3.表面看起来对识别阶段有难度,仔细分析,发现几乎不用任何高级的训练识别算法,就
固定的招某些像素点是否有色彩就够了
    3)linuxfr.org


    使用的反破解技巧:
    1.背景颜色块
    2.前景的横线或矩形
    设计不好的地方:
    1.背景色是单一色块,有形状,通过Region-Growth区域增长来很容易把背景给去掉
    2.前景色是标准的线条,色彩单一
    3.字母无粘连
    4.都是印刷体
    4)Ourcolony


    使用的反破解技巧:
    1.设计的太低级,不屑于去评价
    设计不好的地方:
    1.这种验证码,设计的最丑,但还是能把菜鸟搞定,毕竟学计算机的少,搞这个破解的更
少,正所谓隔行如隔山
    5)LiveJournal


    使用的反破解技巧:
    1.这个设计略微好点,使用个随机噪音,而且作为前景
    2.字母位置粗细都有变化
    设计不好的地方:
    1.字母没有粘连
    2.噪音类型单一
    3.通过在X轴的直方图投影,能准确分割字幕
    4.然后在Y周作直方图投影,能准确定位高度
    5.识别阶段,都是印刷体,简单地很
四、网上的一些高级验证码
    1)ICQ


    2)IMDb


    3)MS MVPS


    4)MVN Forum


    这些类型是被很多人认为比较难得类型,分析一下可以发现,字符检测,定位和分割都不
是难。 唯一影响识别率的是IMDBb和MVPS这两类,字体变形略大。
    总体来说,这些类型的破解也不难,很容易做到50%以上的识别率。
五、高级验证码的破解分析
    时间关系,我简单介绍如何利用图像处理和模式识别技术,自动识别比较高级的验证码。
(以风头正劲的Google为例)


    1)至少从目前的AI的发展程度看,没有简单的做法能自动处理各种不同的验证码,即使
能力很强,那么系统自然也十分复杂强大。所以,要想在很简单的算法实现比较高级的验证
码破解,必须分析不同验证码算法的特点:
    作为一般的图像处理和计算机视觉,会考虑色彩,纹理,形状等直接的特征,同时也考虑
直方图,灰度等统计特征,还考虑FFT,Wavelet等各种变换后的特征。但最终目标都是
Dimension Reduction(降维)然后利于识别,不仅仅是速度的考虑。从图像的角度看,很多系
统都考虑转换为灰度级甚者黑白图片。
    Google的图片可以看出,颜色变化是虚晃一枪,不存在任何处理难度。难度是字体变形
和字符粘连。
    如果能成功的分割字符,那么后期识别无论是用SVM等分类算法,还是分析笔顺比划走向
来硬识别,都相对好做。
    2)图像处理和粘连分割
    代码中的part1目录主要完成图像预处理和粘连字符分割
    001:将图像从jpg等格式转换为位图便于处理
    002:采用Fix/Adaptive的Threshold门限算法,将图片Bin-Value二值化。
    (可用003算法)
    003:采用OSTU分水岭算法,将图片Bin-Value二值化。
    (更通用,大部分时候效果更好)
    005:获取ROI感兴趣的区域。
    006:Edge Trace边缘跟踪。
    007:Edge Detection边界检测。
    008:Thin细化去骨架。
    009:做了一些Tidy整理。
(这个一般要根据特定的Captcha算法调整)
    010:做切割,注意图片中红色的交叉点。
    011:将边缘检测和骨干交叉点监测的图像合并。
(合并过程可以做分析: 比如X坐标偏移门限分析,交叉点区域纹理分析,线条走势分析,
等等各种方法,找出更可能的切分点和分离后部件的组合管理。)


    代码:(代码质量不高,从其他项目拷贝过来,简单修改的。)
    查看代码(./pstzine_09_01.txt)

Copy code
//SuperImage.h
#ifndef _SUPER_IMAGE_
#define _SUPER_IMAGE_
typedef short Pixel;
//
template <class T>
class SuperImage
{
public:
    SuperImage(const int w, const int h);
    ~SuperImage();
public:
    T* data;
    int width;
    int height;
};
//
class EnhancedImage
{
public:
    SuperImage<Pixel>* superImage;
public:
    EnhancedImage();
    ~EnhancedImage();
    int load(const char* name);
    int save(const char* name);
    int copy(EnhancedImage* copy);
    int film(EnhancedImage* film);
    int binv();
    int otsu();
    int line();
    int roii();
    int trac();
    int edge();
    int thin();
    int tidy();   
    int kerf();   
    int join();
};
#endif
//SuperImage.cpp
#include "superImage.h"
#include "ximage.h"
template <class T>
SuperImage<T>::SuperImage(const int w, const int h)
{
    width = w;
    height = h;
    data = new T[w * h];
};
template <class T>
SuperImage<T>::~SuperImage()
{
    if(data)
    {
        delete[] data;
    }
};
EnhancedImage::EnhancedImage()
{
    superImage=NULL;
};
EnhancedImage::~EnhancedImage()
{
    if(superImage)
    {
        delete superImage;
    }
};
int EnhancedImage::load(const char* name)
{
    CxImage input;
    input.Load(name, CXIMAGE_SUPPORT_J2K);   
    if(!input.IsValid())
    {
        return 1;
    }
    else
    {
        int h=input.GetHeight();
        int w=input.GetWidth();       
        superImage = new SuperImage<Pixel>(w,h);
        for(int y=0;y<h;y++)
        {
            for(int x=0;x<w;x++)
            {
                RGBQUAD rgbQUAD = CxImage::RGBtoXYZ(input.GetPixelColor(x,y));
                Pixel pixel = 255;
                if(true)
                {
                    pixel = (rgbQUAD.rgbRed + rgbQUAD.rgbGreen + rgbQUAD.rgbBlue) / 3.0;
                }
                else
                {
                    double yy = (0.299 * rgbQUAD.rgbRed + 0.587 * rgbQUAD.rgbGreen + 0.114 * rgbQUAD.rgbBlue) / 256.0;
                   pixel = (int) (219.0 * yy + 16.5);
                }
                superImage->data[y*w+x]=pixel;
            }
        }
    }
    return 0;
};
int EnhancedImage::save(const char* name)
{
    CxImage output;
    output.Create(superImage->width,superImage->height,24,CXIMAGE_SUPPORT_BMP);  //1 4 8 24
    for(int y=0;y<superImage->height;y++)
    {
        for(int x=0;x<superImage->width;x++)
        {           
            Pixel pixel = superImage->data[y*superImage->width + x];
            RGBQUAD rgbQUAD;
            if(pixel == -1)
            {
                rgbQUAD.rgbRed = 255;
                rgbQUAD.rgbGreen = 0;
                rgbQUAD.rgbBlue = 0;
            }
            else if(pixel == -2)
            {
                rgbQUAD.rgbRed = 0;
                rgbQUAD.rgbGreen = 255;
                rgbQUAD.rgbBlue = 0;
            }
            else if(pixel == -3)
            {
                rgbQUAD.rgbRed = 0;
                rgbQUAD.rgbGreen = 0;
                rgbQUAD.rgbBlue = 255;
            }
            else
            {
                rgbQUAD.rgbRed = pixel;
                rgbQUAD.rgbGreen = pixel;
                rgbQUAD.rgbBlue =pixel;
            }
            output.SetPixelColor(x,y,rgbQUAD);
        }
    }
    output.Save(name,CXIMAGE_SUPPORT_BMP);
    return 0;
};
int EnhancedImage::copy(EnhancedImage* copy)
{
    int w = copy->superImage->width;
    int h = copy->superImage->height;   
    short* cpy = copy->superImage->data;
    //
    this->superImage = new SuperImage<Pixel>(w,h);
    short* tgt = this->superImage->data;
    //
    for (int y=0; y<h; y++)
    {
        for (int x=0; x<w; x++)
        {
            tgt[w*(y)+(x)] = cpy[w*(y)+(x)];
        }
    }
    return 0;
}
int EnhancedImage::film(EnhancedImage* film)
{
    int h = film->superImage->height;
    int w = film->superImage->width;
    short* flm = film->superImage->data;
    short* tgt = this->superImage->data;
    //
    for (int y=0; y<h; y++)
    {
        for (int x=0; x<w; x++)
        {
            unsigned char source = flm[w*(y)+(x)];
            unsigned char target = tgt[w*(y)+(x)];   
            if(source < 0 || target < 0)
            {
                printf("x=%d  y=%d    src=%d  tgt=%d\n",x,y,source,target);
            }
            if(source==0 && target == 255)
            {
                tgt[w*(y)+(x)] = source;
            }
        }
    }
    return 0;
}
int EnhancedImage::binv()
{
    int threshold = 256 / 8 * 7;
    for(int y2=0;y2<superImage->height;y2++)
    {
        for(int x2=0;x2<superImage->width;x2++)
        {           
            Pixel pixel = superImage->data[y2*superImage->width+x2];
            if(pixel>=threshold)
            {
                superImage->data[y2*superImage->width+x2] = 255;
            }
            else
            {
                superImage->data[y2*superImage->width+x2] = 0;
            }
        }
    }
    return 0;
};
int EnhancedImage::otsu()
{
    int ihist[256];
    memset(ihist, 0, sizeof(ihist));   
    int gmin=255;
    int gmax=0;
    for (int y=0; y<superImage->height; y++)
    {
        for (int x=0; x<superImage->width; x++)
        {
            unsigned char point = superImage->data[y*superImage->width+x];
            ihist[point]++;
            if(point > gmax)
            {
                gmax=point;
            }
            if(point < gmin)
            {
                gmin=point;
            }
        }
    }   
    double sum = 0.0;
    int n = 0;   
    for (int k = 0; k <= 255; k++)
    {
        sum += (double) k * (double) ihist[k];    // x*f(x) 质量矩
        n  += ihist[k];                          // f(x) 质量
    }   
    // do the otsu global thresholding method
    int threshold = 127;
    double fmax = -1.0;
    int n1 = 0;
    double csum = 0.0;
    for (k = 0; k < 255; k++)
    {
        n1 += ihist[k];
        if (!n1)
        {
            continue;
        }
        int n2 = n - n1;
        if (n2 == 0)
        {
            break;
        }
        csum += (double) k *ihist[k];
        int m1 = csum / n1;
        int m2 = (sum - csum) / n2;
        int sb = (double) n1 *(double) n2 *(m1 - m2) * (m1 - m2);
        //note: can be optimized.
        if (sb > fmax)
        {
            fmax = sb;
            threshold = k;
        }
    }
    //
    for(int y2=0;y2<superImage->height;y2++)
    {
        for(int x2=0;x2<superImage->width;x2++)
        {           
            Pixel pixel = superImage->data[y2*superImage->width+x2];
            if(pixel>threshold)
            {
                superImage->data[y2*superImage->width+x2] = 255;
            }
            else
            {
                superImage->data[y2*superImage->width+x2] = 0;
            }
        }
    }
    return 0;
};
/*************************************************************************
*
* 函数名称:
*  Hough()
*
* 参数:
*  LPSTR lpDIBBits    - 指向源DIB图像指针
*  LONG  lWidth      - 源图像宽度(象素数,必须是4的倍数)
*  LONG  lHeight      - 源图像高度(象素数)
* 返回值:
*  BOOL              - 运算成功返回TRUE,否则返回FALSE。
*
* 说明:
* 该函数用于对检测图像中的平行直线。如果图像中有两条平行的直线,则将这两条平行直线
* 提取出来。
*
* 要求目标图像为只有0和255两个灰度值的灰度图像。
************************************************************************/
// 在计算图像大小时,采用公式:biSizeImage = biWidth' × biHeight。
// 是biWidth',而不是biWidth,这里的biWidth'必须是4的整倍数,表示
// 大于或等于biWidth的,离4最近的整倍数。WIDTHBYTES就是用来计算
// biWidth'
#define WIDTHBYTES(bits)    (((bits) + 31) / 32 * 4)
#define pi 3.1415927
typedef struct{
    int Value;
    int Dist;
    int AngleNumber;
}MaxValue;
int EnhancedImage::line()
{
    //
    LONG lWidth = superImage->width;
    LONG lHeight = superImage->height;
    //
    LPSTR lpDIBBits = (char *)LocalAlloc(LHND, lWidth * lHeight);
    if (lpDIBBits == NULL)
    {
        return false ;
    }   
    lpDIBBits = (char *)LocalLock(lpDIBBits);
    //
    for (int y=0; y<superImage->height; y++)
    {
        for (int x=0; x<superImage->width; x++)
        {
            unsigned char point = superImage->data[y*superImage->width+x];           
            lpDIBBits[lWidth * y + x] = point;
        }
    }
    //       
    //
    // 指向源图像的指针
    LPSTR    lpSrc;
    // 指向缓存图像的指针
    LPSTR    lpDst;
    // 指向变换域的指针
    LPSTR  lpTrans;
    // 图像每行的字节数
    LONG lLineBytes;
    // 指向缓存DIB图像的指针
    LPSTR    lpNewDIBBits;
    HLOCAL    hNewDIBBits;
    //指向变换域的指针
    LPSTR    lpTransArea;
    HLOCAL    hTransArea;
    //变换域的尺寸
    int iMaxDist;
    int iMaxAngleNumber;
    //变换域的坐标
    int iDist;
    int iAngleNumber;
    //循环变量
    long i;
    long j;
    //像素值
    unsigned char pixel;
    //存储变换域中的两个最大值
    MaxValue MaxValue1;
    MaxValue MaxValue2;
    // 暂时分配内存,以保存新图像
    hNewDIBBits = LocalAlloc(LHND, lWidth * lHeight);
    if (hNewDIBBits == NULL)
    {
        // 分配内存失败
        return FALSE;
    }
    // 锁定内存
    lpNewDIBBits = (char * )LocalLock(hNewDIBBits);
    // 初始化新分配的内存,设定初始值为255
    lpDst = (char *)lpNewDIBBits;
    memset(lpDst, (BYTE)255, lWidth * lHeight);
    //计算变换域的尺寸
    //最大距离
    iMaxDist = (int) sqrt(lWidth*lWidth + lHeight*lHeight);
    //角度从0-180,每格2度
    iMaxAngleNumber = 90;
    //为变换域分配内存
    hTransArea = LocalAlloc(LHND, lWidth * lHeight * sizeof(int));
    if (hNewDIBBits == NULL)
    {
        // 分配内存失败
        return FALSE;
    }
    // 锁定内存
    lpTransArea = (char * )LocalLock(hTransArea);
    // 初始化新分配的内存,设定初始值为0
    lpTrans = (char *)lpTransArea;
    memset(lpTrans, 0, lWidth * lHeight * sizeof(int));
    // 计算图像每行的字节数
    for(j = 0; j <lHeight; j++)
    {
        for(i = 0;i <lWidth; i++)
        {
            // 指向源图像倒数第j行,第i个象素的指针           
            lpSrc = (char *)lpDIBBits + lWidth * j + i;
            //取得当前指针处的像素值,注意要转换为unsigned char型
            pixel = (unsigned char)*lpSrc;
            //目标图像中含有0和255外的其它灰度值
            if(pixel != 255 && *lpSrc != 0)
                return FALSE;
            //如果是黑点,则在变换域的对应各点上加1
            if(pixel == 0)
            {
                //注意步长是2度
                for(iAngleNumber=0; iAngleNumber<iMaxAngleNumber; iAngleNumber++)
                {
                    iDist = (int) fabs(i*cos(iAngleNumber*2*pi/180.0) + j*sin(iAngleNumber*2*pi/180.0));
                    //变换域的对应点上加1
                    *(lpTransArea+iDist*iMaxAngleNumber+iAngleNumber) = *(lpTransArea+iDist*iMaxAngleNumber+iAngleNumber) +1;
                }
            }
        }
    }
    //找到变换域中的两个最大值点
    MaxValue1.Value=0;
    MaxValue2.Value=0;
    //找到第一个最大值点
    for (iDist=0; iDist<iMaxDist;iDist++)
    {
        for(iAngleNumber=0; iAngleNumber<iMaxAngleNumber; iAngleNumber++)
        {
            if((int)*(lpTransArea+iDist*iMaxAngleNumber+iAngleNumber)>MaxValue1.Value)
            {
                MaxValue1.Value = (int)*(lpTransArea+iDist*iMaxAngleNumber+iAngleNumber);
                MaxValue1.Dist = iDist;
                MaxValue1.AngleNumber = iAngleNumber;
            }
        }
    }
    //将第一个最大值点附近清零
    for (iDist = -9;iDist < 10;iDist++)
    {
        for(iAngleNumber=-1; iAngleNumber<2; iAngleNumber++)
        {
            if(iDist+MaxValue1.Dist>=0 && iDist+MaxValue1.Dist<iMaxDist && iAngleNumber+MaxValue1.AngleNumber>=0 && iAngleNumber+MaxValue1.AngleNumber<=iMaxAngleNumber)
            {
                *(lpTransArea+(iDist+MaxValue1.Dist)*iMaxAngleNumber+(iAngleNumber+MaxValue1.AngleNumber))=0;
            }
        }
    }
    //找到第二个最大值点
    for (iDist=0; iDist<iMaxDist;iDist++)
    {
        for(iAngleNumber=0; iAngleNumber<iMaxAngleNumber; iAngleNumber++)
        {
            if((int)*(lpTransArea+iDist*iMaxAngleNumber+iAngleNumber)>MaxValue2.Value)
            {
                MaxValue2.Value = (int)*(lpTransArea+iDist*iMaxAngleNumber+iAngleNumber);
                MaxValue2.Dist = iDist;
                MaxValue2.AngleNumber = iAngleNumber;
            }
        }
    }
    //判断两直线是否平行
    if(abs(MaxValue1.AngleNumber-MaxValue2.AngleNumber)<=2)
    {
        //两直线平行,在缓存图像中重绘这两条直线
        for(j = 0; j <lHeight; j++)
        {
            for(i = 0;i <lWidth; i++)
            {   
                // 指向缓存图像倒数第j行,第i个象素的指针           
                lpDst = (char *)lpNewDIBBits + lLineBytes * j + i;   
                //如果该点在某一条平行直线上,则在缓存图像上将该点赋为黑
                //在第一条直线上
                iDist = (int) fabs(i*cos(MaxValue1.AngleNumber*2*pi/180.0) + j*sin(MaxValue1.AngleNumber*2*pi/180.0));
                if (iDist == MaxValue1.Dist)
                    *lpDst = (unsigned char)0;
                //在第二条直线上
                iDist = (int) fabs(i*cos(MaxValue2.AngleNumber*2*pi/180.0) + j*sin(MaxValue2.AngleNumber*2*pi/180.0));
                if (iDist == MaxValue2.Dist)
                    *lpDst = (unsigned char)0;
            }
        }
    }
    // 复制腐蚀后的图像
    memcpy(lpDIBBits, lpNewDIBBits, lWidth * lHeight);
    // 释放内存
    LocalUnlock(hNewDIBBits);
    LocalFree(hNewDIBBits);
    // 释放内存
    LocalUnlock(hTransArea);
    LocalFree(hTransArea);
    //
    //
    for (int yy=0; yy<superImage->height; yy++)
    {
        for (int xx=0; xx<superImage->width; xx++)
        {
            unsigned char point = lpDIBBits[lWidth * yy + xx];           
            superImage->data[yy*superImage->width+xx] = point;
        }
    }
    //
    LocalUnlock(lpDIBBits);
    LocalFree(lpDIBBits);
    // 返回
    return TRUE;
}
int EnhancedImage::roii()
{
    int minX = superImage->width;
    int minY = superImage->height;
    int maxX = 0;
    int maxY = 0;
    //
    for (int y=0; y<superImage->height; y++)
    {
        for (int x=0; x<superImage->width; x++)
        {
            unsigned char point = superImage->data[y*superImage->width+x];
            if(point==0)
            {
                if(x<minX)
                {
                    minX = x;                   
                }
                if(y<minY)
                {
                    minY = y;
                }
                //
                if(x>maxX)
                {
                    maxX = x;
                }
                if(y>maxY)
                {
                    maxY = y;
                }
            }
        }
    }
    //
    if(minX>maxX)
    {
        minX=maxX=0;
    }
    if(minY>maxY)
    {
        minY=maxY=0;
    }
    //
    if(minX>0)
        minX = minX -1;
    if(minY>0)
        minY = minY -1;
    if(maxX<superImage->width-1)
        maxX= maxX+1;
    if(maxY<superImage->height-1)
        maxY = maxY+1;
    //
    int tmpW = maxX - minX+1;
    int tmpH = maxY - minY+1;
    SuperImage<Pixel>* tmpD = new SuperImage<Pixel>(tmpW,tmpH);
    //
    for(int y2=minY,y3=0;y3<tmpH;y2++,y3++)
    {
        for(int x2=minX,x3=0;x3<tmpW;x2++,x3++)
        {           
            Pixel pixel = superImage->data[y2*superImage->width+x2];
            tmpD->data[y3*tmpW+x3] = pixel;
        }
    }
    //
    if(superImage)
    {
        delete superImage;
        superImage = NULL;
    }
    //
    superImage = tmpD;
    //
    return 0;
};
/*************************************************************************
*
* 函数名称:
*  TraceDIB()
*
* 参数:
*  LPSTR lpDIBBits    - 指向源DIB图像指针
*  LONG  lWidth      - 源图像宽度(象素数,必须是4的倍数)
*  LONG  lHeight      - 源图像高度(象素数)
* 返回值:
*  BOOL              - 运算成功返回TRUE,否则返回FALSE。
*
* 说明:
* 该函数用于对图像进行轮廓跟踪运算。
*
* 要求目标图像为只有0和255两个灰度值的灰度图像。
************************************************************************/
int EnhancedImage::trac()
{
    typedef struct
    {
        int Height;
        int Width;
    }Point;
    //
    LONG lWidth = superImage->width;
    LONG lHeight = superImage->height;
    //
    LPSTR lpDIBBits = (char *)LocalAlloc(LHND, lWidth * lHeight);
    if (lpDIBBits == NULL)
    {
        return false ;
    }   
    lpDIBBits = (char *)LocalLock(lpDIBBits);
    //
    for (int y=0; y<superImage->height; y++)
    {
        for (int x=0; x<superImage->width; x++)
        {
            unsigned char point = superImage->data[y*superImage->width+x];           
            lpDIBBits[lWidth * y + x] = point;
        }
    }
    //       
    //
    // 指向源图像的指针
    LPSTR    lpSrc;
    // 指向缓存图像的指针
    LPSTR    lpDst;
    // 指向缓存DIB图像的指针
    LPSTR    lpNewDIBBits;
    HLOCAL    hNewDIBBits;
    //循环变量
    long i;
    long j;
    //像素值
    unsigned char pixel;
    //是否找到起始点及回到起始点
    bool bFindStartPoint;
    //是否扫描到一个边界点
    bool bFindPoint;
    //起始边界点与当前边界点
    Point StartPoint,CurrentPoint;
    //八个方向和起始扫描方向
    int Direction[8][2]={{-1,1},{0,1},{1,1},{1,0},{1,-1},{0,-1},{-1,-1},{-1,0}};
    int BeginDirect;
    // 图像每行的字节数
    //LONG lLineBytes;
    // 计算图像每行的字节数
    //lLineBytes = WIDTHBYTES(lWidth * 8);
    // 暂时分配内存,以保存新图像
    hNewDIBBits = LocalAlloc(LHND, lWidth * lHeight);
    if (hNewDIBBits == NULL)
    {
        // 分配内存失败
        return FALSE;
    }
    // 锁定内存
    lpNewDIBBits = (char * )LocalLock(hNewDIBBits);
    // 初始化新分配的内存,设定初始值为255
    lpDst = (char *)lpNewDIBBits;
    memset(lpDst, (BYTE)255, lWidth * lHeight);
    //先找到最左上方的边界点
    bFindStartPoint = false;
    for (j = 0;j < lHeight && !bFindStartPoint;j++)
    {
        for(i = 0;i < lWidth && !bFindStartPoint;i++)
        {
            // 指向源图像倒数第j行,第i个象素的指针           
            lpSrc = (char *)lpDIBBits + lWidth * j + i;
            //取得当前指针处的像素值,注意要转换为unsigned char型
            pixel = (unsigned char)*lpSrc;
            if(pixel == 0)
            {
                bFindStartPoint = true;
                StartPoint.Height = j;
                StartPoint.Width = i;
                // 指向目标图像倒数第j行,第i个象素的指针           
                lpDst = (char *)lpNewDIBBits + lWidth * j + i;   
                *lpDst = (unsigned char)0;
            }       
        }
    }
    //由于起始点是在左下方,故起始扫描沿左上方向
    BeginDirect = 0;
    //跟踪边界
    bFindStartPoint = false;
    //从初始点开始扫描
    CurrentPoint.Height = StartPoint.Height;
    CurrentPoint.Width = StartPoint.Width;
    while(!bFindStartPoint)
    {
        bFindPoint = false;
        while(!bFindPoint)
        {
            //沿扫描方向查看一个像素
            lpSrc = (char *)lpDIBBits + lWidth * ( CurrentPoint.Height + Direction[BeginDirect][1]) + (CurrentPoint.Width + Direction[BeginDirect][0]);
            pixel = (unsigned char)*lpSrc;
            if(pixel == 0)
            {
                bFindPoint = true;
                CurrentPoint.Height = CurrentPoint.Height + Direction[BeginDirect][1];
                CurrentPoint.Width = CurrentPoint.Width + Direction[BeginDirect][0];
                if(CurrentPoint.Height == StartPoint.Height && CurrentPoint.Width == StartPoint.Width)
                {
                    bFindStartPoint = true;
                }
                lpDst = (char *)lpNewDIBBits + lWidth * CurrentPoint.Height + CurrentPoint.Width;
                *lpDst = (unsigned char)0;
                //扫描的方向逆时针旋转两格
                BeginDirect--;
                if(BeginDirect == -1)
                    BeginDirect = 7;
                BeginDirect--;
                if(BeginDirect == -1)
                    BeginDirect = 7;
            }
            else
            {
                //扫描方向顺时针旋转一格
                BeginDirect++;
                if(BeginDirect == 8)
                    BeginDirect = 0;
            }
        }
    }
    // 复制腐蚀后的图像
    memcpy(lpDIBBits, lpNewDIBBits, lWidth * lHeight);
    // 释放内存
    LocalUnlock(hNewDIBBits);
    LocalFree(hNewDIBBits);
    //
    //
    for (int yy=0; yy<superImage->height; yy++)
    {
        for (int xx=0; xx<superImage->width; xx++)
        {
            unsigned char point = lpDIBBits[lWidth * yy + xx];           
            superImage->data[yy*superImage->width+xx] = point;
        }
    }
    //
    LocalUnlock(lpDIBBits);
    LocalFree(lpDIBBits);
    // 返回
    return TRUE;
}
int EnhancedImage::edge()
{
    //
    LONG lWidth = superImage->width;
    LONG lHeight = superImage->height;
    //
    LPSTR lpDIBBits = (char *)LocalAlloc(LHND, lWidth * lHeight);
    if (lpDIBBits == NULL)
    {
        return false ;
    }   
    lpDIBBits = (char *)LocalLock(lpDIBBits);
    //
    for (int y=0; y<superImage->height; y++)
    {
        for (int x=0; x<superImage->width; x++)
        {
            unsigned char point = superImage->data[y*superImage->width+x];           
            lpDIBBits[lWidth * y + x] = point;
        }
    }
    //       
    //
    // 指向源图像的指针
    LPSTR    lpSrc;
    // 指向缓存图像的指针
    LPSTR    lpDst;
    // 指向缓存DIB图像的指针
    LPSTR    lpNewDIBBits;
    HLOCAL    hNewDIBBits;
    //循环变量
    long i;
    long j;
    unsigned char n,e,s,w,ne,se,nw,sw;
    //像素值
    unsigned char pixel;
    // 暂时分配内存,以保存新图像
    hNewDIBBits = LocalAlloc(LHND, lWidth * lHeight);
    if (hNewDIBBits == NULL)
    {
        // 分配内存失败
        return FALSE;
    }
    // 锁定内存
    lpNewDIBBits = (char * )LocalLock(hNewDIBBits);
    // 初始化新分配的内存,设定初始值为255
    lpDst = (char *)lpNewDIBBits;
    memset(lpDst, (BYTE)255, lWidth * lHeight);
    for(j = 1; j <lHeight-1; j++)
    {
        for(i = 1;i <lWidth-1; i++)
        {
            // 指向源图像倒数第j行,第i个象素的指针   
            lpSrc = (char *)lpDIBBits + lWidth * j + i;
            // 指向目标图像倒数第j行,第i个象素的指针
            lpDst = (char *)lpNewDIBBits + lWidth * j + i;
            //取得当前指针处的像素值,注意要转换为unsigned char型
            pixel = (unsigned char)*lpSrc;
            if(pixel == 0)
            {
                *lpDst = (unsigned char)0;
                nw = (unsigned char)*(lpSrc + lWidth -1);
                n  = (unsigned char)*(lpSrc + lWidth );
                ne = (unsigned char)*(lpSrc + lWidth +1);
                w = (unsigned char)*(lpSrc -1);
                e = (unsigned char)*(lpSrc +1);
                sw = (unsigned char)*(lpSrc - lWidth -1);
                s  = (unsigned char)*(lpSrc - lWidth );
                se = (unsigned char)*(lpSrc - lWidth +1);
                //如果相邻的八个点都是黑点
                if(nw+n+ne+w+e+sw+s+se==0)
                {
                    *lpDst = (unsigned char)255;
                }
            }
        }
    }
    // 复制腐蚀后的图像
    memcpy(lpDIBBits, lpNewDIBBits, lWidth * lHeight);
    // 释放内存
    LocalUnlock(hNewDIBBits);
    LocalFree(hNewDIBBits);
    //
    //
    for (int yy=0; yy<superImage->height; yy++)
    {
        for (int xx=0; xx<superImage->width; xx++)
        {
            unsigned char point = lpDIBBits[lWidth * yy + xx];           
            superImage->data[yy*superImage->width+xx] = point;
        }
    }
    //
    LocalUnlock(lpDIBBits);
    LocalFree(lpDIBBits);
    // 返回
    return TRUE;
}
int EnhancedImage::thin()
{
    //
    LONG lWidth = superImage->width;
    LONG lHeight = superImage->height;
    //
    LPSTR lpDIBBits = (char *)LocalAlloc(LHND, lWidth * lHeight);
    if (lpDIBBits == NULL)
    {
        return false ;
    }   
    lpDIBBits = (char *)LocalLock(lpDIBBits);
    //
    for (int y=0; y<superImage->height; y++)
    {
        for (int x=0; x<superImage->width; x++)
        {
            unsigned char point = superImage->data[y*superImage->width+x];           
            lpDIBBits[lWidth * y + x] = point;
        }
    }
    //
    //
    LPSTR    lpSrc;               
    LPSTR    lpDst;   
    LPSTR    lpNewDIBBits;   
    HLOCAL    hNewDIBBits;   
    BOOL bModified;               
    long i,j,m,n;       
    BOOL con1;
    BOOL con2;
    BOOL con3;
    BOOL con4;
    unsigned char nCount;       
    unsigned char pixel;       
    unsigned char ne[5][5];   
    hNewDIBBits = LocalAlloc(LHND, lWidth * lHeight);
    if (hNewDIBBits == NULL)
    {
        return false ;
    }
    lpNewDIBBits = (char * )LocalLock(hNewDIBBits);
    // 初始化新分配的内存,设定初始值为255
    lpDst = (char *)lpNewDIBBits;
    memset(lpDst, (BYTE)255, lWidth * lHeight);
    bModified = TRUE;
    while(bModified)
    {
        bModified = FALSE;
        lpDst = (char *)lpNewDIBBits;
        memset(lpDst, (BYTE)255, lWidth * lHeight);
        for(j = 2; j <lHeight-2; j++)
        {
            for(i = 2;i <lWidth-2; i++)
            {
                con1 = FALSE;
                con2 = FALSE;
                con3 = FALSE;
                con4 = FALSE;
                lpSrc = (char *)lpDIBBits + lWidth * j + i;       
                lpDst = (char *)lpNewDIBBits + lWidth * j + i;
                pixel = (unsigned char)*lpSrc;
                if(pixel != 255 && *lpSrc != 0)
                {
                    continue;
                }
                else if(pixel == 255)
                {
                    continue;
                }
                //白色用0代表,黑色用1代表
                for (m = 0;m < 5;m++ )
                {
                    for (n = 0;n < 5;n++)
                    {
                        ne[m][n] =(255 - (unsigned char)*(lpSrc + ((4 - m) - 2)*lWidth + n - 2 )) / 255;
                    }
                }
                //判断2<=NZ(P1)<=6
                nCount =  ne[1][1] + ne[1][2] + ne[1][3]
                    + ne[2][1]            + ne[2][3]
                    + ne[3][1] + ne[3][2] + ne[3][3];
                if ( nCount >= 2 && nCount <=6)
                {
                    con1 = TRUE;
                }
                //判断Z0(P1)=1
                nCount = 0;
                if (ne[1][2] == 0 && ne[1][1] == 1)
                    nCount++;
                if (ne[1][1] == 0 && ne[2][1] == 1)
                    nCount++;
                if (ne[2][1] == 0 && ne[3][1] == 1)
                    nCount++;
                if (ne[3][1] == 0 && ne[3][2] == 1)
                    nCount++;
                if (ne[3][2] == 0 && ne[3][3] == 1)
                    nCount++;
                if (ne[3][3] == 0 && ne[2][3] == 1)
                    nCount++;
                if (ne[2][3] == 0 && ne[1][3] == 1)
                    nCount++;
                if (ne[1][3] == 0 && ne[1][2] == 1)
                    nCount++;
                if (nCount == 1)
                    con2 = TRUE;
                //判断P2*P4*P6=0 or Z0(p4)!=1
                if (ne[1][2]*ne[2][1]*ne[3][2] == 0)
                {
                    con3 = TRUE;
                }
                else
                {
                    nCount = 0;
                    if (ne[1][1] == 0 && ne[1][0] == 1)
                        nCount++;
                    if (ne[1][0] == 0 && ne[2][0] == 1)
                        nCount++;
                    if (ne[2][0] == 0 && ne[3][0] == 1)
                        nCount++;
                    if (ne[3][0] == 0 && ne[3][1] == 1)
                        nCount++;
                    if (ne[3][1] == 0 && ne[3][2] == 1)
                        nCount++;
                    if (ne[3][2] == 0 && ne[2][2] == 1)
                        nCount++;
                    if (ne[2][2] == 0 && ne[1][2] == 1)
                        nCount++;
                    if (ne[1][2] == 0 && ne[1][1] == 1)
                        nCount++;
                    if (nCount != 1)
                        con3 = TRUE;
                }
                //判断P2*P4*P8=0 or Z0(p2)!=1
                if (ne[1][2]*ne[2][1]*ne[2][3] == 0)
                {
                    con4 = TRUE;
                }
                else
                {
                    nCount = 0;
                    if (ne[0][2] == 0 && ne[0][1] == 1)
                        nCount++;
                    if (ne[0][1] == 0 && ne[1][1] == 1)
                        nCount++;
                    if (ne[1][1] == 0 && ne[2][1] == 1)
                        nCount++;
                    if (ne[2][1] == 0 && ne[2][2] == 1)
                        nCount++;
                    if (ne[2][2] == 0 && ne[2][3] == 1)
                        nCount++;
                    if (ne[2][3] == 0 && ne[1][3] == 1)
                        nCount++;
                    if (ne[1][3] == 0 && ne[0][3] == 1)
                        nCount++;
                    if (ne[0][3] == 0 && ne[0][2] == 1)
                        nCount++;
                    if (nCount != 1)
                        con4 = TRUE;
                }
                if(con1 && con2 && con3 && con4)
                {
                    *lpDst = (unsigned char)255;
                    bModified = TRUE;
                }
                else
                {
                    *lpDst = (unsigned char)0;
                }
            }
        }       
        memcpy(lpDIBBits, lpNewDIBBits, lWidth * lHeight);
    }
    memcpy(lpDIBBits, lpNewDIBBits, lWidth * lHeight);
    LocalUnlock(hNewDIBBits);
    LocalFree(hNewDIBBits);
    //
    //
    for (int yy=0; yy<superImage->height; yy++)
    {
        for (int xx=0; xx<superImage->width; xx++)
        {
            unsigned char point = lpDIBBits[lWidth * yy + xx];           
            superImage->data[yy*superImage->width+xx] = point;
        }
    }
    //
    LocalUnlock(lpDIBBits);
    LocalFree(lpDIBBits);
}
int EnhancedImage::tidy()
{
    //一个更好的算法,是要求整理之后,还保证原来的联通性,现在还不够好
    int h = this->superImage->height;
    int w = this->superImage->width;
    short* src = this->superImage->data;
    //
    for (int y=0; y<h; y++)
    {
        for (int x=0; x<w; x++)
        {
            unsigned char o = src[w*(y)+(x)];
            //
            unsigned char u = -1;                   
            unsigned char ur = -1;                   
            unsigned char r = -1;                   
            unsigned char br = -1;                   
            unsigned char b = -1;                   
            unsigned char bl = -1;                   
            unsigned char l = -1;                   
            unsigned char ul = -1;   
            //
            if(y<h)
            {
                u = src[w*(y+1)+(x)];
            }
            if(y<h && x< w)
            {
                ur =src[w*(y+1)+(x+1)];
            }
            if(x<w)
            {
                r=src[w*(y)+(x+1)];
            }
            if(y>0 && x<w)
            {
                br = src[w*(y-1)+(x+1)];
            }
            if(y>0)
            {
                b = src[w*(y-1)+(x)];
            }
            if(y>0 && x>0)
            {
                bl = src[w*(y-1)+(x-1)];
            }
            if(x>0)
            {
                l = src[w*(y)+(x-1)];
            }
            if(y<h && x>0)
            {
                ul = src[w*(y+1)+(x-1)];
            }       
            //
            if(o==0)
            {
                if(r==0 && u==0 &&  br==0)
                {
                    src[w*(y)+(x+1)] = 255;
                }
                if(r==0 && ur==0 &&  r==0)
                {
                    src[w*(y)+(x+1)] = 255;
                }
                if(r==0 && ul==0 &&  br==0)
                {
                    src[w*(y)+(x+1)] = 255;
                }
                //
                if(l==0 && u==0 &&  bl==0)
                {
                    src[w*(y)+(x-1)] = 255;
                }
                if(l==0 && u==0 &&  ur==0)
                {
                    src[w*(y)+(x-1)] = 255;
                }
                if(l==0 && bl==0 &&  ur==0)
                {
                    src[w*(y)+(x-1)] = 255;
                }
                if(l==0 && bl==0 &&  r==0)
                {
                    src[w*(y)+(x-1)] = 255;
                }
                if(l==0 && ul==0 &&  r==0)
                {
                    src[w*(y)+(x-1)] = 255;
                }
                if(l==0 && ul==0 &&  br==0)
                {
                    src[w*(y)+(x-1)] = 255;
                }
            }
        }
    }
    return 0;
}
int EnhancedImage::kerf()
{
    int w = this->superImage->width;
    int h = this->superImage->height;   
    short* src = this->superImage->data;
    //
    SuperImage<Pixel>* tmpD = new SuperImage<Pixel>(w,h);
    short* tgt = tmpD->data;
    //
    for (int y=0; y<h; y++)
    {
        for (int x=0; x<w; x++)
        {
            int count = 0;
            unsigned char p = src[y*w+x];
            tgt[y*w+x] = p;
            //           
            if(y<h && src[w*(y+1)+(x)]  == 0)
            {
                count = count + 1;
            }
            if(y<h && x< w && src[w*(y+1)+(x+1)]  == 0)
            {
                count = count + 1;
            }
            if(x<w && src[w*(y)+(x+1)]  == 0)
            {
                count = count + 1;
            }
            if(y>0 && x<w && src[w*(y-1)+(x+1)]  == 0)
            {
                count = count + 1;
            }
            if(y>0 && src[w*(y-1)+(x)]  == 0)
            {
                count = count + 1;
            }
            if(y>0 && x>0 && src[w*(y-1)+(x-1)]  == 0)
            {
                count = count + 1;
            }
            if(x>0 && src[w*(y)+(x-1)]  == 0)
            {
                count = count + 1;
            }
            if(y<h && x>0 && src[w*(y+1)+(x-1)]  == 0)
            {
                count = count + 1;
            }
            //
            if(p == 0 && count > 2)
            {
                tgt[y*w+x] = -1;
            }
        }
    }
    //
    if(superImage)
    {
        delete superImage;
        superImage = NULL;
    }
    //
    superImage = tmpD;
    //
    return 0;
}
int EnhancedImage::join()
{
    return 0;
}
//AntiCaptchaPart1.cpp
#include <stdio.h>
#include <string>
#include <windows.h>
#include "common.h"
//
void process(char* loadPathName,char* tempPathName)
{
    EnhancedImage otsuImage;
    otsuImage.load(loadPathName);
    if(1)
    {
        char convPathName[MAX_PATH];
        sprintf(convPathName,"%s%s",tempPathName,".001.conv.bmp");
        otsuImage.save(convPathName);
    }
    if(1)
    {
        char binvPathName[MAX_PATH];
        sprintf(binvPathName,"%s%s",tempPathName,".002.binv.bmp");
        otsuImage.binv();
        otsuImage.save(binvPathName);
    }
    if(1)
    {
        char otsuPathName[MAX_PATH];
        sprintf(otsuPathName,"%s%s",tempPathName,".003.otsu.bmp");
        otsuImage.otsu();
        otsuImage.save(otsuPathName);
    }
    if(0)
    {
        char linePathName[MAX_PATH];
        sprintf(linePathName,"%s%s",tempPathName,".004.line.bmp");
        otsuImage.line();
        otsuImage.save(linePathName);
    }
    if(1)
    {
        char roiiPathName[MAX_PATH];
        sprintf(roiiPathName,"%s%s",tempPathName,".005.roii.bmp");
        otsuImage.roii();
        otsuImage.save(roiiPathName);
    }
    EnhancedImage tracImage;
    tracImage.copy(&otsuImage);
    if(1)
    {
        char tracPathName[MAX_PATH];
        sprintf(tracPathName,"%s%s",tempPathName,".006.trac.bmp");
        tracImage.trac();
        tracImage.save(tracPathName);
    }
    //
    EnhancedImage edgeImage;
    edgeImage.copy(&otsuImage);
    if(1)
    {
        char edgePathName[MAX_PATH];
        sprintf(edgePathName,"%s%s",tempPathName,".007.edge.bmp");
        edgeImage.edge();
        edgeImage.save(edgePathName);
    }
    //
    EnhancedImage thinImage;
    thinImage.copy(&otsuImage);
    if(1)
    {
        char thinPathName[MAX_PATH];
        sprintf(thinPathName,"%s%s",tempPathName,".008.thin.bmp");
        thinImage.thin();
        thinImage.save(thinPathName);
    }
    if(1)
    {
        char tidyPathName[MAX_PATH];
        sprintf(tidyPathName,"%s%s",tempPathName,".009.tidy.bmp");
        thinImage.tidy();
        thinImage.save(tidyPathName);
    }
    EnhancedImage kerfImage;
    kerfImage.copy(&thinImage);
    if(1)
    {
        char kerfPathName[MAX_PATH];
        sprintf(kerfPathName,"%s%s",tempPathName,".010.kerf.bmp");
        kerfImage.kerf();
        kerfImage.save(kerfPathName);
    }
    EnhancedImage filmImage;
    filmImage.copy(&kerfImage);
    if(1)
    {   
        char filmPathName[MAX_PATH];
        sprintf(filmPathName,"%s%s",tempPathName,".011.film.bmp");
        filmImage.film(&edgeImage);
       filmImage.save(filmPathName);
    }
}
//
void travel()
{
    char homePathName[MAX_PATH];
    GetCurrentDirectory(MAX_PATH,homePathName);
    //
    SetCurrentDirectory(homePathName);
    SetCurrentDirectory(".\\output");
    WIN32_FIND_DATA    findDataDelete;
    HANDLE    hHandleDelete  =  FindFirstFile("*.*", &findDataDelete);
    int hasNextFileDelete = (hHandleDelete !=  INVALID_HANDLE_VALUE);
    while (hasNextFileDelete !=  0)
    {
        char deletePathName[MAX_PATH];
        GetFullPathName(findDataDelete.cFileName, MAX_PATH, deletePathName, NULL);
        printf("Delete %s\n",deletePathName);
        DeleteFile(deletePathName);
        hasNextFileDelete = FindNextFile(hHandleDelete, &findDataDelete);
    }
    if(hHandleDelete !=  INVALID_HANDLE_VALUE)
    {
        FindClose(hHandleDelete);
    }
    //
    //
    SetCurrentDirectory(homePathName);
    SetCurrentDirectory(".\\sample");
    WIN32_FIND_DATA    findData;
    HANDLE    hFindHandle  =  FindFirstFile("*.jpg", &findData);
    int hasNextFile = (hFindHandle !=  INVALID_HANDLE_VALUE);
    while (hasNextFile !=  0)
    {
        char loadPathName[MAX_PATH];
        GetFullPathName(findData.cFileName, MAX_PATH, loadPathName, NULL);       
        //
        char tempPathName[MAX_PATH];
        sprintf(tempPathName,"%s",loadPathName);
        for(int i=strlen(tempPathName);i>=0;i--)
        {
            if(tempPathName[i]=='\\')
            {
                break;
            }
            else
            {
                tempPathName[i]='\0';
            }
        }
        strcat(tempPathName,"..\\output\\");
        strcat(tempPathName,findData.cFileName);
        //
        printf("Process %s to %s\n",loadPathName,tempPathName);
        process(loadPathName,tempPathName);
        //
        //
        hasNextFile = FindNextFile(hFindHandle, &findData);
    }
    if(hFindHandle !=  INVALID_HANDLE_VALUE)
    {
        FindClose(hFindHandle);
    }
}
//
void main(int argc, char* argv[])
{
    printf("Anti Captcha ...\n");
    travel();
    printf("Anti Captcha !!!\n");
}

    注: 在这里,我们可以看到,基本的部件(字母是分割开了,但可以造成统一字母的被切
割成多个Component。 一种做法是:利用先验知识,做分割; 另外一种做法是,和第二部分的
识别结合起来。 比如按照从左至右,尝试增加component来识别,如果不能识别而且
component的总宽度,总面积还比较小,继续增加。 当然不排除拒识的可能性。 )
    3)字符部件组合和识别。
    part2的代码展示了切割后的字母组合,和基于svm的字符识别的训练和识别过程。
Detection.cpp中展示了ImageSpam检测过程中的一些字符分割和组合,layout的分析和利用
的简单技术。 而Google的验证码的识别,完全可以不用到,仅做参考。
    SVM及使用:
    本质上,SVM是一个分类器,原始的SVM是一个两类分类的分类器。可以通过1:1或者1:n
的方式来组合成一个多类分类的分类器。 天生通过核函数的使用支持高维数据的分类。从
几何意义上讲,就是找到最能表示类别特征的那些向量(支持向量SV),然后找到一条线,能最
大化分类的Margin。
    libSVM是一个不错的实现。
    训练间断和识别阶段的数据整理和归一化是一样的。 这里的简单做法是:
    首先:
    #define SVM_MAX  +0.999
    #define SVM_MIN  +0.001
    其次:
    扫描黑白待识别字幕图片的每个像素,如果为0(黑色,是字母上的像素),那么svm中该位
置就SVM_MAX,反之则反。
    最后:
    训练阶段,在svm的input的前面,为该类打上标记,即是那一个字母。
    识别阶段,当然这个类别标记是SVM分类出来。
    注意:
    如果是SVM菜鸟,最好找一个在SVM外边做了包装的工具,比如样本选择,交叉验证,核函
数选择这些,让程序自动选择和分析。
    代码:通过ReginGrowth来提取单个单个的字符,然后开始识别。
    查看代码(./pstzine_09_02.txt)
六、对验证码设计的一些建议
    1.在噪音等类型的使用上,尽力让字符和用来混淆的前景和背景不容易区分。尽力让坏
人(噪音)长得和好人(字母)一样。
    2.特别好的验证码的设计,要尽力发挥人类擅长而AI算法不擅长的。 比如粘连字符的
分割和手写体(通过印刷体做特别的变形也可以)。 而不要一味的去加一些看起来比较复杂
的噪音或者其他的花哨的东西。即使你做的足够复杂,但如果人也难识别,显然别人认为你
是没事找抽型的。
    3. 从专业的机器视觉的角度说,验证码的设计,一定要让破解者在识别阶段,反复在低
阶视觉和高阶视觉之间多反复几次才能识别出来。 这样可以大大降低破解难度和破解的准
确率。
七、个人郑重申明
    1.这个问题,本身是人工智能,计算机视觉,模式识别领域的一个难题。我是虾米,菜得
不能再菜的那种。作为破解者来说,是出于劣势地位。要做的很好,是很难得。总体来说,我
走的是比较学院派的线路,能真正的破解难度比较高的验证码,不同于网上很多不太入流的
破解方法。我能做的只有利用有限的知识,抛砖引玉而已。 很多OCR的技术,特别是离线手
写体中文等文字识别的技术,个人了解有限的很,都不敢在这里乱写。
    2.希望不要把这种技术用于非法用途。
原文地址:https://www.cnblogs.com/hsapphire/p/1929732.html