简单的bmp验证码识别 (c++)

来源:互联网 发布:淘宝自动回复怎么设置 编辑:程序博客网 时间:2024/05/17 13:06

  菜鸟学习笔记供新手学习参考,一个有着很多限制的小程序,大神路过多多指点。


  不废话,正题如下:


  bmp验证码识别的流程大概分为

    1、获取bmp图片中识别需要用到的数据,包括像素高度像素宽度以及位图数据。

    2、对获取的数据进行必要的处理,包括灰度化、二值化、去噪等。

    3、对处理后的数据(一个二值化的数组)进行扫描,分割出各个字符(即确定多对数组下标)

    4、建立字符模板库,将分割出的字符与模板一一对比,找出对应的字符。


  下面具体讲解:

  1、获取数据。

    bmp文件结构与读取遍地都是,细节不赘述

    需要注意:1)简单识别用到的也就是biWidth(19-22字节),biHeight(23-26字节),以及位图数据(>54字节

                       2)位图数据是倒着存放,读取或处理的时候要进行处理。

                               位图数据每行会自动填充字节为4的倍数,读取或处理时最好跳过相应数目的字节。

                        3)每个像素有3个分量RGB,对应3个字节。

                       4关于调色板:由于现在基本都是24位真彩图,所以这个基本可以不考虑,所以才是读取54字节以后的为位图数据。

                                                       调色板个人理解:     类似超市寄存物品,每个格子就是一种颜色,通过你的编号(即位图数据,注

                                                                                           意这里每个像素不是3字节,而是因多少位的图片而异),拿到你想要的颜色。

//提供方法://1、取得图片信息。//2、取得图片数据。class readBmp{public:boolgetWidth(long &);//得到位图的宽boolgetHeight(long &);//得到位图的高boolgetBit(int &);//得到位图位数boolgetData(bit_t *&);//读取文件的颜色信息,即像素部分,即rgb信息readBmp(const char *path);~readBmp();private:intbitWidth;intbitHeight;intbitBit;boolbitReadSuccess;//记录构造函数里bmp文件是否成功读取void distroy();bit_t   *tmpData;//从文件读出的数据地址boolisInforExisted(void);//判断bmp文件是否成功读取};/*在构造函数里读取所需的文件信息,包括像素高度、宽度、和位图数据自己申请的空间在析构函数释放*/readBmp::readBmp(const char *path){ifstream  bmpFile(path, ios::in | ios::binary);//创建一个读取文件流对象, 不自动生成不存在文件,二进制方式打开bitReadSuccess = bmpFile.good();//判断文件是否成功打开if(true == bitReadSuccess){bmpFile.seekg(18, ios::beg);//bmp文件结构中WIDTH 和 HEIGHT为long类型,在19-26字节bmpFile.read((char *)(&bitWidth), 4);//由于read方法参数类型限制,要将第一个参数如左处理//64位系统g++ long 为8字节,不能用sizeof(long)获取字节数,否则文件指针偏移量过大,读取过多bmpFile.read((char *)(&bitHeight), 4);bmpFile.seekg(2, ios::cur);bmpFile.read((char *)(&bitBit), sizeof (bitBit));if (bitBit != 24)//暂时只考虑24位图片的读取,2、4、8位的可以以后扩充{cout << "非24位图片,请选择合适的图片重试!" << endl;}else{long count = 0;bmpFile.seekg(22, ios::cur);tmpData = new bit_t[bitWidth * bitHeight * 3];longskipWidth = (4 - bitWidth*3%4)%4;//计算每行读取时要skip的字节数for(int i = bitHeight - 1; i >= 0; i--){for( int j = 0; j < bitWidth * 3; j++)bmpFile.get(tmpData[count++]);bmpFile.seekg(skipWidth, ios::cur);//跳过计算过的字节数}}bmpFile.close();}}boolreadBmp::getWidth(long &width)//获取像素宽度{if (isInforExisted() == true)width = bitWidth;elsereturn false;return true;}boolreadBmp::getHeight(long &height)//获取像素高度{if (isInforExisted() == true)height = bitHeight;elsereturn false;return true;}boolreadBmp::getBit(int &bit)//获取位图位数{if (isInforExisted() == true)bit = bitBit;elsereturn false;return true;}boolreadBmp::isInforExisted()//判断文件信息是否读入{if (false == bitReadSuccess)cout << "图片文件信息读取失败,请重新读取后重试!" << endl;return bitReadSuccess;}boolreadBmp::getData(bit_t *&data){if (isInforExisted() == true)data = tmpData;elsereturn false;return true;}readBmp::~readBmp(){delete []tmpData;}


  2、数据处理。

   位图数据读取完成应该得到一个一维数组,总共biWidth*biHeight*3个字节。(本人用char类型存储)

    1灰度化。

          像素信息RGB 3个分量一般是不的,共同表达一中颜色信息。当R=G=B时表达的是一种灰色。因此将图像灰度化只需要将

    3个分量的值变为相等的即可,可以采用加权法、最大值法等,具体可百度“灰度化”。

      注意:采用何种方法灰度化对后面的二值化影响很大,可分别采用测试效果,由于本人处理的图片比较特殊,用的最大值法。

                   前面得到的数组在这一步可以转化为一个二维数组  arr[biHeight][biWidth]  (3个分量可合并为1个)。

     2)二值化。

           其实就是根据算法或经验从位图数据中得到一个值(网称阈值),遍历灰度化后得到的二维数组,利用这个值将数组二值化。

     我用的是全域迭代法,效果一般,求阈值可自选算法或自选值。

     3)去噪。

            也就是去除图片中的干扰点、杂点。我用的是很屌丝的与周围的点比较异同的方法(多调用几遍效果也还不错)。大神自寻算法

//一些图片的处理方法//创建此对象时同时进行灰度化class sortBmp{public:sortBmp(int width, int height, char *indata);~sortBmp();int getThreshold(); //计算阈值    bool            binarize();     //二值化    bool            show();//输出数据信息    bool            noiseSort();                                //去噪int   **getData();private:boolisGrey;boolisBinary;intbitWidth;intbitHeight;int   **bmpData;intmaxGreyValue, minGreyValue;    int             Threshold;intgreyValueExisted[256], greyValueCount[256], greyValueExistedCount;boolisGreyValueExisted(int);booladdGreyValueCount(int);booladdGreyValueMember(int);boolgreyValueExistedSort();doublesum_data(int, int, int *, int *);double sum_data(int, int, int *);    bool            deleteNoisePoint();};sortBmp::sortBmp(const int width, const int height, char *indata):bitWidth(width), bitHeight(height){int i, j, k, key;    int tmp[3];//动态申请一个二维数组bmpData = new int*[height];for(k = 0; k < height; k++){ bmpData[k] = new int[width];}//key记录以为数组的下标key = -1;//注意第一维的下标,以此实现数据的倒置for (i = height - 1; i >= 0; i--)for (j = 0; j < width; j++) {//bmpData[i][j] = ((indata[++key] & 0xFF)*0.3 + (indata[++key] & 0xFF)*0.59 + (indata[++key] & 0xFF)*0.11);  //jia quan//            bmpData[i][j] = ((indata[++key] & 0xFF) + (indata[++key] & 0xFF) + (indata[++key] & 0xFF))/3;               //ping jun           tmp[0] = indata[++key] & 0xFF;           tmp[1] = indata[++key] & 0xFF;           tmp[2] = indata[++key] & 0xFF;//与0xff与运算char转换为int           bmpData[i][j] = (tmp[0]>tmp[1]?tmp[0]:tmp[1])>tmp[2]?(tmp[0]>tmp[1]?tmp[0]:tmp[1]):tmp[2];}isGrey = true;    isBinary = false;//测试输出//cout << endl << endl;//for(int i = 0; i < width*height*3; i++)//cout << (indata[i] & 0xFF) << '\t';//cout << endl; }sortBmp::~sortBmp(){ for (int i = 0; i < bitHeight; i++)delete []bmpData[i];delete []bmpData;//delete []bmpData;//cout << "对象析构。" << endl;}intsortBmp::getThreshold() //获取阈值的算法。 本质是渐进到一个合适的值{intthreshold[2], tmpThreshold; //用来保存初始阈值、缓存阈值和最终的阈值maxGreyValue = **bmpData;   minGreyValue = **bmpData;for (int i = 0; i < 256; i++)greyValueCount[i] = 0;greyValueExistedCount = 0;for (int i = 0; i < bitHeight; i++)for (int j = 0; j < bitWidth; j++){//获取最大最小灰度值//测试语句//cout << bmpData[i][j] << ' ' ;if (maxGreyValue < bmpData[i][j])maxGreyValue = bmpData[i][j];if (minGreyValue > bmpData[i][j])minGreyValue = bmpData[i][j];//如果灰度值已录入,就增加统计的数目,如果未录入,就将其录入if (isGreyValueExisted(bmpData[i][j]) == true)addGreyValueCount(bmpData[i][j]);elseaddGreyValueMember(bmpData[i][j]);//此函数要在greyValueExisted里添加数据,修改greyValueCount和greyValueExistedCount的值 }greyValueExistedSort();//准备数据完毕。threshold[0] = 0;threshold[1] = (maxGreyValue + minGreyValue)/2;while(threshold[1] != threshold[0]){tmpThreshold =             0.4 * (            sum_data(greyValueExisted[0], threshold[1], greyValueExisted, greyValueCount)            /sum_data(greyValueExisted[0], threshold[1], greyValueCount)            +            sum_data(threshold[1]+1, greyValueExisted[greyValueExistedCount-1], greyValueExisted, greyValueCount)            /sum_data(threshold[1]+1, greyValueExisted[greyValueExistedCount-1], greyValueCount)            );threshold[0] = threshold[1];threshold[1] = tmpThreshold; }    Threshold = threshold[1];   return threshold[1];} double sortBmp::sum_data(int start_value, int end_value, int *value_data, int *count_data){int sum_4_arg = 0;    int i = 0;    while (greyValueExisted[i] < start_value)        i++;while (greyValueExisted[i] <= end_value && i < greyValueExistedCount)    {sum_4_arg += value_data[i]*count_data[i];            i++;     }return sum_4_arg;} double sortBmp::sum_data(int start_value, int end_value, int *count_data){ int sum_3_arg = 0;int i = 0;    while (greyValueExisted[i] < start_value)        i++;while (greyValueExisted[i] <= end_value && i < greyValueExistedCount)    {sum_3_arg += count_data[i];            i++;     }return sum_3_arg;} bool sortBmp::isGreyValueExisted(int data){if (greyValueExistedCount == 0)return false;for (int i = 0; i < greyValueExistedCount; i++)if (data == greyValueExisted[i])return true;return false;} boolsortBmp::addGreyValueMember(int data){ greyValueExisted[greyValueExistedCount] = data;greyValueCount[greyValueExistedCount]++;greyValueExistedCount++;return true;}bool sortBmp::addGreyValueCount(int data){for (int i = 0; i < greyValueExistedCount; i++)if (greyValueExisted[i] == data)greyValueCount[i]++;return true;} boolsortBmp::greyValueExistedSort(){int tmp_existed, tmp_count;for (int i = 0; i < greyValueExistedCount - 1; i++)for (int j = i+1; j < greyValueExistedCount; j++){if (greyValueExisted[i] > greyValueExisted[j]){tmp_existed = greyValueExisted[i];greyValueExisted[i] = greyValueExisted[j];greyValueExisted[j] = tmp_existed;tmp_count = greyValueCount[i];greyValueCount[i] = greyValueCount[j];greyValueCount[j] = tmp_count;}  }return true;}//以上都是为算法服务 -。-bool    sortBmp::binarize(){    for (int i = 0; i < bitHeight; i++)        for (int j = 0; j < bitWidth; j++)            if (bmpData[i][j] > Threshold)                bmpData[i][j] = 255;            else                bmpData[i][j] = 0;    return true;}bool    sortBmp::show(){    for(int i = 0; i < bitWidth; i++)        cout << '-';    cout << endl;    for (int i = 0; i < bitHeight; i++)    {        for (int j = 0; j < bitWidth; j++)            if (bmpData[i][j] == 0)                cout << '*';            else                cout << ' ';        cout << '|' << endl;           }    for(int i = 0; i < bitWidth; i++)        cout << '-';    cout << endl;/*    for (int i = 0; i < bitHeight; i++)    {        for (int j = bitWidth+1; j < bitWidth; j++)            if (bmpData[i][j] == 0)                cout << '*';            else                cout << ' ';      } */ }bool    sortBmp::deleteNoisePoint() //在这里去噪,道理简单,写起来真难受{    for (int i = 1; i < bitHeight-1; i++)        for (int j = 1; j < bitWidth-1; j++)            if (((bmpData[i-1][j-1] != bmpData[i][j]) + (bmpData[i-1][j] != bmpData[i][j]) + (bmpData[i-1][j+1] != bmpData[i][j]) + (bmpData[i][j-1] != bmpData[i][j]) + (bmpData[i][j+1] != bmpData[i][j]) + (bmpData[i+1][j-1] != bmpData[i][j]) + (bmpData[i+1][j] != bmpData[i][j]) + (bmpData[i+1][j+1] != bmpData[i][j])) >= 7)//用6会损失很多数据点,选择后续处理3个噪点在一起的情况                 bmpData[i][j] = 255;//~bmpData[i][j];   //处理4个角 /*  if ((bmpData[0][0] != bmpData[0][1]) + (bmpData[0][0] != bmpData[1][0]) + (bmpData[0][0] != bmpData[1][1]) >= 2)        bmpData[0][0] = ~bmpData[0][0];    if ((bmpData[0][bitWidth-1] != bmpData[0][bitWidth-2]) + (bmpData[0][bitWidth-1] != bmpData[1][bitWidth-2]) + (bmpData[0][bitWidth-1] != bmpData[1][bitWidth-1]) >= 2)        bmpData[0][bitWidth-1] = ~bmpData[0][0];    if ((bmpData[bitHeight-1][0] != bmpData[bitHeight-2][0]) + (bmpData[bitHeight-1][0] != bmpData[bitHeight-2][1]) + (bmpData[bitHeight-1][0] != bmpData[bitHeight-1][1]) >= 2)        bmpData[0][0] = ~bmpData[0][0];    if ((bmpData[bitHeight-1][bitWidth-1] != bmpData[bitHeight-2][bitWidth-1]) + (bmpData[bitHeight-1][bitWidth-1] != bmpData[bitHeight-2][bitWidth-2]) + (bmpData[bitHeight-1][bitWidth-1] != bmpData[bitHeight-1][bitWidth-2]) >= 2)        bmpData[0][0] = ~bmpData[0][0];        */    bmpData[0][0] = bmpData[0][bitWidth-1] = bmpData[bitHeight-1][0] = bmpData[bitHeight-1][bitWidth-1] = 255;    //处理除角的边界    for (int i = 1; i < bitWidth-2; i++)        if ((bmpData[0][i] != bmpData[0][i-1]) + (bmpData[0][i] != bmpData[0][i+1]) + (bmpData[0][i] != bmpData[1][i]) + (bmpData[0][i] != bmpData[1][i-1]) + (bmpData[0][i] != bmpData[1][i+1]) >= 4)            bmpData[0][i] = 255;//~bmpData[0][i];    for (int i = 1; i < bitHeight-2; i++)        if ((bmpData[i][0] != bmpData[i-1][0]) + (bmpData[i][0] != bmpData[i+1][0]) + (bmpData[i][0] != bmpData[i][1]) + (bmpData[i][0] != bmpData[i-1][1]) + (bmpData[i][0] != bmpData[i+1][1]) >= 4)            bmpData[i][0] = 255;//~bmpData[i][0];    for (int i = 1; i < bitWidth-2; i++)        if ((bmpData[bitHeight-1][i] != bmpData[bitHeight-1][i-1]) + (bmpData[bitHeight-1][i] != bmpData[bitHeight-1][i+1]) + (bmpData[bitHeight-1][i] != bmpData[bitHeight-2][i]) + (bmpData[bitHeight-1][i] != bmpData[bitHeight-2][i-1]) + (bmpData[bitHeight-1][i] != bmpData[bitHeight-2][i+1]) >= 4)            bmpData[bitHeight-1][i] = 255;//~bmpData[bitHeight-1][i];   for (int i = 1; i < bitHeight-2; i++)        if ((bmpData[i][bitWidth-1] != bmpData[i-1][bitWidth-1]) + (bmpData[i][bitWidth-1] != bmpData[i+1][bitWidth-1]) + (bmpData[i][bitWidth-1] != bmpData[i][bitWidth-2]) + (bmpData[i][bitWidth-1] != bmpData[i-1][bitWidth-2]) + (bmpData[i][bitWidth-1] != bmpData[i+1][bitWidth-2]) >= 4)            bmpData[i][bitWidth-1] = 255;//~bmpData[i][bitWidth-1];    return true;}bool    sortBmp::noiseSort()   //多调用即便去噪更彻底{    deleteNoisePoint();    deleteNoisePoint();    deleteNoisePoint();    return true;}int**sortBmp::getData(){return bmpData;}


 3、字符分割。

    其实从数据处理开始可选行就很强了,流程是一样的,方法却有很多,可以根据自己的能力和想要的效果自己选取合适的算法。

    由于我要处理的图片只有4个字符且字符都是分开的,所以我也就遍历这个二维数组,得到8对边界, 每个字符两对。


  4、模板匹配。

    这个算法可选行也很强,由于要求不高我还是用的最简单的方法。

   将所有字符高、宽中的最大值最为二维数组的两个范围,这个限定大小的二维数组加上一个char类型数据就是一个模板。

    比如所有的字符最宽的1个宽为12,最的一个为14,那么模板就是arr[14][12]、在加上其对应字符。

    遇到一个字符先判断模板文件中是否存在,存在就输出对应信息。

    如果不存在,将字符存入这个数组,然后将这个二维数组和对应的字符(自己输入)存入文件。  

class recognizeBmp{ public:recognizeBmp(int, int, int **);    void        showCharacter();    void        showResult();private:    int         wide_range[8];    int         height_range[8];int bitHeight;int bitWidth;    int       **bmpData;voidgetCharacter(void);voidgetRanges(void);void get_inform(void);bool recognize(int (*)[16], char &, int, int);void addCharacter(int (*)[16], char);bool compare_char_arr(int (*)[16], int (*)[16], int, int);};recognizeBmp::recognizeBmp(int height, int width, int**tmpData):bitWidth(width), bitHeight(height), bmpData(tmpData){getRanges();} void    recognizeBmp::getRanges(void)//取得边界范围{int  i = 0;    int      h          = 0;    int      w          = 0;    long     sum        = 255;    long     tmp_sum    = 255;    for (w = 0; w < bitWidth; w++)//处理4对左右边界    {        tmp_sum = sum;        sum = 255;        h = 0;        while (h < bitHeight)        {            sum &= bmpData[h++][w];        }        if (sum != tmp_sum)            wide_range[i++] = w;    }sum = 255;for (h = 0; h < bitHeight; h++){sum &= bmpData[h][bitWidth-1];}if (sum == 0)wide_range[7] = bitWidth-1;    for (i = 1; i <= 7; i+=2)//处理4对上下边界        if (wide_range[i] != bitWidth-1)            wide_range[i]--;      for (int count = 0; count < 4; count++)    { tmp_sum = 255;sum = 255;        for (h = 0; h < bitHeight; h++)        {tmp_sum = sum; //从上找上边界sum = 255;            for (w = wide_range[2*count]; w < wide_range[2*count+1]; w++)            {                sum &= bmpData[h][w];             } if (tmp_sum != sum) {                height_range[2*count] = h;break; }        }tmp_sum = 255;sum = 255;for (h = bitHeight-1; h >= 0; h--){tmp_sum = sum; //从下找下边界sum = 255;for (w = wide_range[2*count]; w < wide_range[2*count+1]; w++){sum &= bmpData[h][w];}if (tmp_sum != sum){  height_range[2*count+1] = h;  break;}}}            for (int count = 0; count < 4; count++)//假如有字符边界在最底部的话{sum = 255;for (i = wide_range[2*count]; i < wide_range[2*count+1]; i++){sum &= bmpData[bitHeight-1][w];}if (sum != 255)  height_range[2*count +1] = bitHeight-1;} } voidrecognizeBmp::showCharacter(void)//输出字符{for (int count = 0; count < 4; count++){for (int i = height_range[count*2]; i <= height_range[count*2+1]; i++){for (int j = wide_range[count*2]; j <= wide_range[count*2+1]; j++){if (bmpData[i][j] == 255)cout << ' ';elsecout << '*';}cout << endl;}cout << endl;}}/*void recognizeBmp::normalize(void){int sum = 0;int tmp_char[10][10] = {0};for (int c = 0; c < 4; c++){for (int i = 0; i < 10; i++)for (int j = 0; j < 10; j++){sum = 0;for (int h = height_range[2*c]+i*(height_range[2*c+1]-height_range[2*c]+1)/10; h < height_range[2*c]+(i+1)*(height_range[2*c+1]-height_range[2*c]+1)/10   && h <= height_range[2*c+1];  h++)for (int w = wide_range[2*c]+j*(wide_range[2*c+1]-wide_range[2*c]+1)/10;w < wide_range[2*c]+(j+1)*(wide_range[2*c+1]-wide_range[2*c]+1)/10&& w <= wide_range[2*c+1]; w++)if (bmpData[h][w] == 0)sum++;//if (sum > (i*(height_range[2*c+1]-height_range[2*c]+1)/10 +1 )*(j*(wide_range[2*c+1]-wide_range[2*c]+1)/10 +1 )*0.4 && sum != 0)if (sum >= 2)tmp_char[i][j] = 1;}//测试语句for (int i = 0; i < 10; i++){for (int j = 0; j < 10; j++)cout << tmp_char[i][j] << ' ';cout << endl;}cout << endl;}}*/ //归一化失败,另寻它法void recognizeBmp::get_inform(void){int tmp_char[16][16] = {0};//字符最大有16int i = 0;int j = 0;int width = 0;int height = 0;char character;for (int c = 0; c < 4; c++)//4个字符4次识别或者录入{height = height_range[2*c+1] - height_range[2*c] + 1;width  =   wide_range[2*c+1] -   wide_range[2*c] + 1;i = 0;for (int h = height_range[2*c]; h <= height_range[2*c+1]; h++)  //只输入字符范围的数据{j = 0;for (int w = wide_range[2*c]; w <= wide_range[2*c+1]; w++){tmp_char[i][j] = bmpData[h][w];j++;}i++;}if (true == recognize(tmp_char, character, height, width))  //检验是否可识别cout << "第" << c+1 << "个字符是:" << character << endl;else{cout << "字符信息不存在,请添加以便下次使用" << endl << "字符:" << endl;  //不可识别的话就录入for (int h = height_range[2*c]; h <= height_range[2*c+1]; h++) //输出让用户判断是什么字符{for (int w = wide_range[2*c]; w <= wide_range[2*c+1]; w++)if (bmpData[h][w] == 0)cout << '*';elsecout << ' ';cout << endl;}cout << endl << "第" << c+1 << "个字符是:" << endl;cin >> character;addCharacter(tmp_char, character);}}}void recognizeBmp::showResult(void){get_inform();cout << endl << endl;}bool recognizeBmp::recognize(int (*tmp_char)[16], char &character, int height, int width){int test_char[16][16] = {0};if ((double)height/width >= 1.5) //对特殊字符的处理,提高准确率,不过貌似没什么效果,不知到问题在哪{width *= 2;}ifstream infile("../data/characters", ios::binary | ios::in);if (infile.eof())return false;while (!infile.eof()){infile.read((char *)test_char, 16*16*4);character = infile.get();if (true == compare_char_arr(tmp_char, test_char, height, width))  //字符与模板比较return true;}return false;infile.close();}void recognizeBmp::addCharacter(int (*tmp_char)[16], char character) //存入新的模板{ofstream outfile("../data/characters", ios::binary | ios::app);outfile.write((char *)tmp_char, 16*16*4);outfile.put(character);outfile.close();}bool recognizeBmp::compare_char_arr(int (*tmp_char)[16], int (*test_char)[16], int height, int width){int count = 0;for (int i = 0; i < height; i++)for (int j = 0; j < width; j++)if (tmp_char[i][j] == test_char[i][j])count++;if(count >= height*width*7/8)  //  7/8是多次测试得到的合适的值return true;elsereturn false;}



  基本就这么多了,只是给毫无头绪的朋友们提供一个思路,具体细节可以自己实现。

 这个程序其实是未完成的,所以有很多缺陷和限制。

  本来的思路:

               1、先扫描一定数量的图片,启动学习模块,将未识别的字符存入模板文件,最终得到一个模板文件。

                 2、识别图片, 启动识别模块,无法识别直接跳过识别下一个图片(假如以刷票为目的,保证一定成功率就行了)


  最后,我的环境是linux g++


  完整代码已上传,传送门:bmp验证码识别

 

原创粉丝点击