证件文字信息识别[JAVA调用C++]

安卓端上传校园卡照片,经识别之后返回识别结果(识别姓名、学号、学院)。
校园卡样例


证件文字信息识别[JAVA调用C++]_第1张图片
9.jpg

识别结果


证件文字信息识别[JAVA调用C++]_第2张图片
捕获.JPG

【&&&是为了后续处理而设立的分隔符】
在这里只说明图像处理的过程
最后附上全部代码

  1. 总流程和框架
    二值化处理图片,形态学处理得到文字区域,对满足条件的区域进行分割,将分割出的图进行OCR识别出字符串,最后JAVA利用JNA调用dll [ vs里创建的项目类型要选桌面-dll ]

使用到的框架 openCV, tesseract, JNA
头文件

#include  //Include file for every supported OpenCV function
#include  
#include 
#include 
#ifndef CARD_RECOGNIZER
#define CARD_RECOGNIZER

cv::Mat mat, grayMat;
std::vector textAreaRaw;
cv::Rect recognizeArea;

const int IMAGE_SIZE_WIDTH = 1400;      //horizontal direction
const int IMAGE_SIZE_HEIGHT = 900;      //vertical direction
const int INFO_COUNT = 3;               //the count of the text information that needs to be recognized
const int SAMPLE_SIZE_WIDTH = 100;
const int SAMPLE_SIZE_HEIGHT = 30;
const int RECOGNITION_SUCCESS = 1;
const int RECOGNITION_FAIL = 2;

const char* RECOGNIZATION_FAIL_STRING = "FAIL";

tesseract::TessBaseAPI tess;

extern "C" __declspec( dllexport ) const char* recognize(const char* filePath);
void gamma(cv::Mat& src, double c, double gamma);
void binaryProcess();
void segmentBinary(cv::Mat & mat);
int locateText(cv::Mat& inversed);
bool validArea(const cv::RotatedRect& rect);
void normalizeSingleArea(const cv::Mat& src, cv::RotatedRect& rawArea, cv::Mat& result);
char* UTF8ToANSI(const char * uft8);
wchar_t * Utf_8ToUnicode(const char * szU8);
char * UnicodeToAnsi(const wchar_t * szStr);
#endif // CARD_RECOGNIZER

注意要暴露给java的函数前要加上extern "C" __declspec( dllexport )

识别函数

const char * recognize(const char * filePath) {
    using namespace cv;
    //initialize the mat and gray mat according to filePath
    mat = imread(filePath, CV_LOAD_IMAGE_GRAYSCALE);
    resize(mat, mat, cv::Size(IMAGE_SIZE_WIDTH, IMAGE_SIZE_HEIGHT));
    grayMat = mat.clone();

    tess.Init(NULL, "chi_sim", tesseract::OEM_DEFAULT);
    tess.SetPageSegMode(tesseract::PSM_SINGLE_BLOCK);
    recognizeArea = cv::Rect(0.45*IMAGE_SIZE_WIDTH, 0.25*IMAGE_SIZE_HEIGHT, 0.4*IMAGE_SIZE_WIDTH, 0.455*IMAGE_SIZE_HEIGHT);

    binaryProcess();                // Binary Process

    // Get the inversed binary image.
    Mat inversed;
    bitwise_not(mat, inversed);

    // Get the valid contours and raw text areas
    if ( locateText(inversed) == RECOGNITION_FAIL ) {
        return RECOGNIZATION_FAIL_STRING;
    }

    // Sort the text areas by their position
    struct sortByY {
        bool operator () (const RotatedRect & a, const RotatedRect & b) {
            return a.center.y < b.center.y;
        }
    };
    std::sort(textAreaRaw.begin(), textAreaRaw.end(), sortByY());

    std::vector segments(INFO_COUNT);
    std::string result;
    for ( int i = 0; i < INFO_COUNT; ++i ) {
        normalizeSingleArea(grayMat, textAreaRaw[ i ], segments[ i ]);
        segmentBinary(segments[ i ]);
        tess.SetImage(( uchar* ) segments[ i ].data, segments[ i ].cols, segments[ i ].rows, 1, segments[ i ].cols);
        char* resultUTF8 = tess.GetUTF8Text();
        result.append(resultUTF8);
        result.append("&&&");
        delete[] resultUTF8;
    }
    return UTF8ToANSI(result.c_str());
}

2.二值化处理
卡片拍摄受光照影响很大,加上卡片纹路,使用gamma变化之后加以自适应能够得到较好的效果

void gamma(cv::Mat & mat, double c, double gamma) {
    using namespace cv;
    Mat matFloat(mat.size(), CV_32FC1);
    for ( int i = 0; i < mat.rows; ++i ) {
        for ( int j = 0; j < mat.cols; ++j ) {
            matFloat.at(i, j) = c * pow(mat.at(i, j), gamma);
        }
    }
    normalize(matFloat, matFloat, 0, 255, CV_MINMAX);
    convertScaleAbs(matFloat, mat);
}

/*Process of a whole mat.*/
/*Works*/
void binaryProcess() {
    cv::Mat tempSRC = mat.clone();
    gamma(tempSRC, 1, 0.001);

    int blockSize = 31;
    int C = 50;
    int maxVal = 255;

    cv::adaptiveThreshold(tempSRC, mat, maxVal, CV_ADAPTIVE_THRESH_GAUSSIAN_C, CV_THRESH_BINARY, blockSize, C);
}
  1. 文字定位
    大致分五步
    【反色】
    【腐蚀】去掉证件上可能存在的纹路图案信息
    【膨胀】获得文字区域
    【获取轮廓】
    【轮廓筛选】

这一步的效果是这样的

int locateText(cv::Mat& inversedMat) {

    cv::Mat inversed = inversedMat.clone();

    // Get the element for erosion
    cv::Mat lineEliminator = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(2, 2));

    // Perform erosion to get rid of the subtle lines
    cv::erode(inversed, inversed, lineEliminator);

    // Perform dilation to make the contours more prominent
    cv::Mat dilator = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(8, 6));
    cv::dilate(inversed, inversed, dilator, cv::Point(-1, -1), 4);

    // Get the contours
    std::vector< std::vector  > contours;
    findContours(inversed, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);           //CV_RETR_EXTERNAL:Retrieves exterior contours only
    
    // Erase bad contours
    std::vector>::iterator iterator = contours.begin();
    while ( iterator != contours.end() ) {
        cv::RotatedRect minRec = cv::minAreaRect(cv::Mat(*iterator));
        if ( validArea(minRec) ) {                                                      //The valid method should be ajusted with different situation.
            textAreaRaw.push_back(minRec);
            ++iterator;
        } else {
            iterator = contours.erase(iterator);
        }
    }

    if ( textAreaRaw.size() != INFO_COUNT ) {
        return RECOGNITION_FAIL;
    }

    return RECOGNITION_SUCCESS;
}

bool validArea(const cv::RotatedRect & rect) {
    if ( recognizeArea.contains(rect.center) ) {
        return true;
    } else {
        return false;
    }
}
证件文字信息识别[JAVA调用C++]_第3张图片
文字定位.PNG

=========================================================
validArea方法应该结合具体实例来写【拿尺子量比例】,比如我现在要处理的卡片信息是这种情况的


证件文字信息识别[JAVA调用C++]_第4张图片
卡片参数.jpg

那我就应该结合这些参数来判断【先假设一个合理的参数变化范围,然后解个不等式即可】

对于这个校园卡来说
证件文字信息识别[JAVA调用C++]_第5张图片
文字定位2.PNG

要识别的部分是圈起来的部分【在证件规格一致、用户按要求拍摄的情况下】

经过筛选之后应该将其排序

    // Sort the text areas by their position
    struct sortByY {
        bool operator () (const RotatedRect & a, const RotatedRect & b) {
            return a.center.y < b.center.y;
        }
    };
    std::sort(textAreaRaw.begin(), textAreaRaw.end(), sortByY());
  1. 切割
void normalizeSingleArea(const cv::Mat & src, cv::RotatedRect & rawArea, cv::Mat & result) {
    float r, angle;
    angle = rawArea.angle;
    r = ( float ) rawArea.size.width / ( float ) ( float ) rawArea.size.height;

    if ( r < 1 ) {
        angle = 90 + angle;
    }

    cv::Mat rotmat = cv::getRotationMatrix2D(rawArea.center, angle, 1);
    cv::Mat img_rotated;
    warpAffine(src, img_rotated, rotmat, src.size(), CV_INTER_CUBIC);

    //Crop the image
    cv::Size rect_size = rawArea.size;

    if ( r<1 )
        std::swap(rect_size.width, rect_size.height);

    cv::getRectSubPix(img_rotated, rect_size, rawArea.center, result);
}
  1. 识别
    利用训练好的库对子图进行识别即可
    std::vector segments(INFO_COUNT);
    std::string result;
    for ( int i = 0; i < INFO_COUNT; ++i ) {
        normalizeSingleArea(grayMat, textAreaRaw[ i ], segments[ i ]);
        segmentBinary(segments[ i ]);
        tess.SetImage(( uchar* ) segments[ i ].data, segments[ i ].cols, segments[ i ].rows, 1, segments[ i ].cols);
        char* resultUTF8 = tess.GetUTF8Text();
        result.append(resultUTF8);
        result.append("&&&");
        delete[] resultUTF8;
    }
    return UTF8ToANSI(result.c_str());

char* UTF8ToANSI(const char * uft8) {
    wchar_t* temp = Utf_8ToUnicode(uft8);       //unicode sequence
    char* result = UnicodeToAnsi(temp);
    delete[] temp;
    return result;
}

wchar_t * Utf_8ToUnicode(const char * szU8) {
    //UTF8 to Unicode
    //由于中文直接复制过来会成乱码,编译器有时会报错,故采用16进制形式

    //预转换,得到所需空间的大小
    int wcsLen = ::MultiByteToWideChar(CP_UTF8, NULL, szU8, strlen(szU8), NULL, 0);
    //分配空间要给'\0'留个空间,MultiByteToWideChar不会给'\0'空间
    wchar_t* wszString = new wchar_t[ wcsLen + 1 ];
    //转换
    ::MultiByteToWideChar(CP_UTF8, NULL, szU8, strlen(szU8), wszString, wcsLen);
    //最后加上'\0'
    wszString[ wcsLen ] = '\0';
    return wszString;
}

char * UnicodeToAnsi(const wchar_t * szStr) {

    int nLen = WideCharToMultiByte(CP_ACP, 0, szStr, -1, NULL, 0, NULL, NULL);
    if ( nLen == 0 ) {
        return NULL;
    }
    char* pResult = new char[ nLen ];

    WideCharToMultiByte(CP_ACP, 0, szStr, -1, pResult, nLen, NULL, NULL);

    return pResult;
}

UTF8ToANSI(result.c_str())是为了消除乱码【这里略麻烦】
加上“&&&”是方便java里调用split得到三个string
生成一下,生成DLL之后就可以用了

[附java使用例子]

    public interface CppLibrary extends Library {
        //加载链接库
        CppLibrary INSTANTCE = (CppLibrary) Native.loadLibrary("C:\\Coding\\cpp\\CardRecognizerDll\\CardRecognizer\\x64\\Debug\\CardRecognizer.dll", CppLibrary.class);
        //此方法为链接库中的方法
        String recognize(String filePath);
    }

    public static void main(String[] args) {
        System.setProperty("jna.encoding", "GB2312");               //necessary
        String filePath = "C:\\Users\\10068\\Desktop\\9.jpg";       //路径里不要有中文
        String result = CppLibrary.INSTANTCE.recognize(filePath);   //encoding: gb2312 到这一步OK
        System.out.println("GB2312:\n " + result);                                  //OK
    }

你可能感兴趣的:(证件文字信息识别[JAVA调用C++])