diff --git a/src/main/resources/easypr/core/chars_identify.cpp b/src/main/resources/easypr/core/chars_identify.cpp new file mode 100644 index 00000000..a88d7277 --- /dev/null +++ b/src/main/resources/easypr/core/chars_identify.cpp @@ -0,0 +1,454 @@ +#include "easypr/core/chars_identify.h" +#include "easypr/core/character.hpp" +#include "easypr/core/core_func.h" +#include "easypr/core/feature.h" +#include "easypr/core/params.h" +#include "easypr/config.h" + +using namespace cv; + +namespace easypr { + +CharsIdentify* CharsIdentify::instance_ = nullptr; + +CharsIdentify* CharsIdentify::instance() { + if (!instance_) { + instance_ = new CharsIdentify; + } + return instance_; +} + +CharsIdentify::CharsIdentify() { + LOAD_ANN_MODEL(ann_, kDefaultAnnPath); + LOAD_ANN_MODEL(annChinese_, kChineseAnnPath); + LOAD_ANN_MODEL(annGray_, kGrayAnnPath); + + kv_ = std::shared_ptr(new Kv); + kv_->load(kChineseMappingPath); + + extractFeature = getGrayPlusProject; +} + +void CharsIdentify::LoadModel(std::string path) { + if (path != std::string(kDefaultAnnPath)) { + if (!ann_->empty()) + ann_->clear(); + LOAD_ANN_MODEL(ann_, path); + } +} + +void CharsIdentify::LoadChineseModel(std::string path) { + if (path != std::string(kChineseAnnPath)) { + if (!annChinese_->empty()) + annChinese_->clear(); + LOAD_ANN_MODEL(annChinese_, path); + } +} + +void CharsIdentify::LoadGrayChANN(std::string path) { + if (path != std::string(kGrayAnnPath)) { + if (!annGray_->empty()) + annGray_->clear(); + LOAD_ANN_MODEL(annGray_, path); + } +} + +void CharsIdentify::LoadChineseMapping(std::string path) { + kv_->clear(); + kv_->load(path); +} + +void CharsIdentify::classify(cv::Mat featureRows, std::vector& out_maxIndexs, + std::vector& out_maxVals, std::vector isChineseVec){ + int rowNum = featureRows.rows; + + cv::Mat output(rowNum, kCharsTotalNumber, CV_32FC1); + ann_->predict(featureRows, output); + + for (int output_index = 0; output_index < rowNum; output_index++) { + Mat output_row = output.row(output_index); + int result = 0; + float maxVal = -2.f; + bool isChinses = isChineseVec[output_index]; + if (!isChinses) { + result = 0; + for (int j = 0; j < kCharactersNumber; j++) { + float val = output_row.at(j); + // std::cout << "j:" << j << "val:" << val << std::endl; + if (val > maxVal) { + maxVal = val; + result = j; + } + } + } + else { + result = kCharactersNumber; + for (int j = kCharactersNumber; j < kCharsTotalNumber; j++) { + float val = output_row.at(j); + //std::cout << "j:" << j << "val:" << val << std::endl; + if (val > maxVal) { + maxVal = val; + result = j; + } + } + } + out_maxIndexs[output_index] = result; + out_maxVals[output_index] = maxVal; + } +} + + +void CharsIdentify::classify(std::vector& charVec){ + size_t charVecSize = charVec.size(); + + if (charVecSize == 0) + return; + + Mat featureRows; + for (size_t index = 0; index < charVecSize; index++) { + Mat charInput = charVec[index].getCharacterMat(); + Mat feature = charFeatures(charInput, kPredictSize); + featureRows.push_back(feature); + } + + cv::Mat output(charVecSize, kCharsTotalNumber, CV_32FC1); + ann_->predict(featureRows, output); + + for (size_t output_index = 0; output_index < charVecSize; output_index++) { + CCharacter& character = charVec[output_index]; + Mat output_row = output.row(output_index); + + int result = 0; + float maxVal = -2.f; + std::string label = ""; + + bool isChinses = character.getIsChinese(); + if (!isChinses) { + result = 0; + for (int j = 0; j < kCharactersNumber; j++) { + float val = output_row.at(j); + //std::cout << "j:" << j << "val:" << val << std::endl; + if (val > maxVal) { + maxVal = val; + result = j; + } + } + label = std::make_pair(kChars[result], kChars[result]).second; + } + else { + result = kCharactersNumber; + for (int j = kCharactersNumber; j < kCharsTotalNumber; j++) { + float val = output_row.at(j); + //std::cout << "j:" << j << "val:" << val << std::endl; + if (val > maxVal) { + maxVal = val; + result = j; + } + } + const char* key = kChars[result]; + std::string s = key; + std::string province = kv_->get(s); + label = std::make_pair(s, province).second; + } + /*std::cout << "result:" << result << std::endl; + std::cout << "maxVal:" << maxVal << std::endl;*/ + character.setCharacterScore(maxVal); + character.setCharacterStr(label); + } +} + + +void CharsIdentify::classifyChineseGray(std::vector& charVec){ + size_t charVecSize = charVec.size(); + if (charVecSize == 0) + return; + + Mat featureRows; + for (size_t index = 0; index < charVecSize; index++) { + Mat charInput = charVec[index].getCharacterMat(); + cv::Mat feature; + extractFeature(charInput, feature); + featureRows.push_back(feature); + } + + cv::Mat output(charVecSize, kChineseNumber, CV_32FC1); + annGray_->predict(featureRows, output); + + for (size_t output_index = 0; output_index < charVecSize; output_index++) { + CCharacter& character = charVec[output_index]; + Mat output_row = output.row(output_index); + bool isChinese = true; + + float maxVal = -2; + int result = 0; + + for (int j = 0; j < kChineseNumber; j++) { + float val = output_row.at(j); + //std::cout << "j:" << j << "val:" << val << std::endl; + if (val > maxVal) { + maxVal = val; + result = j; + } + } + + // no match + if (-1 == result) { + result = 0; + maxVal = 0; + isChinese = false; + } + + auto index = result + kCharsTotalNumber - kChineseNumber; + const char* key = kChars[index]; + std::string s = key; + std::string province = kv_->get(s); + + /*std::cout << "result:" << result << std::endl; + std::cout << "maxVal:" << maxVal << std::endl;*/ + + character.setCharacterScore(maxVal); + character.setCharacterStr(province); + character.setIsChinese(isChinese); + } +} + +void CharsIdentify::classifyChinese(std::vector& charVec){ + size_t charVecSize = charVec.size(); + + if (charVecSize == 0) + return; + + Mat featureRows; + for (size_t index = 0; index < charVecSize; index++) { + Mat charInput = charVec[index].getCharacterMat(); + Mat feature = charFeatures(charInput, kChineseSize); + featureRows.push_back(feature); + } + + cv::Mat output(charVecSize, kChineseNumber, CV_32FC1); + annChinese_->predict(featureRows, output); + + for (size_t output_index = 0; output_index < charVecSize; output_index++) { + CCharacter& character = charVec[output_index]; + Mat output_row = output.row(output_index); + bool isChinese = true; + + float maxVal = -2; + int result = 0; + + for (int j = 0; j < kChineseNumber; j++) { + float val = output_row.at(j); + //std::cout << "j:" << j << "val:" << val << std::endl; + if (val > maxVal) { + maxVal = val; + result = j; + } + } + + // no match + if (-1 == result) { + result = 0; + maxVal = 0; + isChinese = false; + } + + auto index = result + kCharsTotalNumber - kChineseNumber; + const char* key = kChars[index]; + std::string s = key; + std::string province = kv_->get(s); + + /*std::cout << "result:" << result << std::endl; + std::cout << "maxVal:" << maxVal << std::endl;*/ + + character.setCharacterScore(maxVal); + character.setCharacterStr(province); + character.setIsChinese(isChinese); + } +} + +int CharsIdentify::classify(cv::Mat f, float& maxVal, bool isChinses, bool isAlphabet){ + int result = 0; + + cv::Mat output(1, kCharsTotalNumber, CV_32FC1); + ann_->predict(f, output); + + maxVal = -2.f; + if (!isChinses) { + if (!isAlphabet) { + result = 0; + for (int j = 0; j < kCharactersNumber; j++) { + float val = output.at(j); + // std::cout << "j:" << j << "val:" << val << std::endl; + if (val > maxVal) { + maxVal = val; + result = j; + } + } + } + else { + result = 0; + // begin with 11th char, which is 'A' + for (int j = 10; j < kCharactersNumber; j++) { + float val = output.at(j); + // std::cout << "j:" << j << "val:" << val << std::endl; + if (val > maxVal) { + maxVal = val; + result = j; + } + } + } + } + else { + result = kCharactersNumber; + for (int j = kCharactersNumber; j < kCharsTotalNumber; j++) { + float val = output.at(j); + //std::cout << "j:" << j << "val:" << val << std::endl; + if (val > maxVal) { + maxVal = val; + result = j; + } + } + } + //std::cout << "maxVal:" << maxVal << std::endl; + return result; +} + +bool CharsIdentify::isCharacter(cv::Mat input, std::string& label, float& maxVal, bool isChinese) { + cv::Mat feature = charFeatures(input, kPredictSize); + auto index = static_cast(classify(feature, maxVal, isChinese)); + + if (isChinese) { + //std::cout << "maxVal:" << maxVal << std::endl; + } + + float chineseMaxThresh = 0.2f; + + if (maxVal >= 0.9 || (isChinese && maxVal >= chineseMaxThresh)) { + if (index < kCharactersNumber) { + label = std::make_pair(kChars[index], kChars[index]).second; + } + else { + const char* key = kChars[index]; + std::string s = key; + std::string province = kv_->get(s); + label = std::make_pair(s, province).second; + } + return true; + } + else + return false; +} + +std::pair CharsIdentify::identifyChinese(cv::Mat input, float& out, bool& isChinese) { + cv::Mat feature = charFeatures(input, kChineseSize); + float maxVal = -2; + int result = 0; + + cv::Mat output(1, kChineseNumber, CV_32FC1); + annChinese_->predict(feature, output); + + for (int j = 0; j < kChineseNumber; j++) { + float val = output.at(j); + //std::cout << "j:" << j << "val:" << val << std::endl; + if (val > maxVal) { + maxVal = val; + result = j; + } + } + + // no match + if (-1 == result) { + result = 0; + maxVal = 0; + isChinese = false; + } + else if (maxVal > 0.9){ + isChinese = true; + } + + auto index = result + kCharsTotalNumber - kChineseNumber; + const char* key = kChars[index]; + std::string s = key; + std::string province = kv_->get(s); + out = maxVal; + + return std::make_pair(s, province); +} + +std::pair CharsIdentify::identifyChineseGray(cv::Mat input, float& out, bool& isChinese) { + cv::Mat feature; + extractFeature(input, feature); + float maxVal = -2; + int result = 0; + cv::Mat output(1, kChineseNumber, CV_32FC1); + annGray_->predict(feature, output); + + for (int j = 0; j < kChineseNumber; j++) { + float val = output.at(j); + //std::cout << "j:" << j << "val:" << val << std::endl; + if (val > maxVal) { + maxVal = val; + result = j; + } + } + // no match + if (-1 == result) { + result = 0; + maxVal = 0; + isChinese = false; + } else if (maxVal > 0.9){ + isChinese = true; + } + auto index = result + kCharsTotalNumber - kChineseNumber; + const char* key = kChars[index]; + std::string s = key; + std::string province = kv_->get(s); + out = maxVal; + return std::make_pair(s, province); +} + + +std::pair CharsIdentify::identify(cv::Mat input, bool isChinese, bool isAlphabet) { + cv::Mat feature = charFeatures(input, kPredictSize); + float maxVal = -2; + auto index = static_cast(classify(feature, maxVal, isChinese, isAlphabet)); + if (index < kCharactersNumber) { + return std::make_pair(kChars[index], kChars[index]); + } + else { + const char* key = kChars[index]; + std::string s = key; + std::string province = kv_->get(s); + return std::make_pair(s, province); + } +} + +int CharsIdentify::identify(std::vector inputs, std::vector>& outputs, + std::vector isChineseVec) { + Mat featureRows; + size_t input_size = inputs.size(); + for (size_t i = 0; i < input_size; i++) { + Mat input = inputs[i]; + cv::Mat feature = charFeatures(input, kPredictSize); + featureRows.push_back(feature); + } + + std::vector maxIndexs; + std::vector maxVals; + classify(featureRows, maxIndexs, maxVals, isChineseVec); + + for (size_t row_index = 0; row_index < input_size; row_index++) { + int index = maxIndexs[row_index]; + if (index < kCharactersNumber) { + outputs[row_index] = std::make_pair(kChars[index], kChars[index]); + } + else { + const char* key = kChars[index]; + std::string s = key; + std::string province = kv_->get(s); + outputs[row_index] = std::make_pair(s, province); + } + } + return 0; +} +} diff --git a/src/main/resources/easypr/core/chars_recognise.cpp b/src/main/resources/easypr/core/chars_recognise.cpp new file mode 100644 index 00000000..d101fd37 --- /dev/null +++ b/src/main/resources/easypr/core/chars_recognise.cpp @@ -0,0 +1,117 @@ +#include "easypr/core/chars_recognise.h" +#include "easypr/core/character.hpp" +#include "easypr/util/util.h" +#include + +namespace easypr { + +CCharsRecognise::CCharsRecognise() { m_charsSegment = new CCharsSegment(); } + +CCharsRecognise::~CCharsRecognise() { SAFE_RELEASE(m_charsSegment); } + +int CCharsRecognise::charsRecognise(Mat plate, std::string& plateLicense) { + std::vector matChars; + int result = m_charsSegment->charsSegment(plate, matChars); + if (result == 0) { + int num = matChars.size(); + for (int j = 0; j < num; j++) + { + Mat charMat = matChars.at(j); + bool isChinses = false; + float maxVal = 0; + if (j == 0) { + bool judge = true; + isChinses = true; + auto character = CharsIdentify::instance()->identifyChinese(charMat, maxVal, judge); + plateLicense.append(character.second); + } + else { + isChinses = false; + auto character = CharsIdentify::instance()->identify(charMat, isChinses); + plateLicense.append(character.second); + } + } + + } + if (plateLicense.size() < 7) { + return -1; + } + + return result; +} + + +int CCharsRecognise::charsRecognise(CPlate& plate, std::string& plateLicense) { + std::vector matChars; + std::vector grayChars; + Mat plateMat = plate.getPlateMat(); + if (0) writeTempImage(plateMat, "plateMat/plate"); + Color color; + if (plate.getPlateLocateType() == CMSER) { + color = plate.getPlateColor(); + } + else { + int w = plateMat.cols; + int h = plateMat.rows; + Mat tmpMat = plateMat(Rect_(w * 0.1, h * 0.1, w * 0.8, h * 0.8)); + color = getPlateType(tmpMat, true); + } + + int result = m_charsSegment->charsSegmentUsingOSTU(plateMat, matChars, grayChars, color); + + if (result == 0) { + int num = matChars.size(); + for (int j = 0; j < num; j++) + { + Mat charMat = matChars.at(j); + Mat grayChar = grayChars.at(j); + if (color != Color::BLUE) + grayChar = 255 - grayChar; + + bool isChinses = false; + std::pair character; + float maxVal; + if (0 == j) { + isChinses = true; + bool judge = true; + character = CharsIdentify::instance()->identifyChineseGray(grayChar, maxVal, judge); + plateLicense.append(character.second); + + // set plate chinese mat and str + plate.setChineseMat(grayChar); + plate.setChineseKey(character.first); + if (0) writeTempImage(grayChar, "char_data/" + character.first + "/chars_"); + } + else if (1 == j) { + isChinses = false; + bool isAbc = true; + character = CharsIdentify::instance()->identify(charMat, isChinses, isAbc); + plateLicense.append(character.second); + } + else { + isChinses = false; + SHOW_IMAGE(charMat, 0); + character = CharsIdentify::instance()->identify(charMat, isChinses); + plateLicense.append(character.second); + } + + CCharacter charResult; + charResult.setCharacterMat(charMat); + charResult.setCharacterGrayMat(grayChar); + if (isChinses) + charResult.setCharacterStr(character.first); + else + charResult.setCharacterStr(character.second); + + plate.addReutCharacter(charResult); + } + if (plateLicense.size() < 7) { + return -1; + } + } + + return result; +} + + +} \ No newline at end of file diff --git a/src/main/resources/easypr/core/chars_segment.cpp b/src/main/resources/easypr/core/chars_segment.cpp new file mode 100644 index 00000000..3edc85a0 --- /dev/null +++ b/src/main/resources/easypr/core/chars_segment.cpp @@ -0,0 +1,1056 @@ +#include "easypr/core/chars_segment.h" +#include "easypr/core/chars_identify.h" +#include "easypr/core/core_func.h" +#include "easypr/core/params.h" +#include "easypr/config.h" +#include "thirdparty/mser/mser2.hpp" + +namespace easypr { + +const float DEFAULT_BLUEPERCEMT = 0.3f; +const float DEFAULT_WHITEPERCEMT = 0.1f; + +CCharsSegment::CCharsSegment() { + m_LiuDingSize = DEFAULT_LIUDING_SIZE; + m_theMatWidth = DEFAULT_MAT_WIDTH; + + m_ColorThreshold = DEFAULT_COLORTHRESHOLD; + m_BluePercent = DEFAULT_BLUEPERCEMT; + m_WhitePercent = DEFAULT_WHITEPERCEMT; + + m_debug = DEFAULT_DEBUG; +} + + +bool CCharsSegment::verifyCharSizes(Mat r) { + // Char sizes 45x90 + float aspect = 45.0f / 90.0f; + float charAspect = (float)r.cols / (float)r.rows; + float error = 0.7f; + float minHeight = 10.f; + float maxHeight = 35.f; + // We have a different aspect ratio for number 1, and it can be ~0.2 + float minAspect = 0.05f; + float maxAspect = aspect + aspect * error; + // area of pixels + int area = cv::countNonZero(r); + // bb area + int bbArea = r.cols * r.rows; + //% of pixel in area + int percPixels = area / bbArea; + + if (percPixels <= 1 && charAspect > minAspect && charAspect < maxAspect && + r.rows >= minHeight && r.rows < maxHeight) + return true; + else + return false; +} + + +Mat CCharsSegment::preprocessChar(Mat in) { + // Remap image + int h = in.rows; + int w = in.cols; + + int charSize = CHAR_SIZE; + + Mat transformMat = Mat::eye(2, 3, CV_32F); + int m = max(w, h); + transformMat.at(0, 2) = float(m / 2 - w / 2); + transformMat.at(1, 2) = float(m / 2 - h / 2); + + Mat warpImage(m, m, in.type()); + warpAffine(in, warpImage, transformMat, warpImage.size(), INTER_LINEAR, + BORDER_CONSTANT, Scalar(0)); + + Mat out; + resize(warpImage, out, Size(charSize, charSize)); + + return out; +} + + +//! choose the bese threshold method for chinese +void CCharsSegment::judgeChinese(Mat in, Mat& out, Color plateType) { + Mat auxRoi = in; + float valOstu = -1.f, valAdap = -1.f; + Mat roiOstu, roiAdap; + bool isChinese = true; + if (1) { + if (BLUE == plateType) { + threshold(auxRoi, roiOstu, 0, 255, CV_THRESH_BINARY + CV_THRESH_OTSU); + } + else if (YELLOW == plateType) { + threshold(auxRoi, roiOstu, 0, 255, CV_THRESH_BINARY_INV + CV_THRESH_OTSU); + } + else if (WHITE == plateType) { + threshold(auxRoi, roiOstu, 0, 255, CV_THRESH_BINARY_INV + CV_THRESH_OTSU); + } + else { + threshold(auxRoi, roiOstu, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY); + } + roiOstu = preprocessChar(roiOstu); + if (0) { + imshow("roiOstu", roiOstu); + waitKey(0); + destroyWindow("roiOstu"); + } + auto character = CharsIdentify::instance()->identifyChinese(roiOstu, valOstu, isChinese); + } + if (1) { + if (BLUE == plateType) { + adaptiveThreshold(auxRoi, roiAdap, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 3, 0); + } + else if (YELLOW == plateType) { + adaptiveThreshold(auxRoi, roiAdap, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY_INV, 3, 0); + } + else if (WHITE == plateType) { + adaptiveThreshold(auxRoi, roiAdap, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY_INV, 3, 0); + } + else { + adaptiveThreshold(auxRoi, roiAdap, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 3, 0); + } + roiAdap = preprocessChar(roiAdap); + auto character = CharsIdentify::instance()->identifyChinese(roiAdap, valAdap, isChinese); + } + + //std::cout << "valOstu: " << valOstu << std::endl; + //std::cout << "valAdap: " << valAdap << std::endl; + + if (valOstu >= valAdap) { + out = roiOstu; + } + else { + out = roiAdap; + } +} + +void CCharsSegment::judgeChineseGray(Mat in, Mat& out, Color plateType) { + out = in; +} + +bool slideChineseWindow(Mat& image, Rect mr, Mat& newRoi, Color plateType, float slideLengthRatio, bool useAdapThreshold) { + std::vector charCandidateVec; + + Rect maxrect = mr; + Point tlPoint = mr.tl(); + + bool isChinese = true; + int slideLength = int(slideLengthRatio * maxrect.width); + int slideStep = 1; + int fromX = 0; + fromX = tlPoint.x; + + for (int slideX = -slideLength; slideX < slideLength; slideX += slideStep) { + float x_slide = 0; + + x_slide = float(fromX + slideX); + + float y_slide = (float)tlPoint.y; + Point2f p_slide(x_slide, y_slide); + + //cv::circle(image, p_slide, 2, Scalar(255), 1); + + int chineseWidth = int(maxrect.width); + int chineseHeight = int(maxrect.height); + + Rect rect(Point2f(x_slide, y_slide), Size(chineseWidth, chineseHeight)); + + if (rect.tl().x < 0 || rect.tl().y < 0 || rect.br().x >= image.cols || rect.br().y >= image.rows) + continue; + + Mat auxRoi = image(rect); + + Mat roiOstu, roiAdap; + if (1) { + if (BLUE == plateType) { + threshold(auxRoi, roiOstu, 0, 255, CV_THRESH_BINARY + CV_THRESH_OTSU); + } + else if (YELLOW == plateType) { + threshold(auxRoi, roiOstu, 0, 255, CV_THRESH_BINARY_INV + CV_THRESH_OTSU); + } + else if (WHITE == plateType) { + threshold(auxRoi, roiOstu, 0, 255, CV_THRESH_BINARY_INV + CV_THRESH_OTSU); + } + else { + threshold(auxRoi, roiOstu, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY); + } + roiOstu = preprocessChar(roiOstu, kChineseSize); + + CCharacter charCandidateOstu; + charCandidateOstu.setCharacterPos(rect); + charCandidateOstu.setCharacterMat(roiOstu); + charCandidateOstu.setIsChinese(isChinese); + charCandidateVec.push_back(charCandidateOstu); + } + if (useAdapThreshold) { + if (BLUE == plateType) { + adaptiveThreshold(auxRoi, roiAdap, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 3, 0); + } + else if (YELLOW == plateType) { + adaptiveThreshold(auxRoi, roiAdap, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY_INV, 3, 0); + } + else if (WHITE == plateType) { + adaptiveThreshold(auxRoi, roiAdap, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY_INV, 3, 0); + } + else { + adaptiveThreshold(auxRoi, roiAdap, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 3, 0); + } + roiAdap = preprocessChar(roiAdap, kChineseSize); + + CCharacter charCandidateAdap; + charCandidateAdap.setCharacterPos(rect); + charCandidateAdap.setCharacterMat(roiAdap); + charCandidateAdap.setIsChinese(isChinese); + charCandidateVec.push_back(charCandidateAdap); + } + + } + + CharsIdentify::instance()->classifyChinese(charCandidateVec); + + double overlapThresh = 0.1; + NMStoCharacter(charCandidateVec, overlapThresh); + + if (charCandidateVec.size() >= 1) { + std::sort(charCandidateVec.begin(), charCandidateVec.end(), + [](const CCharacter& r1, const CCharacter& r2) { + return r1.getCharacterScore() > r2.getCharacterScore(); + }); + + newRoi = charCandidateVec.at(0).getCharacterMat(); + return true; + } + + return false; +} + +bool slideChineseGrayWindow(const Mat& image, Rect& mr, Mat& newRoi, Color plateType, float slideLengthRatio) { + std::vector charCandidateVec; + + Rect maxrect = mr; + Point tlPoint = mr.tl(); + + bool isChinese = true; + int slideLength = int(slideLengthRatio * maxrect.width); + int slideStep = 1; + int fromX = 0; + fromX = tlPoint.x; + + for (int slideX = -slideLength; slideX < slideLength; slideX += slideStep) { + float x_slide = 0; + x_slide = float(fromX + slideX); + + float y_slide = (float)tlPoint.y; + + int chineseWidth = int(maxrect.width); + int chineseHeight = int(maxrect.height); + + Rect rect(Point2f(x_slide, y_slide), Size(chineseWidth, chineseHeight)); + + if (rect.tl().x < 0 || rect.tl().y < 0 || rect.br().x >= image.cols || rect.br().y >= image.rows) + continue; + + Mat auxRoi = image(rect); + Mat grayChinese; + grayChinese.create(kGrayCharHeight, kGrayCharWidth, CV_8UC1); + resize(auxRoi, grayChinese, grayChinese.size(), 0, 0, INTER_LINEAR); + + CCharacter charCandidateOstu; + charCandidateOstu.setCharacterPos(rect); + charCandidateOstu.setCharacterMat(grayChinese); + charCandidateOstu.setIsChinese(isChinese); + charCandidateVec.push_back(charCandidateOstu); + } + + CharsIdentify::instance()->classifyChineseGray(charCandidateVec); + + double overlapThresh = 0.1; + NMStoCharacter(charCandidateVec, overlapThresh); + + if (charCandidateVec.size() >= 1) { + std::sort(charCandidateVec.begin(), charCandidateVec.end(), + [](const CCharacter& r1, const CCharacter& r2) { + return r1.getCharacterScore() > r2.getCharacterScore(); + }); + + newRoi = charCandidateVec.at(0).getCharacterMat(); + mr = charCandidateVec.at(0).getCharacterPos(); + return true; + } + return false; +} + + +int CCharsSegment::charsSegment(Mat input, vector& resultVec, Color color) { + if (!input.data) return 0x01; + + Color plateType = color; + + Mat input_grey; + cvtColor(input, input_grey, CV_BGR2GRAY); + + if (0) { + imshow("plate", input_grey); + waitKey(0); + destroyWindow("plate"); + } + + Mat img_threshold; + + img_threshold = input_grey.clone(); + spatial_ostu(img_threshold, 8, 2, plateType); + + if (0) { + imshow("plate", img_threshold); + waitKey(0); + destroyWindow("plate"); + } + + // remove liuding and hor lines + // also judge weather is plate use jump count + if (!clearLiuDing(img_threshold)) return 0x02; + + Mat img_contours; + img_threshold.copyTo(img_contours); + + vector > contours; + findContours(img_contours, + contours, // a vector of contours + CV_RETR_EXTERNAL, // retrieve the external contours + CV_CHAIN_APPROX_NONE); // all pixels of each contours + + vector >::iterator itc = contours.begin(); + vector vecRect; + + while (itc != contours.end()) { + Rect mr = boundingRect(Mat(*itc)); + Mat auxRoi(img_threshold, mr); + + if (verifyCharSizes(auxRoi)) vecRect.push_back(mr); + ++itc; + } + + + if (vecRect.size() == 0) return 0x03; + + vector sortedRect(vecRect); + std::sort(sortedRect.begin(), sortedRect.end(), + [](const Rect& r1, const Rect& r2) { return r1.x < r2.x; }); + + size_t specIndex = 0; + + specIndex = GetSpecificRect(sortedRect); + + Rect chineseRect; + if (specIndex < sortedRect.size()) + chineseRect = GetChineseRect(sortedRect[specIndex]); + else + return 0x04; + + if (0) { + rectangle(img_threshold, chineseRect, Scalar(255)); + imshow("plate", img_threshold); + waitKey(0); + destroyWindow("plate"); + } + + vector newSortedRect; + newSortedRect.push_back(chineseRect); + RebuildRect(sortedRect, newSortedRect, specIndex); + + if (newSortedRect.size() == 0) return 0x05; + + bool useSlideWindow = true; + bool useAdapThreshold = true; + //bool useAdapThreshold = CParams::instance()->getParam1b(); + + for (size_t i = 0; i < newSortedRect.size(); i++) { + Rect mr = newSortedRect[i]; + + // Mat auxRoi(img_threshold, mr); + Mat auxRoi(input_grey, mr); + Mat newRoi; + + if (i == 0) { + if (useSlideWindow) { + float slideLengthRatio = 0.1f; + //float slideLengthRatio = CParams::instance()->getParam1f(); + if (!slideChineseWindow(input_grey, mr, newRoi, plateType, slideLengthRatio, useAdapThreshold)) + judgeChinese(auxRoi, newRoi, plateType); + } + else + judgeChinese(auxRoi, newRoi, plateType); + } + else { + if (BLUE == plateType) { + threshold(auxRoi, newRoi, 0, 255, CV_THRESH_BINARY + CV_THRESH_OTSU); + } + else if (YELLOW == plateType) { + threshold(auxRoi, newRoi, 0, 255, CV_THRESH_BINARY_INV + CV_THRESH_OTSU); + } + else if (WHITE == plateType) { + threshold(auxRoi, newRoi, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY_INV); + } + else { + threshold(auxRoi, newRoi, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY); + } + + newRoi = preprocessChar(newRoi); + } + + if (0) { + if (i == 0) { + imshow("input_grey", input_grey); + waitKey(0); + destroyWindow("input_grey"); + } + if (i == 0) { + imshow("newRoi", newRoi); + waitKey(0); + destroyWindow("newRoi"); + } + } + + resultVec.push_back(newRoi); + } + + return 0; +} + +int CCharsSegment::projectSegment(const Mat& input, Color color, vector& out_indexs) { + if (!input.data) return 0x01; + + Color plateType = color; + Mat input_grey; + cvtColor(input, input_grey, CV_BGR2GRAY); + SHOW_IMAGE(input_grey, 0); + + Mat img_threshold; + img_threshold = input_grey.clone(); + spatial_ostu(img_threshold, 8, 2, plateType); + SHOW_IMAGE(img_threshold, 0); + + // remove liuding and hor lines + // also judge weather is plate use jump count + if (!clearLiuDing(img_threshold)) return 0x02; + SHOW_IMAGE(img_threshold, 0); + + Mat vhist = ProjectedHistogram(img_threshold, VERTICAL, 0); + Mat showHist = showHistogram(vhist); + SHOW_IMAGE(showHist, 1); + + vector values; + vector indexs; + int size = vhist.cols; + for (int i = 0; i < size; i++) { + float val = vhist.at(i); + values.push_back(1.f - val); + } + Mat img_test = img_threshold.clone(); + NMSfor1D(values, indexs); + + out_indexs.resize(size); + for (int j = 0; j < size; j++) + out_indexs.at(j) = 0; + for (int i = 0; i < size; i++) { + float val = vhist.at(i); + if (indexs.at(i) && val < 0.1f) { + out_indexs.at(i) = 1; + for (int j = 0; j < img_test.rows; j++) { + img_test.at(j, i) = (char)255; + } + } + } + SHOW_IMAGE(img_test, 1); + + return 0; +} + +bool verifyCharRectSizes(Rect r) { + // Char sizes 45x90 + float aspect = 45.0f / 90.0f; + float charAspect = (float)r.width / (float)r.height; + float error = 0.5f; + float minHeight = kPlateResizeHeight * 0.5f; + float maxHeight = kPlateResizeHeight * 1.f; + // We have a different aspect ratio for number 1, and it can be ~0.2 + float minAspect = 0.10f; //0.2f; + + float maxAspect = 0.85f; // aspect + aspect * error; //0.8f; + + int ch = r.tl().y + r.height / 2; + int min_ch = int(kPlateResizeHeight * 0.3f); + int max_ch = int(kPlateResizeHeight * 0.7f); + if (ch > max_ch || ch < min_ch) + return false; + + float h = (float)r.height; + if (h > maxHeight || h < minHeight) + return false; + if (charAspect < minAspect || charAspect > maxAspect) + return false; + + return true; +} + +Mat preprocessCharMat(Mat in, int char_size) { + // Remap image + int h = in.rows; + int w = in.cols; + + int charSize = char_size; + + Mat transformMat = Mat::eye(2, 3, CV_32F); + int m = max(w, h); + transformMat.at(0, 2) = float(m / 2 - w / 2); + transformMat.at(1, 2) = float(m / 2 - h / 2); + + Mat warpImage(m, m, in.type()); + warpAffine(in, warpImage, transformMat, warpImage.size(), INTER_LINEAR, + BORDER_CONSTANT, Scalar(0)); + + Mat out; + cv::resize(warpImage, out, Size(charSize, charSize)); + + return out; +} + +Mat clearLiuDingAndBorder(const Mat& grayImage, Color color) { + SHOW_IMAGE(grayImage, 0); + Mat img_threshold; + img_threshold = grayImage.clone(); + spatial_ostu(img_threshold, 1, 1, color); + clearLiuDing(img_threshold); + Rect cropRect; + clearBorder(img_threshold, cropRect); + Mat cropedGrayImage; + resize(grayImage(cropRect), cropedGrayImage, Size(kPlateResizeWidth, kPlateResizeHeight)); + SHOW_IMAGE(cropedGrayImage, 0); + return cropedGrayImage; +} + +void NMStoCharacterByRatio(std::vector &inVec, double overlap, const Rect groundRect) { + // rechange the score + for (auto& character : inVec) { + double score = character.getCharacterScore(); + //cout << "score:" << score << endl; + Rect rect = character.getCharacterPos(); + int w = rect.width; + int h = rect.height; + int gw = groundRect.width; + int gh = groundRect.height; + + float iou = computeIOU(rect, groundRect); + + int w_diff = abs(w - gw); + int h_diff = abs(h - gh); + + //float w_ratio = (float)w / (float)gw; + //float h_ratio = (float)h / (float)gh; + + float w_ratio = 1 - (float)w_diff / (float)gw; + float h_ratio = 1 - (float)h_diff / (float)gh; + + float a = 0.5f; + float b = 0.5f; + //cout << "str:" << character.getCharacterStr() << endl; + // if the charater is '1', its probalilty is redcued by its iou + if ("1" == character.getCharacterStr()) { + a = 0.3f; //0.2f; + b = 0.7f; //0.8f; + } + float c = 0.1f; + //float weighted_score = a * (float)score + b * w_ratio + c * h_ratio; + float weighted_score = a * (float)score + b * w_ratio + c * h_ratio; + SHOW_IMAGE(character.getCharacterMat(), 0); + character.setCharacterScore((double)weighted_score); + //cout << "weighted_score:" << character.getCharacterScore() << endl; + } + + std::sort(inVec.begin(), inVec.end()); + + std::vector::iterator it = inVec.begin(); + for (; it != inVec.end(); ++it) { + CCharacter charSrc = *it; + // cout << "charScore:" << charSrc.getCharacterScore() << endl; + Rect rectSrc = charSrc.getCharacterPos(); + std::vector::iterator itc = it + 1; + + for (; itc != inVec.end();) { + CCharacter charComp = *itc; + Rect rectComp = charComp.getCharacterPos(); + float iou = computeIOU(rectSrc, rectComp); + + if (iou > overlap) { + itc = inVec.erase(itc); + } + else { + ++itc; + } + } + } +} + +int getNearestIndex(Point center, const vector& groundCenters) { + int gc_size = int(groundCenters.size()); + int index = 0; + int min_length = INT_MAX; + for (int p = 0; p < gc_size; p++) { + Point gc_point = groundCenters.at(p); + int length_square = (gc_point.x - center.x) * (gc_point.x - center.x) + + (gc_point.y - center.y) * (gc_point.y - center.y); + //int length_square = abs(gc_point.x - center.x); + if (length_square < min_length) { + min_length = length_square; + index = p; + } + } + return index; +} + +int CCharsSegment::charsSegmentUsingMSER(Mat input, vector& resultVec, vector& grayChars, Color color) { + Mat grayImage; + cvtColor(input, grayImage, CV_BGR2GRAY); + std::vector bgrSplit; + split(input, bgrSplit); + + //Mat grayChannel = clearLiuDingAndBorder(grayImage, color); //clearLiuDingAndBorder(grayImage, color); + Mat grayChannel = grayImage; + + // Mat cropedGrayImage = grayImage; + // generate all channgel images; + vector channelImages; + bool useThreeChannel = false; + channelImages.push_back(grayChannel); + if (useThreeChannel) { + for (int i = 0; i < 3; i++) + channelImages.push_back(bgrSplit.at(i)); + } + int csize = channelImages.size(); + + //TODO three channels + std::vector> all_contours; + std::vector all_boxes; + all_contours.reserve(32); + all_boxes.reserve(32); + + const int imageArea = input.rows * input.cols; + const int delta = 1; + const int minArea = 30; + const double maxAreaRatio = 0.2; + + int type = -1; + if (Color::BLUE == color) type = 0; + if (Color::YELLOW == color) type = 1; + if (Color::WHITE == color) type = 1; + if (Color::UNKNOWN == color) type = 0; + + for (int c_index = 0; c_index < csize; c_index++) { + Mat cimage = channelImages.at(c_index); + Mat testImage = cimage.clone(); + cvtColor(testImage, testImage, CV_GRAY2BGR); + + const float plateMaxSymbolCount = kPlateMaxSymbolCount; + const int symbolIndex = kSymbolIndex; + float segmentRatio = plateMaxSymbolCount - int(plateMaxSymbolCount); + const int plateMaxCharCount = int(plateMaxSymbolCount); + + vector> charsVecVec; + charsVecVec.resize(plateMaxCharCount); + + vector groundCenters; + groundCenters.reserve(plateMaxCharCount); + vector groundRects; + groundRects.reserve(plateMaxCharCount); + + // compute the ground char rect + int avg_char_width = int(kPlateResizeWidth * (1.f / plateMaxSymbolCount)); + int avg_char_height = int(kPlateResizeHeight * 0.85f); + + int x_axis = 0; + int y_axis = int((kPlateResizeHeight - avg_char_height) * 0.5f); + for (int j = 0; j < plateMaxSymbolCount; j++) { + int char_width = avg_char_width; + if (j != symbolIndex) char_width = avg_char_width; + else char_width = int(segmentRatio * avg_char_width); + + Rect avg_char_rect = Rect(x_axis, y_axis, char_width, avg_char_height); + rectangle(testImage, avg_char_rect, Scalar(0, 0, 255)); + + Point center = Point(x_axis + int(char_width * 0.5f), y_axis + int(avg_char_height * 0.5f)); + circle(testImage, center, 3, Scalar(0, 255, 0)); + x_axis += char_width; + + if (j != symbolIndex) { + groundCenters.push_back(center); + groundRects.push_back(avg_char_rect); + } + } + SHOW_IMAGE(testImage, 0); + + Mat showImage = cimage.clone(); + cvtColor(showImage, showImage, CV_GRAY2BGR); + Mat mdoImage = cimage.clone(); + string candidateLicense; + + Ptr mser; + // use origin mser to detect as many as possible characters + mser = MSER2::create(delta, minArea, int(maxAreaRatio * imageArea), false); + mser->detectRegions(cimage, all_contours, all_boxes, type); + + std::vector charVec; + charVec.reserve(16); + size_t size = all_contours.size(); + + int char_index = 0; + int char_size = 20; + + Mat showMSERImage = cimage.clone(); + cvtColor(showMSERImage, showMSERImage, CV_GRAY2BGR); + // verify char size and output to rects; + for (size_t index = 0; index < size; index++) { + Rect rect = all_boxes[index]; + vector &contour = all_contours[index]; + rectangle(showMSERImage, rect, Scalar(0,0,255)); + + // find character + if (verifyCharRectSizes(rect)) { + Mat mserMat = adaptive_image_from_points(contour, rect, Size(char_size, char_size)); + Mat mserInput = preprocessCharMat(mserMat, char_size); + + Rect charRect = rect; + Point center(charRect.tl().x + charRect.width / 2, charRect.tl().y + charRect.height / 2); + Mat tmpMat; + double ostu_level = cv::threshold(cimage(charRect), tmpMat, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU); + Mat grayCharMat = cimage(charRect); + Mat ostuMat; + switch (color) { + case BLUE: threshold(grayCharMat, ostuMat, 0, 255, CV_THRESH_BINARY + CV_THRESH_OTSU); break; + case YELLOW: threshold(grayCharMat, ostuMat, 0, 255, CV_THRESH_BINARY_INV + CV_THRESH_OTSU); break; + case WHITE: threshold(grayCharMat, ostuMat, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY_INV); break; + default: threshold(grayCharMat, ostuMat, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY); break; + } + Mat ostuInput = preprocessChar(ostuMat); + // use judegMDOratio2 function to + // remove the small lines in character like "zh-cuan" + if (judegMDOratio2(cimage, rect, contour, mdoImage, 1.2f, true)) { + CCharacter charCandidate; + //cout << contour.size() << endl; + charCandidate.setCharacterPos(charRect); + charCandidate.setCharacterMat(ostuInput); //charInput or ostuInput + charCandidate.setOstuLevel(ostu_level); + charCandidate.setCenterPoint(center); + int pos = getNearestIndex(center, groundCenters); + charsVecVec.at(pos).push_back(charCandidate); + charCandidate.setIndex(pos); + charCandidate.setIsChinese(false); + charVec.push_back(charCandidate); + } + } + else { + SHOW_IMAGE(showMSERImage(rect), 0); + } + } + SHOW_IMAGE(showMSERImage, 0); + SHOW_IMAGE(mdoImage, 0); + + // classify all the images; + CharsIdentify::instance()->classify(charVec); + Rect maxrect = groundRects.at(0); + + // NMS to the seven groud truth rect + bool useGround = true; + if (useGround) { + for (auto charCandidate : charVec) { + int pos = charCandidate.getIndex(); + charsVecVec.at(pos).push_back(charCandidate); + } + charVec.clear(); + for (size_t c = 0; c < charsVecVec.size(); c++) { + Mat testImage_2 = cimage.clone(); + cvtColor(testImage_2, testImage_2, CV_GRAY2BGR); + vector& charPosVec = charsVecVec.at(c); + for (auto character : charPosVec) { + rectangle(testImage_2, character.getCharacterPos(), Scalar(0, 255, 0)); + } + SHOW_IMAGE(testImage_2, 0); + + double overlapThresh = 0.; + NMStoCharacterByRatio(charPosVec, overlapThresh, groundRects.at(c)); + charPosVec.shrink_to_fit(); + + Mat testImage_3 = cimage.clone(); + cvtColor(testImage_3, testImage_3, CV_GRAY2BGR); + for (auto character : charPosVec) { + rectangle(testImage_3, character.getCharacterPos(), Scalar(0, 255, 0)); + } + + // only the last group will contain more than one candidate character + if (charsVecVec.size() - 1 == c) { + for (auto charPos : charPosVec) + charVec.push_back(charPos); + } + else { + if (charPosVec.size() != 0) { + CCharacter& inputChar = charPosVec.at(0); + charVec.push_back(inputChar); + Mat charMat = inputChar.getCharacterMat(); + SHOW_IMAGE(charMat, 0); + } + } + for (auto charPos : charPosVec) { + Rect r = charPos.getCharacterPos(); + if (r.area() > maxrect.area()) + maxrect = r; + } + SHOW_IMAGE(testImage_3, 0); + } + } + else { + NMStoCharacterByRatio(charVec, 0.2f, maxrect); + } + + if (charVec.size() < kCharsCountInOnePlate) return 0x03; + std::sort(charVec.begin(), charVec.end(),[](const CCharacter& r1, const CCharacter& r2) { return r1.getCharacterPos().x < r2.getCharacterPos().x; }); + + string predictLicense = ""; + vector sortedRect; + for (auto charCandidate : charVec) { + sortedRect.push_back(charCandidate.getCharacterPos()); + predictLicense.append(charCandidate.getCharacterStr()); + } + std::sort(sortedRect.begin(), sortedRect.end(), + [](const Rect& r1, const Rect& r2) { return r1.x < r2.x; }); + cout << "predictLicense: " << predictLicense << endl; + + // find chinese rect + size_t specIndex = 0; + specIndex = GetSpecificRect(sortedRect); + SHOW_IMAGE(showImage(sortedRect[specIndex]), 0); + + Rect chineseRect; + if (specIndex < sortedRect.size()) + chineseRect = GetChineseRect(sortedRect[specIndex]); + else + return 0x04; + + vector newSortedRect; + newSortedRect.push_back(chineseRect); + if (newSortedRect.size() == 0) return 0x05; + + SHOW_IMAGE(showImage(chineseRect), 0); + RebuildRect(sortedRect, newSortedRect, specIndex); + + Mat theImage = channelImages.at(c_index); + for (size_t i = 0; i < newSortedRect.size(); i++) { + Rect mr = newSortedRect[i]; + //mr = rectEnlarge(newSortedRect[i], cimage.cols, cimage.rows); + Mat auxRoi(theImage, mr); + Mat newRoi; + if (i == 0) { + //Rect large_mr = rectEnlarge(mr, theImage.cols, theImage.rows); + Rect large_mr = mr; + Mat grayChar(theImage, large_mr); + Mat grayChinese; + grayChinese.create(kGrayCharHeight, kGrayCharWidth, CV_8UC1); + resize(grayChar, grayChinese, grayChinese.size(), 0, 0, INTER_LINEAR); + + Mat newChineseRoi; + if (1) { + float slideLengthRatio = 0.1f; + if (!slideChineseGrayWindow(theImage, large_mr, newChineseRoi, color, slideLengthRatio)) + judgeChineseGray(grayChinese, newChineseRoi, color); + } + grayChars.push_back(newChineseRoi); + } + else { + switch (color) { + case BLUE: threshold(auxRoi, newRoi, 0, 255, CV_THRESH_BINARY + CV_THRESH_OTSU); break; + case YELLOW: threshold(auxRoi, newRoi, 0, 255, CV_THRESH_BINARY_INV + CV_THRESH_OTSU); break; + case WHITE: threshold(auxRoi, newRoi, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY_INV); break; + default: threshold(auxRoi, newRoi, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY); break; + } + newRoi = preprocessChar(newRoi); + Rect fit_mr = rectFit(mr, cimage.cols, cimage.rows); + Mat grayChar(cimage, fit_mr); + grayChars.push_back(grayChar); + } + + rectangle(showImage, mr, Scalar(0, 0, 255), 1); + resultVec.push_back(newRoi); + } + SHOW_IMAGE(showImage, 0); + } + + return 0; +} + + +int CCharsSegment::charsSegmentUsingOSTU(Mat input, vector& resultVec, vector& grayChars, Color color) { + if (!input.data) return 0x01; + + Color plateType = color; + Mat input_grey; + cvtColor(input, input_grey, CV_BGR2GRAY); + + Mat img_threshold; + img_threshold = input_grey.clone(); + spatial_ostu(img_threshold, 8, 2, plateType); + + // remove liuding and hor lines, also judge weather is plate use jump count + if (!clearLiuDing(img_threshold)) return 0x02; + + Mat img_contours; + img_threshold.copyTo(img_contours); + + vector > contours; + findContours(img_contours, + contours, // a vector of contours + CV_RETR_EXTERNAL, // retrieve the external contours + CV_CHAIN_APPROX_NONE); // all pixels of each contours + + vector >::iterator itc = contours.begin(); + vector vecRect; + while (itc != contours.end()) { + Rect mr = boundingRect(Mat(*itc)); + Mat auxRoi(img_threshold, mr); + if (verifyCharSizes(auxRoi)) + vecRect.push_back(mr); + ++itc; + } + + if (vecRect.size() == 0) return 0x03; + + vector sortedRect(vecRect); + std::sort(sortedRect.begin(), sortedRect.end(), + [](const Rect& r1, const Rect& r2) { return r1.x < r2.x; }); + + size_t specIndex = 0; + specIndex = GetSpecificRect(sortedRect); + + Rect chineseRect; + if (specIndex < sortedRect.size()) + chineseRect = GetChineseRect(sortedRect[specIndex]); + else + return 0x04; + + if (0) { + rectangle(img_threshold, chineseRect, Scalar(255)); + imshow("plate", img_threshold); + waitKey(0); + destroyWindow("plate"); + } + + vector newSortedRect; + newSortedRect.push_back(chineseRect); + RebuildRect(sortedRect, newSortedRect, specIndex); + + if (newSortedRect.size() == 0) return 0x05; + + bool useSlideWindow = true; + bool useAdapThreshold = true; + //bool useAdapThreshold = CParams::instance()->getParam1b(); + + for (size_t i = 0; i < newSortedRect.size(); i++) { + Rect mr = newSortedRect[i]; + Mat auxRoi(input_grey, mr); + Mat newRoi; + + if (i == 0) { + // genenrate gray chinese char + Rect large_mr = rectEnlarge(mr, input_grey.cols, input_grey.rows); + Mat grayChar(input_grey, large_mr); + Mat grayChinese; + grayChinese.create(kGrayCharHeight, kGrayCharWidth, CV_8UC1); + resize(grayChar, grayChinese, grayChinese.size(), 0, 0, INTER_LINEAR); + + Mat newChineseRoi; + if (useSlideWindow) { + float slideLengthRatio = 0.1f; + if (!slideChineseGrayWindow(input_grey, large_mr, newChineseRoi, plateType, slideLengthRatio)) + judgeChineseGray(grayChinese, newChineseRoi, plateType); + } + else { + judgeChinese(auxRoi, newRoi, plateType); + } + grayChars.push_back(newChineseRoi); + } + else { + switch (plateType) { + case BLUE: threshold(auxRoi, newRoi, 0, 255, CV_THRESH_BINARY + CV_THRESH_OTSU); break; + case YELLOW: threshold(auxRoi, newRoi, 0, 255, CV_THRESH_BINARY_INV + CV_THRESH_OTSU); break; + case WHITE: threshold(auxRoi, newRoi, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY_INV); break; + default: threshold(auxRoi, newRoi, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY); break; + } + newRoi = preprocessChar(newRoi); + + // genenrate gray chinese char + Rect fit_mr = rectFit(mr, input_grey.cols, input_grey.rows); + Mat grayChar(input_grey, fit_mr); + grayChars.push_back(grayChar); + } + resultVec.push_back(newRoi); + } + return 0; +} + + +Rect CCharsSegment::GetChineseRect(const Rect rectSpe) { + int height = rectSpe.height; + float newwidth = rectSpe.width * 1.15f; + int x = rectSpe.x; + int y = rectSpe.y; + + int newx = x - int(newwidth * 1.15); + newx = newx > 0 ? newx : 0; + + Rect a(newx, y, int(newwidth), height); + + return a; +} + +int CCharsSegment::GetSpecificRect(const vector& vecRect) { + vector xpositions; + int maxHeight = 0; + int maxWidth = 0; + + for (size_t i = 0; i < vecRect.size(); i++) { + xpositions.push_back(vecRect[i].x); + + if (vecRect[i].height > maxHeight) { + maxHeight = vecRect[i].height; + } + if (vecRect[i].width > maxWidth) { + maxWidth = vecRect[i].width; + } + } + + int specIndex = 0; + for (size_t i = 0; i < vecRect.size(); i++) { + Rect mr = vecRect[i]; + int midx = mr.x + mr.width / 2; + + // use prior knowledage to find the specific character + // position in 1/7 and 2/7 + if ((mr.width > maxWidth * 0.6 || mr.height > maxHeight * 0.6) && + (midx < int(m_theMatWidth / kPlateMaxSymbolCount) * kSymbolIndex && + midx > int(m_theMatWidth / kPlateMaxSymbolCount) * (kSymbolIndex - 1))) { + specIndex = i; + } + } + + return specIndex; +} + +int CCharsSegment::RebuildRect(const vector& vecRect, + vector& outRect, int specIndex) { + int count = 6; + for (size_t i = specIndex; i < vecRect.size() && count; ++i, --count) { + outRect.push_back(vecRect[i]); + } + + return 0; +} + +} diff --git a/src/main/resources/easypr/core/core_func.cpp b/src/main/resources/easypr/core/core_func.cpp new file mode 100644 index 00000000..a4e419e1 --- /dev/null +++ b/src/main/resources/easypr/core/core_func.cpp @@ -0,0 +1,2569 @@ +#include "easypr/core/core_func.h" +#include "easypr/core/plate.hpp" +#include "easypr/core/chars_identify.h" +#include "easypr/config.h" +#include "easypr/core/params.h" +#include "thirdparty/mser/mser2.hpp" +#include + +namespace easypr { + Mat colorMatch(const Mat &src, Mat &match, const Color r, + const bool adaptive_minsv) { + + // if use adaptive_minsv + // min value of s and v is adaptive to h + const float max_sv = 255; + const float minref_sv = 64; + + const float minabs_sv = 95; //95; + + // H range of blue + + const int min_blue = 100; // 100 + const int max_blue = 140; // 140 + + // H range of yellow + + const int min_yellow = 15; // 15 + const int max_yellow = 40; // 40 + + // H range of white + + const int min_white = 0; // 15 + const int max_white = 30; // 40 + + Mat src_hsv; + + // convert to HSV space + cvtColor(src, src_hsv, CV_BGR2HSV); + + std::vector hsvSplit; + split(src_hsv, hsvSplit); + equalizeHist(hsvSplit[2], hsvSplit[2]); + merge(hsvSplit, src_hsv); + + // match to find the color + + int min_h = 0; + int max_h = 0; + switch (r) { + case BLUE: + min_h = min_blue; + max_h = max_blue; + break; + case YELLOW: + min_h = min_yellow; + max_h = max_yellow; + break; + case WHITE: + min_h = min_white; + max_h = max_white; + break; + default: + // Color::UNKNOWN + break; + } + + float diff_h = float((max_h - min_h) / 2); + float avg_h = min_h + diff_h; + + int channels = src_hsv.channels(); + int nRows = src_hsv.rows; + + // consider multi channel image + int nCols = src_hsv.cols * channels; + if (src_hsv.isContinuous()) { + nCols *= nRows; + nRows = 1; + } + + int i, j; + uchar* p; + float s_all = 0; + float v_all = 0; + float count = 0; + for (i = 0; i < nRows; ++i) { + p = src_hsv.ptr(i); + for (j = 0; j < nCols; j += 3) { + int H = int(p[j]); // 0-180 + int S = int(p[j + 1]); // 0-255 + int V = int(p[j + 2]); // 0-255 + + s_all += S; + v_all += V; + count++; + + bool colorMatched = false; + + if (H > min_h && H < max_h) { + float Hdiff = 0; + if (H > avg_h) + Hdiff = H - avg_h; + else + Hdiff = avg_h - H; + + float Hdiff_p = float(Hdiff) / diff_h; + + float min_sv = 0; + if (true == adaptive_minsv) + min_sv = + minref_sv - + minref_sv / 2 * + (1 + - Hdiff_p); // inref_sv - minref_sv / 2 * (1 - Hdiff_p) + else + min_sv = minabs_sv; // add + + if ((S > min_sv && S < max_sv) && (V > min_sv && V < max_sv)) + colorMatched = true; + } + + if (colorMatched == true) { + p[j] = 0; + p[j + 1] = 0; + p[j + 2] = 255; + } + else { + p[j] = 0; + p[j + 1] = 0; + p[j + 2] = 0; + } + } + } + + // cout << "avg_s:" << s_all / count << endl; + // cout << "avg_v:" << v_all / count << endl; + + // get the final binary + + Mat src_grey; + std::vector hsvSplit_done; + split(src_hsv, hsvSplit_done); + src_grey = hsvSplit_done[2]; + + match = src_grey; + + return src_grey; + } + + bool bFindLeftRightBound1(Mat &bound_threshold, int &posLeft, int &posRight) { + + float span = bound_threshold.rows * 0.2f; + + for (int i = 0; i < bound_threshold.cols - span - 1; i += 3) { + int whiteCount = 0; + for (int k = 0; k < bound_threshold.rows; k++) { + for (int l = i; l < i + span; l++) { + if (bound_threshold.data[k * bound_threshold.step[0] + l] == 255) { + whiteCount++; + } + } + } + if (whiteCount * 1.0 / (span * bound_threshold.rows) > 0.15) { + posLeft = i; + break; + } + } + span = bound_threshold.rows * 0.2f; + + + for (int i = bound_threshold.cols - 1; i > span; i -= 2) { + int whiteCount = 0; + for (int k = 0; k < bound_threshold.rows; k++) { + for (int l = i; l > i - span; l--) { + if (bound_threshold.data[k * bound_threshold.step[0] + l] == 255) { + whiteCount++; + } + } + } + + if (whiteCount * 1.0 / (span * bound_threshold.rows) > 0.06) { + posRight = i; + if (posRight + 5 < bound_threshold.cols) { + posRight = posRight + 5; + } else { + posRight = bound_threshold.cols - 1; + } + + break; + } + } + + if (posLeft < posRight) { + return true; + } + return false; + } + + bool bFindLeftRightBound(Mat &bound_threshold, int &posLeft, int &posRight) { + + + float span = bound_threshold.rows * 0.2f; + + for (int i = 0; i < bound_threshold.cols - span - 1; i += 2) { + int whiteCount = 0; + for (int k = 0; k < bound_threshold.rows; k++) { + for (int l = i; l < i + span; l++) { + if (bound_threshold.data[k * bound_threshold.step[0] + l] == 255) { + whiteCount++; + } + } + } + if (whiteCount * 1.0 / (span * bound_threshold.rows) > 0.36) { + posLeft = i; + break; + } + } + span = bound_threshold.rows * 0.2f; + + + for (int i = bound_threshold.cols - 1; i > span; i -= 2) { + int whiteCount = 0; + for (int k = 0; k < bound_threshold.rows; k++) { + for (int l = i; l > i - span; l--) { + if (bound_threshold.data[k * bound_threshold.step[0] + l] == 255) { + whiteCount++; + } + } + } + + if (whiteCount * 1.0 / (span * bound_threshold.rows) > 0.26) { + posRight = i; + break; + } + } + + if (posLeft < posRight) { + return true; + } + return false; + } + + bool bFindLeftRightBound2(Mat &bound_threshold, int &posLeft, int &posRight) { + + float span = bound_threshold.rows * 0.2f; + + for (int i = 0; i < bound_threshold.cols - span - 1; i += 3) { + int whiteCount = 0; + for (int k = 0; k < bound_threshold.rows; k++) { + for (int l = i; l < i + span; l++) { + if (bound_threshold.data[k * bound_threshold.step[0] + l] == 255) { + whiteCount++; + } + } + } + if (whiteCount * 1.0 / (span * bound_threshold.rows) > 0.32) { + posLeft = i; + break; + } + } + span = bound_threshold.rows * 0.2f; + + + for (int i = bound_threshold.cols - 1; i > span; i -= 3) { + int whiteCount = 0; + for (int k = 0; k < bound_threshold.rows; k++) { + for (int l = i; l > i - span; l--) { + if (bound_threshold.data[k * bound_threshold.step[0] + l] == 255) { + whiteCount++; + } + } + } + + if (whiteCount * 1.0 / (span * bound_threshold.rows) > 0.22) { + posRight = i; + break; + } + } + + if (posLeft < posRight) { + return true; + } + return false; + } + + + bool plateColorJudge(const Mat &src, const Color r, const bool adaptive_minsv, + float &percent) { + + const float thresh = 0.45f; + + Mat src_gray; + colorMatch(src, src_gray, r, adaptive_minsv); + + percent = + float(countNonZero(src_gray)) / float(src_gray.rows * src_gray.cols); + // cout << "percent:" << percent << endl; + + if (percent > thresh) + return true; + else + return false; + } + + Color getPlateType(const Mat &src, const bool adaptive_minsv) { + float max_percent = 0; + Color max_color = UNKNOWN; + + float blue_percent = 0; + float yellow_percent = 0; + float white_percent = 0; + + if (plateColorJudge(src, BLUE, adaptive_minsv, blue_percent) == true) { + // cout << "BLUE" << endl; + return BLUE; + } else if (plateColorJudge(src, YELLOW, adaptive_minsv, yellow_percent) == + true) { + // cout << "YELLOW" << endl; + return YELLOW; + } else if (plateColorJudge(src, WHITE, adaptive_minsv, white_percent) == + true) { + // cout << "WHITE" << endl; + return WHITE; + } else { + //std::cout << "OTHER" << std::endl; + + /*max_percent = blue_percent > yellow_percent ? blue_percent : yellow_percent; + max_color = blue_percent > yellow_percent ? BLUE : YELLOW; + max_color = max_percent > white_percent ? max_color : WHITE;*/ + + // always return blue + return BLUE; + } + } + + void clearLiuDingOnly(Mat &img) { + const int x = 7; + Mat jump = Mat::zeros(1, img.rows, CV_32F); + for (int i = 0; i < img.rows; i++) { + int jumpCount = 0; + int whiteCount = 0; + for (int j = 0; j < img.cols - 1; j++) { + if (img.at(i, j) != img.at(i, j + 1)) jumpCount++; + + if (img.at(i, j) == 255) { + whiteCount++; + } + } + + jump.at(i) = (float) jumpCount; + } + + for (int i = 0; i < img.rows; i++) { + if (jump.at(i) <= x) { + for (int j = 0; j < img.cols; j++) { + img.at(i, j) = 0; + } + } + } + } + + bool clearLiuDing(Mat &img) { + std::vector fJump; + int whiteCount = 0; + const int x = 7; + Mat jump = Mat::zeros(1, img.rows, CV_32F); + for (int i = 0; i < img.rows; i++) { + int jumpCount = 0; + + for (int j = 0; j < img.cols - 1; j++) { + if (img.at(i, j) != img.at(i, j + 1)) jumpCount++; + + if (img.at(i, j) == 255) { + whiteCount++; + } + } + + jump.at(i) = (float) jumpCount; + } + + int iCount = 0; + for (int i = 0; i < img.rows; i++) { + fJump.push_back(jump.at(i)); + if (jump.at(i) >= 16 && jump.at(i) <= 45) { + + // jump condition + iCount++; + } + } + + // if not is not plate + if (iCount * 1.0 / img.rows <= 0.40) { + return false; + } + + if (whiteCount * 1.0 / (img.rows * img.cols) < 0.15 || + whiteCount * 1.0 / (img.rows * img.cols) > 0.50) { + return false; + } + + for (int i = 0; i < img.rows; i++) { + if (jump.at(i) <= x) { + for (int j = 0; j < img.cols; j++) { + img.at(i, j) = 0; + } + } + } + return true; + } + + +void clearBorder(const Mat &img, Rect& cropRect) { + int r = img.rows; + int c = img.cols; + Mat boder = Mat::zeros(1, r, CV_8UC1); + const int noJunpCount_thresh = int(0.15f * c); + + // if nojumpcount > + for (int i = 0; i < r; i++) { + int nojumpCount = 0; + int isBorder = 0; + for (int j = 0; j < c - 1; j++) { + if (img.at(i, j) == img.at(i, j + 1)) + nojumpCount++; + if (nojumpCount > noJunpCount_thresh) { + nojumpCount = 0; + isBorder = 1; + break; + } + } + boder.at(i) = (char) isBorder; + } + + const int mintop = int(0.1f * r); + const int maxtop = int(0.9f * r); + + int minMatTop = 0; + int maxMatTop = r - 1; + + for (int i = 0; i < mintop; i++) { + if (boder.at(i) == 1) { + minMatTop = i; + } + } + + for (int i = r - 1; i > maxtop; i--) { + if (boder.at(i) == 1) { + maxMatTop = i; + } + } + + cropRect = Rect(0, minMatTop, c, maxMatTop - minMatTop + 1); + +} + + void clearLiuDing(Mat mask, int &top, int &bottom) { + const int x = 7; + + for (int i = 0; i < mask.rows / 2; i++) { + int whiteCount = 0; + int jumpCount = 0; + for (int j = 0; j < mask.cols - 1; j++) { + if (mask.at(i, j) != mask.at(i, j + 1)) jumpCount++; + + if ((int) mask.at(i, j) == 255) { + whiteCount++; + } + } + if ((jumpCount < x && whiteCount * 1.0 / mask.cols > 0.15) || + whiteCount < 4) { + top = i; + } + } + top -= 1; + if (top < 0) { + top = 0; + } + + // ok, find top and bottom boudnadry + + for (int i = mask.rows - 1; i >= mask.rows / 2; i--) { + int jumpCount = 0; + int whiteCount = 0; + for (int j = 0; j < mask.cols - 1; j++) { + if (mask.at(i, j) != mask.at(i, j + 1)) jumpCount++; + if (mask.at(i, j) == 255) { + whiteCount++; + } + } + if ((jumpCount < x && whiteCount * 1.0 / mask.cols > 0.15) || + whiteCount < 4) { + bottom = i; + } + } + bottom += 1; + if (bottom >= mask.rows) { + bottom = mask.rows - 1; + } + + if (top >= bottom) { + top = 0; + bottom = mask.rows - 1; + } + } + + int ThresholdOtsu(Mat mat) { + int height = mat.rows; + int width = mat.cols; + + // histogram + float histogram[256] = {0}; + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + unsigned char p = (unsigned char) ((mat.data[i * mat.step[0] + j])); + histogram[p]++; + } + } + // normalize histogram + int size = height * width; + for (int i = 0; i < 256; i++) { + histogram[i] = histogram[i] / size; + } + + // average pixel value + float avgValue = 0; + for (int i = 0; i < 256; i++) { + avgValue += i * histogram[i]; + } + + int thresholdV; + float maxVariance = 0; + float w = 0, u = 0; + for (int i = 0; i < 256; i++) { + w += histogram[i]; + u += i * histogram[i]; + + float t = avgValue * w - u; + float variance = t * t / (w * (1 - w)); + if (variance > maxVariance) { + maxVariance = variance; + thresholdV = i; + } + } + + return thresholdV; + } + + + Mat histeq(Mat in) { + Mat out(in.size(), in.type()); + if (in.channels() == 3) { + Mat hsv; + std::vector hsvSplit; + cvtColor(in, hsv, CV_BGR2HSV); + split(hsv, hsvSplit); + equalizeHist(hsvSplit[2], hsvSplit[2]); + merge(hsvSplit, hsv); + cvtColor(hsv, out, CV_HSV2BGR); + } else if (in.channels() == 1) { + equalizeHist(in, out); + } + return out; + } + +#define HORIZONTAL 1 +#define VERTICAL 0 + + Mat CutTheRect(Mat &in, Rect &rect) { + int size = in.cols; // (rect.width>rect.height)?rect.width:rect.height; + Mat dstMat(size, size, CV_8UC1); + dstMat.setTo(Scalar(0, 0, 0)); + + int x = (int) floor((float) (size - rect.width) / 2.0f); + int y = (int) floor((float) (size - rect.height) / 2.0f); + + for (int i = 0; i < rect.height; ++i) { + + for (int j = 0; j < rect.width; ++j) { + dstMat.data[dstMat.step[0] * (i + y) + j + x] = + in.data[in.step[0] * (i + rect.y) + j + rect.x]; + } + } + + // + return dstMat; + } + + Rect GetCenterRect(Mat &in) { + Rect _rect; + + int top = 0; + int bottom = in.rows - 1; + + // find the center rect + + for (int i = 0; i < in.rows; ++i) { + bool bFind = false; + for (int j = 0; j < in.cols; ++j) { + if (in.data[i * in.step[0] + j] > 20) { + top = i; + bFind = true; + break; + } + } + if (bFind) { + break; + } + + } + for (int i = in.rows - 1; + i >= 0; + --i) { + bool bFind = false; + for (int j = 0; j < in.cols; ++j) { + if (in.data[i * in.step[0] + j] > 20) { + bottom = i; + bFind = true; + break; + } + } + if (bFind) { + break; + } + + } + + + int left = 0; + int right = in.cols - 1; + for (int j = 0; j < in.cols; ++j) { + bool bFind = false; + for (int i = 0; i < in.rows; ++i) { + if (in.data[i * in.step[0] + j] > 20) { + left = j; + bFind = true; + break; + } + } + if (bFind) { + break; + } + + } + for (int j = in.cols - 1; + j >= 0; + --j) { + bool bFind = false; + for (int i = 0; i < in.rows; ++i) { + if (in.data[i * in.step[0] + j] > 20) { + right = j; + bFind = true; + + break; + } + } + if (bFind) { + break; + } + } + + _rect.x = left; + _rect.y = top; + _rect.width = right - left + 1; + _rect.height = bottom - top + 1; + + return _rect; + } + + float countOfBigValue(Mat &mat, int iValue) { + float iCount = 0.0; + if (mat.rows > 1) { + for (int i = 0; i < mat.rows; ++i) { + if (mat.data[i * mat.step[0]] > iValue) { + iCount += 1.0; + } + } + return iCount; + + } else { + for (int i = 0; i < mat.cols; ++i) { + if (mat.data[i] > iValue) { + iCount += 1.0; + } + } + + return iCount; + } + } + + Mat ProjectedHistogram(Mat img, int t, int threshold) { + int sz = (t) ? img.rows : img.cols; + Mat mhist = Mat::zeros(1, sz, CV_32F); + + for (int j = 0; j < sz; j++) { + Mat data = (t) ? img.row(j) : img.col(j); + + mhist.at(j) = countOfBigValue(data, threshold); + } + + // Normalize histogram + double min, max; + minMaxLoc(mhist, &min, &max); + + if (max > 0) + mhist.convertTo(mhist, -1, 1.0f / max, 0); + + return mhist; + } + + Mat showHistogram(const Mat &hist) { + int height = 32; + int width = hist.cols; + Mat show = Mat::zeros(height, width, CV_8UC1); + for (int i = 0; i < width; i++) { + int len = int((float) height * hist.at(i)); + for (int j = height - 1; j >= 0; j--) { + if (height - j <= len) + show.at(j, i) = (char) 255; + } + } + return show; + } + + Mat preprocessChar(Mat in, int char_size) { + // Remap image + int h = in.rows; + int w = in.cols; + + int charSize = char_size; + + Mat transformMat = Mat::eye(2, 3, CV_32F); + int m = max(w, h); + transformMat.at(0, 2) = float(m / 2 - w / 2); + transformMat.at(1, 2) = float(m / 2 - h / 2); + + Mat warpImage(m, m, in.type()); + warpAffine(in, warpImage, transformMat, warpImage.size(), INTER_LINEAR, + BORDER_CONSTANT, Scalar(0)); + + Mat out; + cv::resize(warpImage, out, Size(charSize, charSize)); + + return out; + } + + Rect GetChineseRect(const Rect rectSpe) { + int height = rectSpe.height; + float newwidth = rectSpe.width * 1.10f; + int x = rectSpe.x; + int y = rectSpe.y; + + int newx = x - int(newwidth * 1.10f); + newx = newx > 0 ? newx : 0; + + Rect a(newx, y, int(newwidth), height); + + return a; + } + + bool verifyCharSizes(Rect r) { + // Char sizes 45x90 + float aspect = 45.0f / 90.0f; + float charAspect = (float) r.width / (float) r.height; + float error = 0.35f; + float minHeight = 25.f; + float maxHeight = 50.f; + // We have a different aspect ratio for number 1, and it can be ~0.2 + float minAspect = 0.05f; + float maxAspect = aspect + aspect * error; + + // bb area + int bbArea = r.width * r.height; + + if (charAspect > minAspect && charAspect < maxAspect /*&& + r.rows >= minHeight && r.rows < maxHeight*/) + return true; + else + return false; + } + + + Mat scaleImage(const Mat &image, const Size &maxSize, double &scale_ratio) { + Mat ret; + + if (image.cols > maxSize.width || image.rows > maxSize.height) { + double widthRatio = image.cols / (double) maxSize.width; + double heightRatio = image.rows / (double) maxSize.height; + double m_real_to_scaled_ratio = max(widthRatio, heightRatio); + + int newWidth = int(image.cols / m_real_to_scaled_ratio); + int newHeight = int(image.rows / m_real_to_scaled_ratio); + + cv::resize(image, ret, Size(newWidth, newHeight), 0, 0); + scale_ratio = m_real_to_scaled_ratio; + } else { + ret = image; + scale_ratio = 1.0; + } + + return ret; + } + + +// Scale back RotatedRect + RotatedRect scaleBackRRect(const RotatedRect &rr, const float scale_ratio) { + float width = rr.size.width * scale_ratio; + float height = rr.size.height * scale_ratio; + float x = rr.center.x * scale_ratio; + float y = rr.center.y * scale_ratio; + RotatedRect mserRect(Point2f(x, y), Size2f(width, height), rr.angle); + + return mserRect; + } + + bool verifyPlateSize(Rect mr) { + float error = 0.6f; + // Spain car plate size: 52x11 aspect 4,7272 + // China car plate size: 440mm*140mm,aspect 3.142857 + + // Real car plate size: 136 * 32, aspect 4 + float aspect = 3.75; + + // Set a min and max area. All other patchs are discarded + // int min= 1*aspect*1; // minimum area + // int max= 2000*aspect*2000; // maximum area + int min = 34 * 8 * 1; // minimum area + int max = 34 * 8 * 200; // maximum area + + // Get only patchs that match to a respect ratio. + float rmin = aspect - aspect * error; + float rmax = aspect + aspect * error; + + float area = float(mr.height * mr.width); + float r = (float) mr.width / (float) mr.height; + if (r < 1) r = (float) mr.height / (float) mr.width; + + // cout << "area:" << area << endl; + // cout << "r:" << r << endl; + + if ((area < min || area > max) || (r < rmin || r > rmax)) + return false; + else + return true; + } + + bool verifyRotatedPlateSizes(RotatedRect mr, bool showDebug) { + float error = 0.65f; + // Spain car plate size: 52x11 aspect 4,7272 + // China car plate size: 440mm*140mm,aspect 3.142857 + + // Real car plate size: 136 * 32, aspect 4 + float aspect = 3.75f; + + // Set a min and max area. All other patchs are discarded + // int min= 1*aspect*1; // minimum area + // int max= 2000*aspect*2000; // maximum area + //int min = 34 * 8 * 1; // minimum area + //int max = 34 * 8 * 200; // maximum area + + // Get only patchs that match to a respect ratio. + float aspect_min = aspect - aspect * error; + float aspect_max = aspect + aspect * error; + + float width_max = 600.f; + float width_min = 30.f; + + float min = float(width_min * width_min / aspect_max); // minimum area + float max = float(width_max * width_max / aspect_min); // maximum area + + float width = mr.size.width; + float height = mr.size.height; + float area = width * height; + + float ratio = width / height; + float angle = mr.angle; + if (ratio < 1) { + swap(width, height); + ratio = width / height; + + angle = 90.f + angle; + //std::cout << "angle:" << angle << std::endl; + } + + float angle_min = -60.f; + float angle_max = 60.f; + + //std::cout << "aspect_min:" << aspect_min << std::endl; + //std::cout << "aspect_max:" << aspect_max << std::endl; + + if (area < min || area > max) { + if (0 && showDebug) { + std::cout << "area < min || area > max: " << area << std::endl; + } + + return false; + } else if (ratio < aspect_min || ratio > aspect_max) { + if (0 && showDebug) { + std::cout << "ratio < aspect_min || ratio > aspect_max: " << ratio << std::endl; + } + + return false; + } else if (angle < angle_min || angle > angle_max) { + if (0 && showDebug) { + std::cout << "angle < angle_min || angle > angle_max: " << angle << std::endl; + } + + return false; + } else if (width < width_min || width > width_max) { + if (0 && showDebug) { + std::cout << "width < width_min || width > width_max: " << width << std::endl; + } + + return false; + } else { + return true; + } + + return true; + } + +//! non-maximum suppression + void NMStoCharacter(std::vector &inVec, double overlap) { + + std::sort(inVec.begin(), inVec.end()); + + std::vector::iterator it = inVec.begin(); + for (; it != inVec.end(); ++it) { + CCharacter charSrc = *it; + //std::cout << "plateScore:" << plateSrc.getPlateScore() << std::endl; + Rect rectSrc = charSrc.getCharacterPos(); + + std::vector::iterator itc = it + 1; + + for (; itc != inVec.end();) { + CCharacter charComp = *itc; + Rect rectComp = charComp.getCharacterPos(); + //Rect rectInter = rectSrc & rectComp; + //Rect rectUnion = rectSrc | rectComp; + //double r = double(rectInter.area()) / double(rectUnion.area()); + + float iou = computeIOU(rectSrc, rectComp); + + if (iou > overlap) { + itc = inVec.erase(itc); + } else { + ++itc; + } + } + } + } + +// judge weather two CCharacter are nearly the same; + bool compareCharRect(const CCharacter &character1, const CCharacter &character2) { + Rect rect1 = character1.getCharacterPos(); + Rect rect2 = character2.getCharacterPos(); + + // the character in plate are similar height + float width_1 = float(rect1.width); + float height_1 = float(rect1.height); + + float width_2 = float(rect2.width); + float height_2 = float(rect2.height); + + float height_diff = abs(height_1 - height_2); + double height_diff_ratio = height_diff / min(height_1, height_2); + + if (height_diff_ratio > 0.25) + return false; + + // the character in plate are similar in the y-axis + float y_1 = float(rect1.tl().y); + float y_2 = float(rect2.tl().y); + + float y_diff = abs(y_1 - y_2); + double y_diff_ratio = y_diff / min(height_1, height_2); + + if (y_diff_ratio > 0.5) + return false; + + // the character center in plate are not to near in the x-axis + float x_1 = float(rect1.tl().x + rect1.width / 2); + float x_2 = float(rect2.tl().x + rect2.width / 2); + + float x_diff = abs(x_1 - x_2); + double x_diff_ratio = x_diff / min(height_1, height_2); + + if (x_diff_ratio < 0.25) + return false; + + // the character in plate are near in the x-axis but not very near + float x_margin_left = float(min(rect1.br().x, rect2.br().x)); + float x_margin_right = float(max(rect1.tl().x, rect2.tl().x)); + + float x_margin_diff = abs(x_margin_left - x_margin_right); + double x_margin_diff_ratio = x_margin_diff / min(height_1, height_2); + + if (x_margin_diff_ratio > 1.0) + return false; + + return true; + } + +//! merge chars to group, using the similarity + void mergeCharToGroup(std::vector vecRect, + std::vector> &charGroupVec) { + + std::vector labels; + + int numbers = 0; + if (vecRect.size() > 0) + numbers = partition(vecRect, labels, &compareCharRect); + + for (size_t j = 0; j < size_t(numbers); j++) { + std::vector charGroup; + + for (size_t t = 0; t < vecRect.size(); t++) { + int label = labels[t]; + + if (label == j) + charGroup.push_back(vecRect[t]); + } + + if (charGroup.size() < 2) + continue; + + charGroupVec.push_back(charGroup); + } + } + + + void rotatedRectangle(InputOutputArray image, RotatedRect rrect, const Scalar &color, int thickness, int lineType, + int shift) { + Point2f rect_points[4]; + rrect.points(rect_points); + for (int j = 0; j < 4; j++) { + cv::line(image, rect_points[j], rect_points[(j + 1) % 4], color, thickness, lineType, shift); + } + } + + + void searchWeakSeed(const std::vector &charVec, std::vector &mserCharacter, double thresh1, + double thresh2, + const Vec4f &line, Point &boundaryPoint, const Rect &maxrect, Rect &plateResult, Mat result, + CharSearchDirection searchDirection) { + + float k = line[1] / line[0]; + float x_1 = line[2]; + float y_1 = line[3]; + + std::vector searchWeakSeedVec; + searchWeakSeedVec.reserve(8); + + for (auto weakSeed : charVec) { + Rect weakRect = weakSeed.getCharacterPos(); + + //cv::rectangle(result, weakRect, Scalar(255, 0, 255)); + + Point weakCenter(weakRect.tl().x + weakRect.width / 2, weakRect.tl().y + weakRect.height / 2); + float x_2 = (float) weakCenter.x; + + if (searchDirection == CharSearchDirection::LEFT) { + if (weakCenter.x + weakRect.width / 2 > boundaryPoint.x) { + continue; + } + } else if (searchDirection == CharSearchDirection::RIGHT) { + if (weakCenter.x - weakRect.width / 2 < boundaryPoint.x) { + continue; + } + } + + float y_2l = k * (x_2 - x_1) + y_1; + float y_2 = (float) weakCenter.y; + + float y_diff_ratio = abs(y_2l - y_2) / maxrect.height; + + if (y_diff_ratio < thresh1) { + float width_1 = float(maxrect.width); + float height_1 = float(maxrect.height); + + float width_2 = float(weakRect.width); + float height_2 = float(weakRect.height); + + float height_diff = abs(height_1 - height_2); + double height_diff_ratio = height_diff / min(height_1, height_2); + + float width_diff = abs(width_1 - width_2); + double width_diff_ratio = width_diff / maxrect.width; + + if (height_diff_ratio < thresh1 && width_diff_ratio < 0.5) { + //std::cout << "h" << height_diff_ratio << std::endl; + //std::cout << "w" << width_diff_ratio << std::endl; + searchWeakSeedVec.push_back(weakSeed); + } else { + + } + } + } + + // form right to left to split + if (searchWeakSeedVec.size() != 0) { + if (searchDirection == CharSearchDirection::LEFT) { + std::sort(searchWeakSeedVec.begin(), searchWeakSeedVec.end(), + [](const CCharacter &r1, const CCharacter &r2) { + return r1.getCharacterPos().tl().x > r2.getCharacterPos().tl().x; + }); + } else if (searchDirection == CharSearchDirection::RIGHT) { + std::sort(searchWeakSeedVec.begin(), searchWeakSeedVec.end(), + [](const CCharacter &r1, const CCharacter &r2) { + return r1.getCharacterPos().tl().x < r2.getCharacterPos().tl().x; + }); + } + + CCharacter firstWeakSeed = searchWeakSeedVec.at(0); + Rect firstWeakRect = firstWeakSeed.getCharacterPos(); + Point firstWeakCenter(firstWeakRect.tl().x + firstWeakRect.width / 2, + firstWeakRect.tl().y + firstWeakRect.height / 2); + + float ratio = (float) abs(firstWeakCenter.x - boundaryPoint.x) / (float) maxrect.height; + if (ratio > thresh2) { + if (0) { + std::cout << "search seed ratio:" << ratio << std::endl; + } + return; + } + + mserCharacter.push_back(firstWeakSeed); + plateResult |= firstWeakRect; + boundaryPoint = firstWeakCenter; + + for (size_t weakSeedIndex = 0; weakSeedIndex + 1 < searchWeakSeedVec.size(); weakSeedIndex++) { + CCharacter weakSeed = searchWeakSeedVec[weakSeedIndex]; + CCharacter weakSeedCompare = searchWeakSeedVec[weakSeedIndex + 1]; + + Rect rect1 = weakSeed.getCharacterPos(); + Rect rect2 = weakSeedCompare.getCharacterPos(); + + Rect weakRect = rect2; + Point weakCenter(weakRect.tl().x + weakRect.width / 2, weakRect.tl().y + weakRect.height / 2); + + // the character in plate are similar height + float width_1 = float(rect1.width); + float height_1 = float(rect1.height); + + float width_2 = float(rect2.width); + float height_2 = float(rect2.height); + + // the character in plate are near in the x-axis but not very near + float x_margin_left = float(min(rect1.br().x, rect2.br().x)); + float x_margin_right = float(max(rect1.tl().x, rect2.tl().x)); + + float x_margin_diff = abs(x_margin_left - x_margin_right); + double x_margin_diff_ratio = x_margin_diff / min(height_1, height_2); + + if (x_margin_diff_ratio > thresh2) { + if (0) { + std::cout << "search seed x_margin_diff_ratio:" << x_margin_diff_ratio << std::endl; + } + break; + } else { + //::rectangle(result, weakRect, Scalar(255, 0, 0), 1); + mserCharacter.push_back(weakSeedCompare); + plateResult |= weakRect; + if (searchDirection == CharSearchDirection::LEFT) { + if (weakCenter.x < boundaryPoint.x) { + boundaryPoint = weakCenter; + } + } else if (searchDirection == CharSearchDirection::RIGHT) { + if (weakCenter.x > boundaryPoint.x) { + boundaryPoint = weakCenter; + } + } + } + } + } + } + + void slideWindowSearch(const Mat &image, std::vector &slideCharacter, const Vec4f &line, + Point &fromPoint, const Vec2i &dist, double ostu_level, float ratioWindow, + float threshIsCharacter, const Rect &maxrect, Rect &plateResult, + CharSearchDirection searchDirection, bool isChinese, Mat &result) { + float k = line[1] / line[0]; + float x_1 = line[2]; + float y_1 = line[3]; + + int slideLength = int(ratioWindow * maxrect.width); + int slideStep = 1; + int fromX = 0; + if (searchDirection == CharSearchDirection::LEFT) { + fromX = fromPoint.x - dist[0]; + } else if (searchDirection == CharSearchDirection::RIGHT) { + fromX = fromPoint.x + dist[0]; + } + + std::vector charCandidateVec; + for (int slideX = -slideLength; slideX < slideLength; slideX += slideStep) { + float x_slide = 0; + + if (searchDirection == CharSearchDirection::LEFT) { + x_slide = float(fromX - slideX); + } else if (searchDirection == CharSearchDirection::RIGHT) { + x_slide = float(fromX + slideX); + } + + float y_slide = k * (x_slide - x_1) + y_1; + Point2f p_slide(x_slide, y_slide); + cv::circle(result, p_slide, 2, Scalar(255, 255, 255), 1); + + int chineseWidth = int(maxrect.width * 1.05); + int chineseHeight = int(maxrect.height * 1.05); + + Rect rect(Point2f(x_slide - chineseWidth / 2, y_slide - chineseHeight / 2), Size(chineseWidth, chineseHeight)); + + if (rect.tl().x < 0 || rect.tl().y < 0 || rect.br().x >= image.cols || rect.br().y >= image.rows) + continue; + + Mat region = image(rect); + Mat binary_region; + + cv::threshold(region, binary_region, ostu_level, 255, CV_THRESH_BINARY); + //double ostu_level = threshold(region, binary_region, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU); + //std::cout << "ostu_level:" << ostu_level << std::endl;*/ + + Mat charInput = preprocessChar(binary_region, 20); + + if (0) { + imshow("charInput", charInput); + waitKey(0); + destroyWindow("charInput"); + } + + CCharacter charCandidate; + charCandidate.setCharacterPos(rect); + charCandidate.setCharacterMat(charInput); + charCandidate.setIsChinese(isChinese); + charCandidateVec.push_back(charCandidate); + } + + if (isChinese) { + CharsIdentify::instance()->classifyChinese(charCandidateVec); + } else { + CharsIdentify::instance()->classify(charCandidateVec); + } + + double overlapThresh = 0.1; + NMStoCharacter(charCandidateVec, overlapThresh); + + for (auto character : charCandidateVec) { + Rect rect = character.getCharacterPos(); + Point center(rect.tl().x + rect.width / 2, rect.tl().y + rect.height / 2); + + if (character.getCharacterScore() > threshIsCharacter && character.getCharacterStr() != "1") { + //cv::rectangle(result, rect, Scalar(255, 255, 255), 1); + plateResult |= rect; + slideCharacter.push_back(character); + fromPoint = center; + + if (0) { + std::cout << "label:" << character.getCharacterStr(); + std::cout << "__score:" << character.getCharacterScore() << std::endl; + } + } + } + } + + + bool judegMDOratio2(const Mat &image, const Rect &rect, std::vector &contour, Mat &result, const float thresh, + bool useExtendHeight) { + + Mat mser = image(rect); + Mat mser_mat; + cv::threshold(mser, mser_mat, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU); + + Rect normalRect = adaptive_charrect_from_rect(rect, image.cols, image.rows, useExtendHeight); + Mat region = image(normalRect); + Mat thresh_mat; + cv::threshold(region, thresh_mat, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU); + + // count mser diff ratio + int countdiff = abs(countNonZero(thresh_mat) - countNonZero(mser_mat)); + + float MserDiffOstuRatio = float(countdiff) / float(rect.area()); + + if (MserDiffOstuRatio > thresh) { + //std::cout << "MserDiffOstuRatio:" << MserDiffOstuRatio << std::endl; + /*imshow("tmpMat", mser_mat); + waitKey(0); + imshow("tmpMat", thresh_mat); + waitKey(0);*/ + + cv::rectangle(result, normalRect, Scalar(0, 0, 0), 2); + return false; + } + + return true; + } + + Rect interRect(const Rect &a, const Rect &b) { + Rect c; + int x1 = a.x > b.x ? a.x : b.x; + int y1 = a.y > b.y ? a.y : b.y; + c.width = (a.x + a.width < b.x + b.width ? a.x + a.width : b.x + b.width) - x1; + c.height = (a.y + a.height < b.y + b.height ? a.y + a.height : b.y + b.height) - y1; + c.x = x1; + c.y = y1; + if (c.width <= 0 || c.height <= 0) + c = Rect(); + return c; + } + + Rect mergeRect(const Rect &a, const Rect &b) { + Rect c; + int x1 = a.x < b.x ? a.x : b.x; + int y1 = a.y < b.y ? a.y : b.y; + c.width = (a.x + a.width > b.x + b.width ? a.x + a.width : b.x + b.width) - x1; + c.height = (a.y + a.height > b.y + b.height ? a.y + a.height : b.y + b.height) - y1; + c.x = x1; + c.y = y1; + return c; + } + + bool computeIOU(const RotatedRect &rrect1, const RotatedRect &rrect2, const int width, const int height, + const float thresh, float &result) { + Rect_ safe_rect1; + calcSafeRect(rrect1, width, height, safe_rect1); + + Rect_ safe_rect2; + calcSafeRect(rrect2, width, height, safe_rect2); + + Rect inter = interRect(safe_rect1, safe_rect2); + Rect urect = mergeRect(safe_rect1, safe_rect2); + + float iou = (float) inter.area() / (float) urect.area(); + + result = iou; + + if (iou > thresh) { + return true; + } + + return false; + } + + float computeIOU(const RotatedRect &rrect1, const RotatedRect &rrect2, const int width, const int height) { + Rect_ safe_rect1; + calcSafeRect(rrect1, width, height, safe_rect1); + + Rect_ safe_rect2; + calcSafeRect(rrect2, width, height, safe_rect2); + + Rect inter = interRect(safe_rect1, safe_rect2); + Rect urect = mergeRect(safe_rect1, safe_rect2); + + float iou = (float) inter.area() / (float) urect.area(); + //std::cout << "iou" << iou << std::endl; + + return iou; + } + + bool computeIOU(const Rect &rect1, const Rect &rect2, const float thresh, float &result) { + + Rect inter = interRect(rect1, rect2); + Rect urect = mergeRect(rect1, rect2); + + float iou = (float) inter.area() / (float) urect.area(); + result = iou; + + if (iou > thresh) { + return true; + } + + return false; + } + + float computeIOU(const Rect &rect1, const Rect &rect2) { + + Rect inter = interRect(rect1, rect2); + Rect urect = mergeRect(rect1, rect2); + + float iou = (float) inter.area() / (float) urect.area(); + + return iou; + } + + +// the slope are nealy the same along the line +// if one slope is much different others, it should be outliers +// this function to remove it + void removeRightOutliers(std::vector &charGroup, std::vector &out_charGroup, double thresh1, + double thresh2, Mat result) { + std::sort(charGroup.begin(), charGroup.end(), + [](const CCharacter &r1, const CCharacter &r2) { + return r1.getCenterPoint().x < r2.getCenterPoint().x; + }); + + std::vector slopeVec; + float slope_last = 0; + for (size_t charGroup_i = 0; charGroup_i + 1 < charGroup.size(); charGroup_i++) { + // line_between_two_points + Vec4f line_btp; + CCharacter leftChar = charGroup.at(charGroup_i); + CCharacter rightChar = charGroup.at(charGroup_i + 1); + std::vector two_points; + two_points.push_back(leftChar.getCenterPoint()); + two_points.push_back(rightChar.getCenterPoint()); + fitLine(Mat(two_points), line_btp, CV_DIST_L2, 0, 0.01, 0.01); + float slope = line_btp[1] / line_btp[0]; + slopeVec.push_back(slope); + + if (0) { + cv::line(result, leftChar.getCenterPoint(), rightChar.getCenterPoint(), Scalar(0, 0, 255)); + } + } + + int uniformity_count = 0; + int outlier_index = -1; + for (size_t slopeVec_i = 0; slopeVec_i + 1 < slopeVec.size(); slopeVec_i++) { + float slope_1 = slopeVec.at(slopeVec_i); + float slope_2 = slopeVec.at(slopeVec_i + 1); + float slope_diff = abs(slope_1 - slope_2); + if (0) { + std::cout << "slope_diff:" << slope_diff << std::endl; + } + if (slope_diff <= thresh1) { + uniformity_count++; + } + if (0) { + std::cout << "slope_1:" << slope_1 << std::endl; + std::cout << "slope_2:" << slope_2 << std::endl; + } + if (1/*(slope_1 <= 0 && slope_2 >= 0) || (slope_1 >= 0 && slope_2 <= 0)*/) { + if (uniformity_count >= 2 && slope_diff >= thresh2) { + outlier_index = slopeVec_i + 2; + break; + } + } + } + if (0) { + std::cout << "uniformity_count:" << uniformity_count << std::endl; + std::cout << "outlier_index:" << outlier_index << std::endl; + } + + for (int charGroup_i = 0; charGroup_i < (int) charGroup.size(); charGroup_i++) { + if (charGroup_i != outlier_index) { + CCharacter theChar = charGroup.at(charGroup_i); + out_charGroup.push_back(theChar); + } + } + + if (0) { + std::cout << "end:" << std::endl; + } + } + + Rect getSafeRect(Point2f center, float width, float height, Mat image) { + int rows = image.rows; + int cols = image.cols; + + float x = center.x; + float y = center.y; + + float x_tl = (x - width / 2.f); + float y_tl = (y - height / 2.f); + + float x_br = (x + width / 2.f); + float y_br = (y + height / 2.f); + + x_tl = x_tl > 0.f ? x_tl : 0.f; + y_tl = y_tl > 0.f ? y_tl : 0.f; + x_br = x_br < (float) image.cols ? x_br : (float) image.cols; + y_br = y_br < (float) image.rows ? y_br : (float) image.rows; + + Rect rect(Point((int) x_tl, int(y_tl)), Point((int) x_br, int(y_br))); + return rect; + } + +// based on the assumptions: distance beween two nearby characters in plate are the same. +// add not found rect and combine two small and near rect. + void reFoundAndCombineRect(std::vector &mserCharacter, float min_thresh, float max_thresh, + Vec2i dist, Rect maxrect, Mat result) { + if (mserCharacter.size() == 0) { + return; + } + + std::sort(mserCharacter.begin(), mserCharacter.end(), + [](const CCharacter &r1, const CCharacter &r2) { + return r1.getCenterPoint().x < r2.getCenterPoint().x; + }); + + int comparDist = dist[0] * dist[0] + dist[1] * dist[1]; + if (0) { + std::cout << "comparDist:" << comparDist << std::endl; + } + + std::vector reCharacters; + + size_t mserCharacter_i = 0; + for (; mserCharacter_i + 1 < mserCharacter.size(); mserCharacter_i++) { + CCharacter leftChar = mserCharacter.at(mserCharacter_i); + CCharacter rightChar = mserCharacter.at(mserCharacter_i + 1); + + Point leftCenter = leftChar.getCenterPoint(); + Point rightCenter = rightChar.getCenterPoint(); + + int x_diff = leftCenter.x - rightCenter.x; + int y_diff = leftCenter.y - rightCenter.y; + + // distance between two centers + int distance2 = x_diff * x_diff + y_diff * y_diff; + + if (0) { + std::cout << "distance2:" << distance2 << std::endl; + } + + float ratio = (float) distance2 / (float) comparDist; + if (ratio > max_thresh) { + float x_add = (float) (leftCenter.x + rightCenter.x) / 2.f; + float y_add = (float) (leftCenter.y + rightCenter.y) / 2.f; + + float width = (float) maxrect.width; + float height = (float) maxrect.height; + + float x_tl = (x_add - width / 2.f); + float y_tl = (y_add - height / 2.f); + + //Rect rect_add((int)x_tl, (int)y_tl, (int)width, (int)height); + Rect rect_add = getSafeRect(Point2f(x_add, y_add), width, height, result); + + reCharacters.push_back(leftChar); + + CCharacter charAdd; + charAdd.setCenterPoint(Point((int) x_add, (int) y_add)); + charAdd.setCharacterPos(rect_add); + reCharacters.push_back(charAdd); + + if (1) { + cv::rectangle(result, rect_add, Scalar(0, 128, 255)); + } + } else if (ratio < min_thresh) { + Rect rect_union = leftChar.getCharacterPos() | rightChar.getCharacterPos(); + /*float x_add = (float)(leftCenter.x + rightCenter.x) / 2.f; + float y_add = (float)(leftCenter.y + rightCenter.y) / 2.f;*/ + int x_add = rect_union.tl().x + rect_union.width / 2; + int y_add = rect_union.tl().y + rect_union.height / 2; + + CCharacter charAdd; + charAdd.setCenterPoint(Point(x_add, y_add)); + charAdd.setCharacterPos(rect_union); + reCharacters.push_back(charAdd); + if (1) { + cv::rectangle(result, rect_union, Scalar(0, 128, 255)); + } + + mserCharacter_i++; + } else { + reCharacters.push_back(leftChar); + } + } + + if (mserCharacter_i + 1 == mserCharacter.size()) { + reCharacters.push_back(mserCharacter.at(mserCharacter_i)); + } + + mserCharacter = reCharacters; + } + + + void removeOutliers(std::vector &charGroup, double thresh, Mat result) { + std::vector points; + Vec4f line; + for (auto character : charGroup) { + points.push_back(character.getCenterPoint()); + } + + fitLine(Mat(points), line, CV_DIST_L2, 0, 0.01, 0.01); + + float k = line[1] / line[0]; + float x_1 = line[2]; + float y_1 = line[3]; + float step = 100; + cv::line(result, Point2f(x_1 - step, y_1 - k * step), Point2f(x_1 + step, k * step + y_1), Scalar(0, 0, 255)); + + float a = k; + float b = -1; + float c = y_1 - k * x_1; + float sumdistance = 0; + for (auto character : charGroup) { + Point center = character.getCenterPoint(); + float distance = (a * center.x + b * center.y + c) / std::sqrt(a * a + b * b); + std::cout << "distance:" << distance << std::endl; + sumdistance += distance; + } + float avgdistance = sumdistance / (float) charGroup.size(); + + std::vector::iterator it = charGroup.begin(); + for (; it != charGroup.end();) { + Point center = it->getCenterPoint(); + float distance = a * center.x + b * center.y + c; + float ratio = distance / avgdistance; + std::cout << "ratio:" << ratio << std::endl; + if (ratio > (float) thresh) { + it = charGroup.erase(it); + } else { + ++it; + } + } + } + +//! use verify size to first generate char candidates + void mserCharMatch(const Mat &src, std::vector &match, std::vector &out_plateVec_blue, + std::vector &out_plateVec_yellow, + bool usePlateMser, std::vector &out_plateRRect_blue, + std::vector &out_plateRRect_yellow, int img_index, + bool showDebug) { + Mat image = src; + + std::vector>> all_contours; + std::vector> all_boxes; + all_contours.resize(2); + all_contours.at(0).reserve(1024); + all_contours.at(1).reserve(1024); + all_boxes.resize(2); + all_boxes.at(0).reserve(1024); + all_boxes.at(1).reserve(1024); + + match.resize(2); + + std::vector flags; + flags.push_back(BLUE); + flags.push_back(YELLOW); + + const int imageArea = image.rows * image.cols; + const int delta = 1; + //const int delta = CParams::instance()->getParam2i();; + const int minArea = 30; + const double maxAreaRatio = 0.05; + + Ptr mser; + mser = MSER2::create(delta, minArea, int(maxAreaRatio * imageArea)); + mser->detectRegions(image, all_contours.at(0), all_boxes.at(0), all_contours.at(1), all_boxes.at(1)); + + // mser detect + // color_index = 0 : mser-, detect white characters, which is in blue plate. + // color_index = 1 : mser+, detect dark characters, which is in yellow plate. + +#pragma omp parallel for + for (int color_index = 0; color_index < 2; color_index++) { + Color the_color = flags.at(color_index); + + std::vector charVec; + charVec.reserve(128); + + match.at(color_index) = Mat::zeros(image.rows, image.cols, image.type()); + + Mat result = image.clone(); + cvtColor(result, result, COLOR_GRAY2BGR); + + size_t size = all_contours.at(color_index).size(); + + int char_index = 0; + int char_size = 20; + + // Chinese plate has max 7 characters. + const int char_max_count = 7; + + // verify char size and output to rects; + for (size_t index = 0; index < size; index++) { + Rect rect = all_boxes.at(color_index)[index]; + std::vector &contour = all_contours.at(color_index)[index]; + + // sometimes a plate could be a mser rect, so we could + // also use mser algorithm to find plate + if (usePlateMser) { + RotatedRect rrect = minAreaRect(Mat(contour)); + if (verifyRotatedPlateSizes(rrect)) { + //rotatedRectangle(result, rrect, Scalar(255, 0, 0), 2); + if (the_color == BLUE) out_plateRRect_blue.push_back(rrect); + if (the_color == YELLOW) out_plateRRect_yellow.push_back(rrect); + } + } + + // find character + if (verifyCharSizes(rect)) { + Mat mserMat = adaptive_image_from_points(contour, rect, Size(char_size, char_size)); + Mat charInput = preprocessChar(mserMat, char_size); + Rect charRect = rect; + + Point center(charRect.tl().x + charRect.width / 2, charRect.tl().y + charRect.height / 2); + Mat tmpMat; + double ostu_level = cv::threshold(image(charRect), tmpMat, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU); + + //cv::circle(result, center, 3, Scalar(0, 0, 255), 2); + + // use judegMDOratio2 function to + // remove the small lines in character like "zh-cuan" + if (judegMDOratio2(image, rect, contour, result)) { + CCharacter charCandidate; + charCandidate.setCharacterPos(charRect); + charCandidate.setCharacterMat(charInput); + charCandidate.setOstuLevel(ostu_level); + charCandidate.setCenterPoint(center); + charCandidate.setIsChinese(false); + charVec.push_back(charCandidate); + } + } + } + + // improtant, use matrix multiplication to acclerate the + // classification of many samples. use the character + // score, we can use non-maximum superssion (nms) to + // reduce the characters which are not likely to be true + // charaters, and use the score to select the strong seed + // of which the score is larger than 0.9 + CharsIdentify::instance()->classify(charVec); + + // use nms to remove the character are not likely to be true. + double overlapThresh = 0.6; + //double overlapThresh = CParams::instance()->getParam1f(); + NMStoCharacter(charVec, overlapThresh); + charVec.shrink_to_fit(); + + std::vector strongSeedVec; + strongSeedVec.reserve(64); + std::vector weakSeedVec; + weakSeedVec.reserve(64); + std::vector littleSeedVec; + littleSeedVec.reserve(64); + + //size_t charCan_size = charVec.size(); + for (auto charCandidate : charVec) { + //CCharacter& charCandidate = charVec[char_index]; + Rect rect = charCandidate.getCharacterPos(); + double score = charCandidate.getCharacterScore(); + if (charCandidate.getIsStrong()) { + strongSeedVec.push_back(charCandidate); + } else if (charCandidate.getIsWeak()) { + weakSeedVec.push_back(charCandidate); + //cv::rectangle(result, rect, Scalar(255, 0, 255)); + } else if (charCandidate.getIsLittle()) { + littleSeedVec.push_back(charCandidate); + //cv::rectangle(result, rect, Scalar(255, 0, 255)); + } + } + + std::vector searchCandidate = charVec; + + // nms to srong seed, only leave the strongest one + overlapThresh = 0.3; + NMStoCharacter(strongSeedVec, overlapThresh); + + // merge chars to group + std::vector> charGroupVec; + charGroupVec.reserve(64); + mergeCharToGroup(strongSeedVec, charGroupVec); + + // genenrate the line of the group + // based on the assumptions , the mser rects which are + // given high socre by character classifier could be no doubtly + // be the characters in one plate, and we can use these characeters + // to fit a line which is the middle line of the plate. + std::vector plateVec; + plateVec.reserve(16); + for (auto charGroup : charGroupVec) { + Rect plateResult = charGroup[0].getCharacterPos(); + std::vector points; + points.reserve(32); + + Vec4f line; + int maxarea = 0; + Rect maxrect; + double ostu_level_sum = 0; + + int leftx = image.cols; + Point leftPoint(leftx, 0); + int rightx = 0; + Point rightPoint(rightx, 0); + + std::vector mserCharVec; + mserCharVec.reserve(32); + + // remove outlier CharGroup + std::vector roCharGroup; + roCharGroup.reserve(32); + + removeRightOutliers(charGroup, roCharGroup, 0.2, 0.5, result); + //roCharGroup = charGroup; + + for (auto character : roCharGroup) { + Rect charRect = character.getCharacterPos(); + cv::rectangle(result, charRect, Scalar(0, 255, 0), 1); + plateResult |= charRect; + + Point center(charRect.tl().x + charRect.width / 2, charRect.tl().y + charRect.height / 2); + points.push_back(center); + mserCharVec.push_back(character); + //cv::circle(result, center, 3, Scalar(0, 255, 0), 2); + + ostu_level_sum += character.getOstuLevel(); + + if (charRect.area() > maxarea) { + maxrect = charRect; + maxarea = charRect.area(); + } + if (center.x < leftPoint.x) { + leftPoint = center; + } + if (center.x > rightPoint.x) { + rightPoint = center; + } + } + + double ostu_level_avg = ostu_level_sum / (double) roCharGroup.size(); + if (1 && showDebug) { + std::cout << "ostu_level_avg:" << ostu_level_avg << std::endl; + } + float ratio_maxrect = (float) maxrect.width / (float) maxrect.height; + + if (points.size() >= 2 && ratio_maxrect >= 0.3) { + fitLine(Mat(points), line, CV_DIST_L2, 0, 0.01, 0.01); + + float k = line[1] / line[0]; + //float angle = atan(k) * 180 / (float)CV_PI; + //std::cout << "k:" << k << std::endl; + //std::cout << "angle:" << angle << std::endl; + //std::cout << "cos:" << 0.3 * cos(k) << std::endl; + //std::cout << "ratio_maxrect:" << ratio_maxrect << std::endl; + + std::sort(mserCharVec.begin(), mserCharVec.end(), + [](const CCharacter &r1, const CCharacter &r2) { + return r1.getCharacterPos().tl().x < r2.getCharacterPos().tl().x; + }); + + CCharacter midChar = mserCharVec.at(int(mserCharVec.size() / 2.f)); + Rect midRect = midChar.getCharacterPos(); + Point midCenter(midRect.tl().x + midRect.width / 2, midRect.tl().y + midRect.height / 2); + + int mindist = 7 * maxrect.width; + std::vector distVecVec; + distVecVec.reserve(32); + + Vec2i mindistVec; + Vec2i avgdistVec; + + // computer the dist which is the distacne between + // two near characters in the plate, use dist we can + // judege how to computer the max search range, and choose the + // best location of the sliding window in the next steps. + for (size_t mser_i = 0; mser_i + 1 < mserCharVec.size(); mser_i++) { + Rect charRect = mserCharVec.at(mser_i).getCharacterPos(); + Point center(charRect.tl().x + charRect.width / 2, charRect.tl().y + charRect.height / 2); + + Rect charRectCompare = mserCharVec.at(mser_i + 1).getCharacterPos(); + Point centerCompare(charRectCompare.tl().x + charRectCompare.width / 2, + charRectCompare.tl().y + charRectCompare.height / 2); + + int dist = charRectCompare.x - charRect.x; + Vec2i distVec(charRectCompare.x - charRect.x, charRectCompare.y - charRect.y); + distVecVec.push_back(distVec); + + //if (dist < mindist) { + // mindist = dist; + // mindistVec = distVec; + //} + } + + std::sort(distVecVec.begin(), distVecVec.end(), + [](const Vec2i &r1, const Vec2i &r2) { + return r1[0] < r2[0]; + }); + + avgdistVec = distVecVec.at(int((distVecVec.size() - 1) / 2.f)); + + //float step = 10.f * (float)maxrect.width; + //float step = (float)mindistVec[0]; + float step = (float) avgdistVec[0]; + + //cv::line(result, Point2f(line[2] - step, line[3] - k*step), Point2f(line[2] + step, k*step + line[3]), Scalar(255, 255, 255)); + cv::line(result, Point2f(midCenter.x - step, midCenter.y - k * step), + Point2f(midCenter.x + step, k * step + midCenter.y), Scalar(255, 255, 255)); + //cv::circle(result, leftPoint, 3, Scalar(0, 0, 255), 2); + + CPlate plate; + plate.setPlateLeftPoint(leftPoint); + plate.setPlateRightPoint(rightPoint); + + plate.setPlateLine(line); + plate.setPlatDistVec(avgdistVec); + plate.setOstuLevel(ostu_level_avg); + + plate.setPlateMergeCharRect(plateResult); + plate.setPlateMaxCharRect(maxrect); + plate.setMserCharacter(mserCharVec); + plateVec.push_back(plate); + } + } + + // use strong seed to construct the first shape of the plate, + // then we need to find characters which are the weak seed. + // because we use strong seed to build the middle lines of the plate, + // we can simply use this to consider weak seeds only lie in the + // near place of the middle line + for (auto plate : plateVec) { + Vec4f line = plate.getPlateLine(); + Point leftPoint = plate.getPlateLeftPoint(); + Point rightPoint = plate.getPlateRightPoint(); + + Rect plateResult = plate.getPlateMergeCharRect(); + Rect maxrect = plate.getPlateMaxCharRect(); + Vec2i dist = plate.getPlateDistVec(); + double ostu_level = plate.getOstuLevel(); + + std::vector mserCharacter = plate.getCopyOfMserCharacters(); + mserCharacter.reserve(16); + + float k = line[1] / line[0]; + float x_1 = line[2]; + float y_1 = line[3]; + + std::vector searchWeakSeedVec; + searchWeakSeedVec.reserve(16); + + std::vector searchRightWeakSeed; + searchRightWeakSeed.reserve(8); + std::vector searchLeftWeakSeed; + searchLeftWeakSeed.reserve(8); + + std::vector slideRightWindow; + slideRightWindow.reserve(8); + std::vector slideLeftWindow; + slideLeftWindow.reserve(8); + + // draw weak seed and little seed from line; + // search for mser rect + if (1 && showDebug) { + std::cout << "search for mser rect:" << std::endl; + } + + if (0 && showDebug) { + std::stringstream ss(std::stringstream::in | std::stringstream::out); + ss << "resources/image/tmp/" << img_index << "_1_" << "searcgMserRect.jpg"; + imwrite(ss.str(), result); + } + if (1 && showDebug) { + std::cout << "mserCharacter:" << mserCharacter.size() << std::endl; + } + + // if the count of strong seed is larger than max count, we dont need + // all the next steps, if not, we first need to search the weak seed in + // the same line as the strong seed. The judge condition contains the distance + // between strong seed and weak seed , and the rect simily of each other to improve + // the roubustnedd of the seed growing algorithm. + if (mserCharacter.size() < char_max_count) { + double thresh1 = 0.15; + double thresh2 = 2.0; + searchWeakSeed(searchCandidate, searchRightWeakSeed, thresh1, thresh2, line, rightPoint, + maxrect, plateResult, result, CharSearchDirection::RIGHT); + if (1 && showDebug) { + std::cout << "searchRightWeakSeed:" << searchRightWeakSeed.size() << std::endl; + } + for (auto seed : searchRightWeakSeed) { + cv::rectangle(result, seed.getCharacterPos(), Scalar(255, 0, 0), 1); + mserCharacter.push_back(seed); + } + + searchWeakSeed(searchCandidate, searchLeftWeakSeed, thresh1, thresh2, line, leftPoint, + maxrect, plateResult, result, CharSearchDirection::LEFT); + if (1 && showDebug) { + std::cout << "searchLeftWeakSeed:" << searchLeftWeakSeed.size() << std::endl; + } + for (auto seed : searchLeftWeakSeed) { + cv::rectangle(result, seed.getCharacterPos(), Scalar(255, 0, 0), 1); + mserCharacter.push_back(seed); + } + } + + // sometimes the weak seed is in the middle of the strong seed. + // and sometimes two strong seed are actually the two parts of one character. + // because we only consider the weak seed in the left and right direction of strong seed. + // now we examine all the strong seed and weak seed. not only to find the seed in the middle, + // but also to combine two seed which are parts of one character to one seed. + // only by this process, we could use the seed count as the condition to judge if or not to use slide window. + float min_thresh = 0.3f; + float max_thresh = 2.5f; + reFoundAndCombineRect(mserCharacter, min_thresh, max_thresh, dist, maxrect, result); + + // if the characters count is less than max count + // this means the mser rect in the lines are not enough. + // sometimes there are still some characters could not be captured by mser algorithm, + // such as blur, low light ,and some chinese characters like zh-cuan. + // to handle this ,we use a simple slide window method to find them. + if (mserCharacter.size() < char_max_count) { + if (1 && showDebug) { + std::cout << "search chinese:" << std::endl; + std::cout << "judege the left is chinese:" << std::endl; + } + + // if the left most character is chinese, this means + // that must be the first character in chinese plate, + // and we need not to do a slide window to left. So, + // the first thing is to judge the left charcater is + // or not the chinese. + bool leftIsChinese = false; + if (1) { + std::sort(mserCharacter.begin(), mserCharacter.end(), + [](const CCharacter &r1, const CCharacter &r2) { + return r1.getCharacterPos().tl().x < r2.getCharacterPos().tl().x; + }); + + CCharacter leftChar = mserCharacter[0]; + + //Rect theRect = adaptive_charrect_from_rect(leftChar.getCharacterPos(), image.cols, image.rows); + Rect theRect = leftChar.getCharacterPos(); + //cv::rectangle(result, theRect, Scalar(255, 0, 0), 1); + + Mat region = image(theRect); + Mat binary_region; + + ostu_level = cv::threshold(region, binary_region, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU); + if (1 && showDebug) { + std::cout << "left : ostu_level:" << ostu_level << std::endl; + } + //plate.setOstuLevel(ostu_level); + + Mat charInput = preprocessChar(binary_region, char_size); + if (0 /*&& showDebug*/) { + imshow("charInput", charInput); + waitKey(0); + destroyWindow("charInput"); + } + + std::string label = ""; + float maxVal = -2.f; + leftIsChinese = CharsIdentify::instance()->isCharacter(charInput, label, maxVal, true); + //auto character = CharsIdentify::instance()->identifyChinese(charInput, maxVal, leftIsChinese); + //label = character.second; + if (0 /* && showDebug*/) { + std::cout << "isChinese:" << leftIsChinese << std::endl; + std::cout << "chinese:" << label; + std::cout << "__score:" << maxVal << std::endl; + } + } + + // if the left most character is not a chinese, + // this means we meed to slide a window to find the missed mser rect. + // search for sliding window + float ratioWindow = 0.4f; + //float ratioWindow = CParams::instance()->getParam3f(); + float threshIsCharacter = 0.8f; + //float threshIsCharacter = CParams::instance()->getParam3f(); + if (!leftIsChinese) { + slideWindowSearch(image, slideLeftWindow, line, leftPoint, dist, ostu_level, ratioWindow, threshIsCharacter, + maxrect, plateResult, CharSearchDirection::LEFT, true, result); + if (1 && showDebug) { + std::cout << "slideLeftWindow:" << slideLeftWindow.size() << std::endl; + } + for (auto window : slideLeftWindow) { + cv::rectangle(result, window.getCharacterPos(), Scalar(0, 0, 255), 1); + mserCharacter.push_back(window); + } + } + } + + // if we still have less than max count characters, + // we need to slide a window to right to search for the missed mser rect. + if (mserCharacter.size() < char_max_count) { + // change ostu_level + float ratioWindow = 0.4f; + //float ratioWindow = CParams::instance()->getParam3f(); + float threshIsCharacter = 0.8f; + //float threshIsCharacter = CParams::instance()->getParam3f(); + slideWindowSearch(image, slideRightWindow, line, rightPoint, dist, plate.getOstuLevel(), ratioWindow, + threshIsCharacter, + maxrect, plateResult, CharSearchDirection::RIGHT, false, result); + if (1 && showDebug) { + std::cout << "slideRightWindow:" << slideRightWindow.size() << std::endl; + } + for (auto window : slideRightWindow) { + cv::rectangle(result, window.getCharacterPos(), Scalar(0, 0, 255), 1); + mserCharacter.push_back(window); + } + } + + // computer the plate angle + float angle = atan(k) * 180 / (float) CV_PI; + if (1 && showDebug) { + std::cout << "k:" << k << std::endl; + std::cout << "angle:" << angle << std::endl; + } + + // the plateResult rect need to be enlarge to contains all the plate, + // not only the character area. + float widthEnlargeRatio = 1.15f; //1.15f; + float heightEnlargeRatio = 1.25f; //1.25f; + RotatedRect platePos( + Point2f((float) plateResult.x + plateResult.width / 2.f, (float) plateResult.y + plateResult.height / 2.f), + Size2f(plateResult.width * widthEnlargeRatio, maxrect.height * heightEnlargeRatio), angle); + + // justify the size is likely to be a plate size. + if (verifyRotatedPlateSizes(platePos)) { + rotatedRectangle(result, platePos, Scalar(0, 0, 255), 1); + + plate.setPlatePos(platePos); + plate.setPlateColor(the_color); + plate.setPlateLocateType(CMSER); + + if (the_color == BLUE) out_plateVec_blue.push_back(plate); + if (the_color == YELLOW) out_plateVec_yellow.push_back(plate); + } + + // use deskew to rotate the image, so we need the binary image. + if (1) { + for (auto mserChar : mserCharacter) { + Rect rect = mserChar.getCharacterPos(); + match.at(color_index)(rect) = 255; + } + cv::line(match.at(color_index), rightPoint, leftPoint, Scalar(255)); + } + } + + if (0 /*&& showDebug*/) { + imshow("result", result); + waitKey(0); + destroyWindow("result"); + } + + if (0) { + imshow("match", match.at(color_index)); + waitKey(0); + destroyWindow("match"); + } + + if (1) { + std::stringstream ss(std::stringstream::in | std::stringstream::out); + ss << "resources/image/tmp/plateDetect/plate_" << img_index << "_" << the_color << ".jpg"; + imwrite(ss.str(), result); + } + } + + + } + +// this spatial_ostu algorithm are robust to +// the plate which has the same light shine, which is that +// the light in the left of the plate is strong than the right. + void spatial_ostu(InputArray _src, int grid_x, int grid_y, Color type) { + Mat src = _src.getMat(); + + int width = src.cols / grid_x; + int height = src.rows / grid_y; + + // iterate through grid + for (int i = 0; i < grid_y; i++) { + for (int j = 0; j < grid_x; j++) { + Mat src_cell = Mat(src, Range(i * height, (i + 1) * height), Range(j * width, (j + 1) * width)); + if (type == BLUE) { + cv::threshold(src_cell, src_cell, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY); + } else if (type == YELLOW) { + cv::threshold(src_cell, src_cell, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY_INV); + } else if (type == WHITE) { + cv::threshold(src_cell, src_cell, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY_INV); + } else { + cv::threshold(src_cell, src_cell, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY); + } + } + } + } + + + bool mat_valid_position(const Mat &mat, int row, int col) { + return row >= 0 && col >= 0 && row < mat.rows && col < mat.cols; + } + + + template + static void mat_set_invoke(Mat &mat, int row, int col, const Scalar &value) { + if (1 == mat.channels()) { + mat.at(row, col) = (T) value.val[0]; + } else if (3 == mat.channels()) { + T *ptr_src = mat.ptr(row, col); + *ptr_src++ = (T) value.val[0]; + *ptr_src++ = (T) value.val[1]; + *ptr_src = (T) value.val[2]; + } else if (4 == mat.channels()) { + T *ptr_src = mat.ptr(row, col); + *ptr_src++ = (T) value.val[0]; + *ptr_src++ = (T) value.val[1]; + *ptr_src++ = (T) value.val[2]; + *ptr_src = (T) value.val[3]; + } + } + + void setPoint(Mat &mat, int row, int col, const Scalar &value) { + if (CV_8U == mat.depth()) { + mat_set_invoke(mat, row, col, value); + } else if (CV_8S == mat.depth()) { + mat_set_invoke(mat, row, col, value); + } else if (CV_16U == mat.depth()) { + mat_set_invoke(mat, row, col, value); + } else if (CV_16S == mat.depth()) { + mat_set_invoke(mat, row, col, value); + } else if (CV_32S == mat.depth()) { + mat_set_invoke(mat, row, col, value); + } else if (CV_32F == mat.depth()) { + mat_set_invoke(mat, row, col, value); + } else if (CV_64F == mat.depth()) { + mat_set_invoke(mat, row, col, value); + } + } + + Rect adaptive_charrect_from_rect(const Rect &rect, int maxwidth, int maxheight, bool useExtendHeight) { + int expendWidth = 0; + int extendHeight = 0; + + if (rect.height > 3 * rect.width) { + expendWidth = int((int(rect.height * 0.5f) - rect.width) * 0.5f); + if (useExtendHeight) { + extendHeight = int(rect.height * 0.3f); + } + } + + //Rect resultRect(rect.tl().x - expendWidth, rect.tl().y, + // rect.width + expendWidth * 2, rect.height); + + int tlx = rect.tl().x - expendWidth > 0 ? rect.tl().x - expendWidth : 0; + int tly = rect.tl().y - extendHeight > 0 ? rect.tl().y - extendHeight : 0; + + int brx = rect.br().x + expendWidth < maxwidth ? rect.br().x + expendWidth : maxwidth; + int bry = rect.br().y + extendHeight < maxheight ? rect.br().y + extendHeight : maxheight; + + Rect resultRect(tlx, tly, brx - tlx, bry - tly); + return resultRect; + } + + + Mat adaptive_image_from_points(const std::vector &points, + const Rect &rect, const Size &size, + const Scalar &backgroundColor /* = ml_color_white */, + const Scalar &forgroundColor /* = ml_color_black */, bool gray /* = true */) { + int expendHeight = 0; + int expendWidth = 0; + + if (rect.width > rect.height) { + expendHeight = (rect.width - rect.height) / 2; + } else if (rect.height > rect.width) { + expendWidth = (rect.height - rect.width) / 2; + } + + Mat image(rect.height + expendHeight * 2, rect.width + expendWidth * 2, gray ? CV_8UC1 : CV_8UC3, backgroundColor); + + for (int i = 0; i < (int) points.size(); ++i) { + Point point = points[i]; + Point currentPt(point.x - rect.tl().x + expendWidth, point.y - rect.tl().y + expendHeight); + if (mat_valid_position(image, currentPt.y, currentPt.x)) { + setPoint(image, currentPt.y, currentPt.x, forgroundColor); + } + } + + Mat result; + cv::resize(image, result, size, 0, 0, INTER_NEAREST); + + return result; + } + +// calc safe Rect +// if not exit, return false + + bool calcSafeRect(const RotatedRect &roi_rect, const Mat &src, + Rect_ &safeBoundRect) { + Rect_ boudRect = roi_rect.boundingRect(); + + float tl_x = boudRect.x > 0 ? boudRect.x : 0; + float tl_y = boudRect.y > 0 ? boudRect.y : 0; + + float br_x = boudRect.x + boudRect.width < src.cols + ? boudRect.x + boudRect.width - 1 + : src.cols - 1; + float br_y = boudRect.y + boudRect.height < src.rows + ? boudRect.y + boudRect.height - 1 + : src.rows - 1; + + float roi_width = br_x - tl_x; + float roi_height = br_y - tl_y; + + if (roi_width <= 0 || roi_height <= 0) return false; + + // a new rect not out the range of mat + + safeBoundRect = Rect_(tl_x, tl_y, roi_width, roi_height); + + return true; + } + + bool calcSafeRect(const RotatedRect &roi_rect, const int width, const int height, + Rect_ &safeBoundRect) { + Rect_ boudRect = roi_rect.boundingRect(); + + float tl_x = boudRect.x > 0 ? boudRect.x : 0; + float tl_y = boudRect.y > 0 ? boudRect.y : 0; + + float br_x = boudRect.x + boudRect.width < width + ? boudRect.x + boudRect.width - 1 + : width - 1; + float br_y = boudRect.y + boudRect.height < height + ? boudRect.y + boudRect.height - 1 + : height - 1; + + float roi_width = br_x - tl_x; + float roi_height = br_y - tl_y; + + if (roi_width <= 0 || roi_height <= 0) return false; + + // a new rect not out the range of mat + + safeBoundRect = Rect_(tl_x, tl_y, roi_width, roi_height); + + return true; + } + + + Mat uniformResize(const Mat &result, float &scale) { + const int RESULTWIDTH = kShowWindowWidth; // 640 930 + const int RESULTHEIGHT = kShowWindowHeight; // 540 710 + + Mat img_window; + img_window.create(RESULTHEIGHT, RESULTWIDTH, CV_8UC3); + + int nRows = result.rows; + int nCols = result.cols; + + Mat result_resize; + if (nCols <= img_window.cols && nRows <= img_window.rows) { + result_resize = result; + } else if (nCols > img_window.cols && nRows <= img_window.rows) { + scale = float(img_window.cols) / float(nCols); + resize(result, result_resize, Size(), scale, scale, CV_INTER_AREA); + + } else if (nCols <= img_window.cols && nRows > img_window.rows) { + scale = float(img_window.rows) / float(nRows); + resize(result, result_resize, Size(), scale, scale, CV_INTER_AREA); + + } else if (nCols > img_window.cols && nRows > img_window.rows) { + float scale1 = float(img_window.cols) / float(nCols); + float scale2 = float(img_window.rows) / float(nRows); + scale = scale1 < scale2 ? scale1 : scale2; + resize(result, result_resize, Size(), scale, scale, CV_INTER_AREA); + } else { + result_resize = result; + } + return result_resize; + } + + Mat uniformResizePlates (const Mat &result, float &scale) { + const int RESULTWIDTH = kPlateResizeWidth; // 640 930 + const int RESULTHEIGHT = kPlateResizeHeight; // 540 710 + + Mat img_window; + img_window.create(RESULTHEIGHT, RESULTWIDTH, CV_8UC3); + + int nRows = result.rows; + int nCols = result.cols; + + Mat result_resize; + if (nCols <= img_window.cols && nRows <= img_window.rows) { + result_resize = result; + } + else if (nCols > img_window.cols && nRows <= img_window.rows) { + scale = float(img_window.cols) / float(nCols); + resize(result, result_resize, Size(), scale, scale, CV_INTER_AREA); + + } + else if (nCols <= img_window.cols && nRows > img_window.rows) { + scale = float(img_window.rows) / float(nRows); + resize(result, result_resize, Size(), scale, scale, CV_INTER_AREA); + + } + else if (nCols > img_window.cols && nRows > img_window.rows) { + float scale1 = float(img_window.cols) / float(nCols); + float scale2 = float(img_window.rows) / float(nRows); + scale = scale1 < scale2 ? scale1 : scale2; + resize(result, result_resize, Size(), scale, scale, CV_INTER_AREA); + } + else { + result_resize = result; + } + return result_resize; + } + + + + void showDectectResults(const Mat &img, const vector &plateVec, size_t num) { + int index = 0; + if (1) { + Mat result; + img.copyTo(result); + for (size_t j = 0; j < plateVec.size(); j++) { + // add plates to left corner + const CPlate& item = plateVec.at(j); + Mat plateMat = item.getPlateMat(); + + int height = 36; + int width = 136; + if (height * index + height < result.rows) { + Mat imageRoi = result(Rect(0, 0 + height * index, width, height)); + addWeighted(imageRoi, 0, plateMat, 1, 0, imageRoi); + } + index++; + + // draw the bouding box + RotatedRect theRect = item.getPlatePos(); + float scale = item.getPlateScale(); + RotatedRect minRect = scaleBackRRect(theRect, scale); + + Point2f rect_points[4]; + minRect.points(rect_points); + Scalar lineColor = Scalar(255, 255, 255); + if (item.getPlateLocateType() == SOBEL) lineColor = Scalar(255, 0, 0); + if (item.getPlateLocateType() == COLOR) lineColor = Scalar(0, 255, 0); + if (item.getPlateLocateType() == CMSER) lineColor = Scalar(0, 0, 255); + + for (int j = 0; j < 4; j++) + line(result, rect_points[j], rect_points[(j + 1) % 4], lineColor, 2, 8); + } + showResult(result); + } + } + + Mat showResult(const Mat &result, int img_index) { + namedWindow("EasyPR", CV_WINDOW_AUTOSIZE); + + const int RESULTWIDTH = kShowWindowWidth; // 640 930 + const int RESULTHEIGHT = kShowWindowHeight; // 540 710 + + Mat img_window; + img_window.create(RESULTHEIGHT, RESULTWIDTH, CV_8UC3); + + int nRows = result.rows; + int nCols = result.cols; + + Mat result_resize; + if (nCols <= img_window.cols && nRows <= img_window.rows) { + result_resize = result; + + } else if (nCols > img_window.cols && nRows <= img_window.rows) { + float scale = float(img_window.cols) / float(nCols); + resize(result, result_resize, Size(), scale, scale, CV_INTER_AREA); + + } else if (nCols <= img_window.cols && nRows > img_window.rows) { + float scale = float(img_window.rows) / float(nRows); + resize(result, result_resize, Size(), scale, scale, CV_INTER_AREA); + + } else if (nCols > img_window.cols && nRows > img_window.rows) { + float scale1 = float(img_window.cols) / float(nCols); + float scale2 = float(img_window.rows) / float(nRows); + float scale = scale1 < scale2 ? scale1 : scale2; + resize(result, result_resize, Size(), scale, scale, CV_INTER_AREA); + } + else { + result_resize = result; + } + + Mat imageRoi = img_window(Rect((RESULTWIDTH - result_resize.cols) / 2, + (RESULTHEIGHT - result_resize.rows) / 2, + result_resize.cols, result_resize.rows)); + addWeighted(imageRoi, 0, result_resize, 1, 0, imageRoi); + + if (1) { + imshow("EasyPR", img_window); + waitKey(0); + destroyWindow("EasyPR"); + } + + if (1) { + std::stringstream ss(std::stringstream::in | std::stringstream::out); + ss << "resources/image/tmp/Result/plate_" << img_index << ".jpg"; + imwrite(ss.str(), img_window); + } + + return img_window; + } + + Rect rectEnlarge(const Rect &src, const int mat_width, const int mat_height) { + float w = (float) src.width; + float h = (float) src.height; + // enlarge the rect, + // width to 120% + // height to 105% + float new_w = w * 1.2f; + float new_h = h * 1.05f; + + Rect_ boudRect; + boudRect.x = (float) src.x - w * 0.1f; + boudRect.y = (float) src.y - h * 0.025f; + boudRect.width = new_w; + boudRect.height = new_h; + + float tl_x = boudRect.x > 0 ? boudRect.x : 0; + float tl_y = boudRect.y > 0 ? boudRect.y : 0; + + float br_x = boudRect.x + boudRect.width - 1 <= mat_width - 1 + ? boudRect.x + boudRect.width - 1 + : mat_width - 1; + float br_y = boudRect.y + boudRect.height - 1 < mat_height - 1 + ? boudRect.y + boudRect.height - 1 + : mat_height - 1; + + float roi_width = br_x - tl_x + 1; + float roi_height = br_y - tl_y + 1; + + Rect dst(0, 0, 0, 0); + if (roi_width <= 0 || roi_height <= 0) + return src; + + //! a new rect not out the range of mat + dst = Rect_(tl_x, tl_y, roi_width, roi_height); + return dst; + + } + + Rect rectFit(const Rect &src, const int mat_width, const int mat_height) { + float w = (float)src.width; + float h = (float)src.height; + float new_w = h * 0.5f; + float new_h = h * 1.05f; + + if (new_w <= w || new_h <= h) { + return src; + } + + float ex_w = (new_w - w) * 0.5f; + float ex_h = (new_h - h) * 0.5f; + + Rect_ boudRect; + boudRect.x = (float)src.x - ex_w; + boudRect.y = (float)src.y - ex_h; + boudRect.width = new_w; + boudRect.height = new_h; + + float tl_x = boudRect.x > 0 ? boudRect.x : 0; + float tl_y = boudRect.y > 0 ? boudRect.y : 0; + + float br_x = boudRect.x + boudRect.width - 1 <= mat_width - 1 + ? boudRect.x + boudRect.width - 1 + : mat_width - 1; + float br_y = boudRect.y + boudRect.height - 1 < mat_height - 1 + ? boudRect.y + boudRect.height - 1 + : mat_height - 1; + + float roi_width = br_x - tl_x + 1; + float roi_height = br_y - tl_y + 1; + + Rect dst(0, 0, 0, 0); + if (roi_width <= 2 || roi_height <= 2) + return src; + + //! a new rect not out the range of mat + dst = Rect_(tl_x, tl_y, roi_width - 1, roi_height - 1); + return dst; + + } + + + void writeTempImage(const Mat &outImg, const string path, int index) { + std::stringstream ss(std::stringstream::in | std::stringstream::out); + time_t t = time(0); // get time now + struct tm *now = localtime(&t); + char buf[80]; + strftime(buf, sizeof(buf), "%Y-%m-%d %H_%M_%S", now); + ss << "resources/image/tmp/" << path << "_" << std::string(buf) << "_" << index << ".jpg"; + imwrite(ss.str(), outImg); + } +} \ No newline at end of file diff --git a/src/main/resources/easypr/core/feature.cpp b/src/main/resources/easypr/core/feature.cpp new file mode 100644 index 00000000..ddced9ab --- /dev/null +++ b/src/main/resources/easypr/core/feature.cpp @@ -0,0 +1,466 @@ +#include "easypr/core/feature.h" +#include "easypr/core/core_func.h" +#include "thirdparty/LBP/lbp.hpp" + +namespace easypr { + + +Mat getHistogram(Mat in) { + const int VERTICAL = 0; + const int HORIZONTAL = 1; + + // Histogram features + Mat vhist = ProjectedHistogram(in, VERTICAL); + Mat hhist = ProjectedHistogram(in, HORIZONTAL); + + // Last 10 is the number of moments components + int numCols = vhist.cols + hhist.cols; + + Mat out = Mat::zeros(1, numCols, CV_32F); + + int j = 0; + for (int i = 0; i < vhist.cols; i++) { + out.at(j) = vhist.at(i); + j++; + } + for (int i = 0; i < hhist.cols; i++) { + out.at(j) = hhist.at(i); + j++; + } + + return out; +} + +void getHistogramFeatures(const Mat& image, Mat& features) { + Mat grayImage; + cvtColor(image, grayImage, CV_RGB2GRAY); + + //grayImage = histeq(grayImage); + + Mat img_threshold; + threshold(grayImage, img_threshold, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY); + //Mat img_threshold = grayImage.clone(); + //spatial_ostu(img_threshold, 8, 2, getPlateType(image, false)); + + features = getHistogram(img_threshold); +} + +// compute color histom +void getColorFeatures(const Mat& src, Mat& features) { + Mat src_hsv; + + //grayImage = histeq(grayImage); + cvtColor(src, src_hsv, CV_BGR2HSV); + int channels = src_hsv.channels(); + int nRows = src_hsv.rows; + + // consider multi channel image + int nCols = src_hsv.cols * channels; + if (src_hsv.isContinuous()) { + nCols *= nRows; + nRows = 1; + } + + const int sz = 180; + int h[sz] = { 0 }; + + uchar* p; + for (int i = 0; i < nRows; ++i) { + p = src_hsv.ptr(i); + for (int j = 0; j < nCols; j += 3) { + int H = int(p[j]); // 0-180 + if (H > sz - 1) H = sz - 1; + if (H < 0) H = 0; + h[H]++; + } + } + + Mat mhist = Mat::zeros(1, sz, CV_32F); + for (int j = 0; j < sz; j++) { + mhist.at(j) = (float)h[j]; + } + + // Normalize histogram + double min, max; + minMaxLoc(mhist, &min, &max); + + if (max > 0) + mhist.convertTo(mhist, -1, 1.0f / max, 0); + + features = mhist; +} + + +void getHistomPlusColoFeatures(const Mat& image, Mat& features) { + // TODO + Mat feature1, feature2; + getHistogramFeatures(image, feature1); + getColorFeatures(image, feature2); + hconcat(feature1.reshape(1, 1), feature2.reshape(1, 1), features); +} + + +void getSIFTFeatures(const Mat& image, Mat& features) { + // TODO +} + + +//HOG Features +void getHOGFeatures(const Mat& image, Mat& features) { + //HOG descripter + HOGDescriptor hog(cvSize(128, 64), cvSize(16, 16), cvSize(8, 8), cvSize(8, 8), 3); //these parameters work well + std::vector descriptor; + + // resize input image to (128,64) for compute + Size dsize = Size(128,64); + Mat trainImg = Mat(dsize, CV_32S); + resize(image, trainImg, dsize); + + // compute descripter + hog.compute(trainImg, descriptor, Size(8, 8)); + + // copy the result + Mat mat_featrue(descriptor); + mat_featrue.copyTo(features); +} + + +void getHSVHistFeatures(const Mat& image, Mat& features) { + // TODO +} + +//! LBP feature +void getLBPFeatures(const Mat& image, Mat& features) { + + Mat grayImage; + cvtColor(image, grayImage, CV_RGB2GRAY); + + Mat lbpimage; + lbpimage = libfacerec::olbp(grayImage); + Mat lbp_hist = libfacerec::spatial_histogram(lbpimage, 32, 4, 4); + + features = lbp_hist; +} + +Mat charFeatures(Mat in, int sizeData) { + const int VERTICAL = 0; + const int HORIZONTAL = 1; + + // cut the cetner, will afect 5% perices. + Rect _rect = GetCenterRect(in); + Mat tmpIn = CutTheRect(in, _rect); + //Mat tmpIn = in.clone(); + + // Low data feature + Mat lowData; + resize(tmpIn, lowData, Size(sizeData, sizeData)); + + // Histogram features + Mat vhist = ProjectedHistogram(lowData, VERTICAL); + Mat hhist = ProjectedHistogram(lowData, HORIZONTAL); + + // Last 10 is the number of moments components + int numCols = vhist.cols + hhist.cols + lowData.cols * lowData.cols; + + Mat out = Mat::zeros(1, numCols, CV_32F); + // Asign values to + + int j = 0; + for (int i = 0; i < vhist.cols; i++) { + out.at(j) = vhist.at(i); + j++; + } + for (int i = 0; i < hhist.cols; i++) { + out.at(j) = hhist.at(i); + j++; + } + for (int x = 0; x < lowData.cols; x++) { + for (int y = 0; y < lowData.rows; y++) { + out.at(j) += (float)lowData.at (x, y); + j++; + } + } + + //std::cout << out << std::endl; + + return out; +} + + +Mat charFeatures2(Mat in, int sizeData) { + const int VERTICAL = 0; + const int HORIZONTAL = 1; + + // cut the cetner, will afect 5% perices. + Rect _rect = GetCenterRect(in); + Mat tmpIn = CutTheRect(in, _rect); + //Mat tmpIn = in.clone(); + + // Low data feature + Mat lowData; + resize(tmpIn, lowData, Size(sizeData, sizeData)); + + // Histogram features + Mat vhist = ProjectedHistogram(lowData, VERTICAL); + Mat hhist = ProjectedHistogram(lowData, HORIZONTAL); + + // Last 10 is the number of moments components + int numCols = vhist.cols + hhist.cols + lowData.cols * lowData.cols; + + Mat out = Mat::zeros(1, numCols, CV_32F); + + int j = 0; + for (int i = 0; i < vhist.cols; i++) { + out.at(j) = vhist.at(i); + j++; + } + for (int i = 0; i < hhist.cols; i++) { + out.at(j) = hhist.at(i); + j++; + } + for (int x = 0; x < lowData.cols; x++) { + for (int y = 0; y < lowData.rows; y++) { + out.at(j) += (float)lowData.at (x, y); + j++; + } + } + + //std::cout << out << std::endl; + + return out; +} + +Mat charProjectFeatures(const Mat& in, int sizeData) { + const int VERTICAL = 0; + const int HORIZONTAL = 1; + + SHOW_IMAGE(in, 0); + // cut the cetner, will afect 5% perices. + + Mat lowData; + resize(in, lowData, Size(sizeData, sizeData)); + + SHOW_IMAGE(lowData, 0); + // Histogram features + Mat vhist = ProjectedHistogram(lowData, VERTICAL); + Mat hhist = ProjectedHistogram(lowData, HORIZONTAL); + + // Last 10 is the number of moments components + int numCols = vhist.cols + hhist.cols; + + Mat out = Mat::zeros(1, numCols, CV_32F); + + int j = 0; + for (int i = 0; i < vhist.cols; i++) { + out.at(j) = vhist.at(i); + j++; + } + for (int i = 0; i < hhist.cols; i++) { + out.at(j) = hhist.at(i); + j++; + } + //std::cout << out << std::endl; + + return out; +} + +void getGrayCharFeatures(const Mat& grayChar, Mat& features) { + // TODO: check channnels == 1 + SHOW_IMAGE(grayChar, 0); + SHOW_IMAGE(255 - grayChar, 0); + + // resize to uniform size, like 20x32 + bool useResize = false; + bool useConvert = true; + bool useMean = true; + bool useLBP = false; + + Mat char_mat; + if (useResize) { + char_mat.create(kGrayCharHeight, kGrayCharWidth, CV_8UC1); + resize(grayChar, char_mat, char_mat.size(), 0, 0, INTER_LINEAR); + } else { + char_mat = grayChar; + } + SHOW_IMAGE(char_mat, 0); + + // convert to float + Mat float_img; + if (useConvert) { + float scale = 1.f / 255; + char_mat.convertTo(float_img, CV_32FC1, scale, 0); + } else { + float_img = char_mat; + } + SHOW_IMAGE(float_img, 0); + + // cut from mean, it can be optional + + Mat mean_img; + if (useMean) { + float_img -= mean(float_img); + mean_img = float_img; + } else { + mean_img = float_img; + } + SHOW_IMAGE(mean_img, 0); + + // use lbp to get features, it can be changed to other + Mat feautreImg; + if (useLBP) { + Mat lbpimage = libfacerec::olbp(char_mat); + SHOW_IMAGE(lbpimage, 0); + feautreImg = libfacerec::spatial_histogram(lbpimage, kCharLBPPatterns, kCharLBPGridX, kCharLBPGridY); + } else { + feautreImg = mean_img.reshape(1, 1); + } + + // return back + features = feautreImg; +} + + +void getGrayPlusProject(const Mat& grayChar, Mat& features) +{ + // TODO: check channnels == 1 + SHOW_IMAGE(grayChar, 0); + SHOW_IMAGE(255 - grayChar, 0); + + // resize to uniform size, like 20x32 + bool useResize = false; + bool useConvert = true; + bool useMean = true; + bool useLBP = false; + + Mat char_mat; + if (useResize) { + char_mat.create(kGrayCharHeight, kGrayCharWidth, CV_8UC1); + resize(grayChar, char_mat, char_mat.size(), 0, 0, INTER_LINEAR); + } + else { + char_mat = grayChar; + } + SHOW_IMAGE(char_mat, 0); + + // convert to float + Mat float_img; + if (useConvert) { + float scale = 1.f / 255; + char_mat.convertTo(float_img, CV_32FC1, scale, 0); + } + else { + float_img = char_mat; + } + SHOW_IMAGE(float_img, 0); + + // cut from mean, it can be optional + + Mat mean_img; + if (useMean) { + float_img -= mean(float_img); + mean_img = float_img; + } + else { + mean_img = float_img; + } + SHOW_IMAGE(mean_img, 0); + + // use lbp to get features, it can be changed to other + Mat feautreImg; + if (useLBP) { + Mat lbpimage = libfacerec::olbp(char_mat); + SHOW_IMAGE(lbpimage, 0); + feautreImg = libfacerec::spatial_histogram(lbpimage, kCharLBPPatterns, kCharLBPGridX, kCharLBPGridY); + } + else { + feautreImg = mean_img.reshape(1, 1); + } + SHOW_IMAGE(grayChar, 0); + Mat binaryChar; + threshold(grayChar, binaryChar, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY); + SHOW_IMAGE(binaryChar, 0); + Mat projectFeature = charProjectFeatures(binaryChar, 32); + + hconcat(feautreImg.reshape(1, 1), projectFeature.reshape(1, 1), features); +} + + +void getGrayPlusLBP(const Mat& grayChar, Mat& features) +{ + // TODO: check channnels == 1 + SHOW_IMAGE(grayChar, 0); + SHOW_IMAGE(255 - grayChar, 0); + + // resize to uniform size, like 20x32 + bool useResize = false; + bool useConvert = true; + bool useMean = true; + bool useLBP = true; + + Mat char_mat; + if (useResize) { + char_mat.create(kGrayCharHeight, kGrayCharWidth, CV_8UC1); + resize(grayChar, char_mat, char_mat.size(), 0, 0, INTER_LINEAR); + } + else { + char_mat = grayChar; + } + SHOW_IMAGE(char_mat, 0); + + // convert to float + Mat float_img; + if (useConvert) { + float scale = 1.f / 255; + char_mat.convertTo(float_img, CV_32FC1, scale, 0); + } + else { + float_img = char_mat; + } + SHOW_IMAGE(float_img, 0); + + // cut from mean, it can be optional + + Mat mean_img; + if (useMean) { + float_img -= mean(float_img); + mean_img = float_img; + } + else { + mean_img = float_img; + } + SHOW_IMAGE(mean_img, 0); + + // use lbp to get features, it can be changed to other + Mat originImage = mean_img.clone(); + Mat lbpimage = libfacerec::olbp(mean_img); + SHOW_IMAGE(lbpimage, 0); + lbpimage = libfacerec::spatial_histogram(lbpimage, kCharLBPPatterns, kCharLBPGridX, kCharLBPGridY); + + // 32x20 + 16x16 + hconcat(mean_img.reshape(1, 1), lbpimage.reshape(1, 1), features); +} + +void getLBPplusHistFeatures(const Mat& image, Mat& features) { + Mat grayImage; + cvtColor(image, grayImage, CV_RGB2GRAY); + + Mat lbpimage; + lbpimage = libfacerec::olbp(grayImage); + Mat lbp_hist = libfacerec::spatial_histogram(lbpimage, 64, 8, 4); + //features = lbp_hist.reshape(1, 1); + + Mat greyImage; + cvtColor(image, greyImage, CV_RGB2GRAY); + + //grayImage = histeq(grayImage); + Mat img_threshold; + threshold(greyImage, img_threshold, 0, 255, + CV_THRESH_OTSU + CV_THRESH_BINARY); + Mat histomFeatures = getHistogram(img_threshold); + + hconcat(lbp_hist.reshape(1, 1), histomFeatures.reshape(1, 1), features); + //std::cout << features << std::endl; + //features = histomFeatures; +} + +} diff --git a/src/main/resources/easypr/core/params.cpp b/src/main/resources/easypr/core/params.cpp new file mode 100644 index 00000000..88090439 --- /dev/null +++ b/src/main/resources/easypr/core/params.cpp @@ -0,0 +1,12 @@ +#include "easypr/core/params.h" + +namespace easypr { + CParams* CParams::instance_ = nullptr; + + CParams* CParams::instance() { + if (!instance_) { + instance_ = new CParams; + } + return instance_; + } +}/*! \namespace easypr*/ \ No newline at end of file diff --git a/src/main/resources/easypr/core/plate_detect.cpp b/src/main/resources/easypr/core/plate_detect.cpp new file mode 100644 index 00000000..26d1ad9c --- /dev/null +++ b/src/main/resources/easypr/core/plate_detect.cpp @@ -0,0 +1,77 @@ +#include "easypr/core/plate_detect.h" +#include "easypr/util/util.h" +#include "easypr/core/core_func.h" +#include "easypr/config.h" + +namespace easypr { + + CPlateDetect::CPlateDetect() { + m_plateLocate = new CPlateLocate(); + m_maxPlates = 3; + m_type = 0; + m_showDetect = false; + } + + CPlateDetect::~CPlateDetect() { SAFE_RELEASE(m_plateLocate); } + + int CPlateDetect::plateDetect(Mat src, std::vector &resultVec, int type, + bool showDetectArea, int img_index) { + std::vector sobel_Plates; + sobel_Plates.reserve(16); + std::vector color_Plates; + color_Plates.reserve(16); + std::vector mser_Plates; + mser_Plates.reserve(16); + std::vector all_result_Plates; + all_result_Plates.reserve(64); +#pragma omp parallel sections + { +#pragma omp section + { + if (!type || type & PR_DETECT_SOBEL) { + m_plateLocate->plateSobelLocate(src, sobel_Plates, img_index); + } + } +#pragma omp section + { + if (!type || type & PR_DETECT_COLOR) { + m_plateLocate->plateColorLocate(src, color_Plates, img_index); + } + } +#pragma omp section + { + if (!type || type & PR_DETECT_CMSER) { + m_plateLocate->plateMserLocate(src, mser_Plates, img_index); + } + } + } + for (auto plate : sobel_Plates) { + plate.setPlateLocateType(SOBEL); + all_result_Plates.push_back(plate); + } + for (auto plate : color_Plates) { + plate.setPlateLocateType(COLOR); + all_result_Plates.push_back(plate); + } + for (auto plate : mser_Plates) { + plate.setPlateLocateType(CMSER); + all_result_Plates.push_back(plate); + } + // use nms to judge plate + PlateJudge::instance()->plateJudgeUsingNMS(all_result_Plates, resultVec, m_maxPlates); + + if (0) + showDectectResults(src, resultVec, m_maxPlates); + return 0; + } + + int CPlateDetect::plateDetect(Mat src, std::vector &resultVec, int img_index) { + int result = plateDetect(src, resultVec, m_type, false, img_index); + return result; + } + + void CPlateDetect::LoadSVM(std::string path) { + PlateJudge::instance()->LoadModel(path); + } + +} \ No newline at end of file diff --git a/src/main/resources/easypr/core/plate_judge.cpp b/src/main/resources/easypr/core/plate_judge.cpp new file mode 100644 index 00000000..4dfa0328 --- /dev/null +++ b/src/main/resources/easypr/core/plate_judge.cpp @@ -0,0 +1,193 @@ +#include "easypr/core/plate_judge.h" +#include "easypr/config.h" +#include "easypr/core/core_func.h" +#include "easypr/core/params.h" + +namespace easypr { + + PlateJudge* PlateJudge::instance_ = nullptr; + + PlateJudge* PlateJudge::instance() { + if (!instance_) { + instance_ = new PlateJudge; + } + return instance_; + } + + PlateJudge::PlateJudge() { + bool useLBP = false; + if (useLBP) { + LOAD_SVM_MODEL(svm_, kLBPSvmPath); + extractFeature = getLBPFeatures; + } + else { + LOAD_SVM_MODEL(svm_, kHistSvmPath); + extractFeature = getHistomPlusColoFeatures; + } + } + + void PlateJudge::LoadModel(std::string path) { + if (path != std::string(kDefaultSvmPath)) { + if (!svm_->empty()) + svm_->clear(); + LOAD_SVM_MODEL(svm_, path); + } + } + + // set the score of plate + // 0 is plate, -1 is not. + int PlateJudge::plateSetScore(CPlate& plate) { + Mat features; + extractFeature(plate.getPlateMat(), features); + float score = svm_->predict(features, noArray(), cv::ml::StatModel::Flags::RAW_OUTPUT); + //std::cout << "score:" << score << std::endl; + if (0) { + imshow("plate", plate.getPlateMat()); + waitKey(0); + destroyWindow("plate"); + } + // score is the distance of margin,below zero is plate, up is not + // when score is below zero, the samll the value, the more possibliy to be a plate. + plate.setPlateScore(score); + if (score < 0.5) return 0; + else return -1; + } + + int PlateJudge::plateJudge(const Mat& plateMat) { + CPlate plate; + plate.setPlateMat(plateMat); + return plateSetScore(plate); + } + + int PlateJudge::plateJudge(const std::vector &inVec, + std::vector &resultVec) { + int num = inVec.size(); + for (int j = 0; j < num; j++) { + Mat inMat = inVec[j]; + + int response = -1; + response = plateJudge(inMat); + + if (response == 0) resultVec.push_back(inMat); + } + return 0; + } + + int PlateJudge::plateJudge(const std::vector &inVec, + std::vector &resultVec) { + int num = inVec.size(); + for (int j = 0; j < num; j++) { + CPlate inPlate = inVec[j]; + Mat inMat = inPlate.getPlateMat(); + int response = -1; + response = plateJudge(inMat); + + if (response == 0) + resultVec.push_back(inPlate); + else { + int w = inMat.cols; + int h = inMat.rows; + Mat tmpmat = inMat(Rect_(w * 0.05, h * 0.1, w * 0.9, h * 0.8)); + Mat tmpDes = inMat.clone(); + resize(tmpmat, tmpDes, Size(inMat.size())); + + response = plateJudge(tmpDes); + if (response == 0) resultVec.push_back(inPlate); + } + } + return 0; + } + + // non-maximum suppression + void NMS(std::vector &inVec, std::vector &resultVec, double overlap) { + std::sort(inVec.begin(), inVec.end()); + std::vector::iterator it = inVec.begin(); + for (; it != inVec.end(); ++it) { + CPlate plateSrc = *it; + //std::cout << "plateScore:" << plateSrc.getPlateScore() << std::endl; + Rect rectSrc = plateSrc.getPlatePos().boundingRect(); + std::vector::iterator itc = it + 1; + for (; itc != inVec.end();) { + CPlate plateComp = *itc; + Rect rectComp = plateComp.getPlatePos().boundingRect(); + float iou = computeIOU(rectSrc, rectComp); + if (iou > overlap) { + itc = inVec.erase(itc); + } + else { + ++itc; + } + } + } + resultVec = inVec; + } + + // judge plate using nms + int PlateJudge::plateJudgeUsingNMS(const std::vector &inVec, std::vector &resultVec, int maxPlates) { + std::vector plateVec; + int num = inVec.size(); + bool useCascadeJudge = true; + + for (int j = 0; j < num; j++) { + CPlate plate = inVec[j]; + Mat inMat = plate.getPlateMat(); + int result = plateSetScore(plate); + if (0 == result) { + if (0) { + imshow("inMat", inMat); + waitKey(0); + destroyWindow("inMat"); + } + + if (plate.getPlateLocateType() == CMSER) { + int w = inMat.cols; + int h = inMat.rows; + Mat tmpmat = inMat(Rect_(w * 0.05, h * 0.1, w * 0.9, h * 0.8)); + Mat tmpDes = inMat.clone(); + resize(tmpmat, tmpDes, Size(inMat.size())); + plate.setPlateMat(tmpDes); + if (useCascadeJudge) { + int resultCascade = plateSetScore(plate); + if (plate.getPlateLocateType() != CMSER) + plate.setPlateMat(inMat); + if (resultCascade == 0) { + if (0) { + imshow("tmpDes", tmpDes); + waitKey(0); + destroyWindow("tmpDes"); + } + plateVec.push_back(plate); + } + } + else + plateVec.push_back(plate); + } + else + plateVec.push_back(plate); + } + } + + std::vector reDupPlateVec; + double overlap = 0.5; + // double overlap = CParams::instance()->getParam1f(); + // use NMS to get the result plates + NMS(plateVec, reDupPlateVec, overlap); + // sort the plates due to their scores + std::sort(reDupPlateVec.begin(), reDupPlateVec.end()); + // output the plate judge plates + std::vector::iterator it = reDupPlateVec.begin(); + int count = 0; + for (; it != reDupPlateVec.end(); ++it) { + resultVec.push_back(*it); + if (0) { + imshow("plateMat", it->getPlateMat()); + waitKey(0); + destroyWindow("plateMat"); + } + count++; + if (count >= maxPlates) + break; + } + return 0; + } +} diff --git a/src/main/resources/easypr/core/plate_locate.cpp b/src/main/resources/easypr/core/plate_locate.cpp new file mode 100644 index 00000000..90b87191 --- /dev/null +++ b/src/main/resources/easypr/core/plate_locate.cpp @@ -0,0 +1,999 @@ +#include "easypr/core/plate_locate.h" +#include "easypr/core/core_func.h" +#include "easypr/util/util.h" +#include "easypr/core/params.h" + +using namespace std; + +namespace easypr { + +const float DEFAULT_ERROR = 0.9f; // 0.6 +const float DEFAULT_ASPECT = 3.75f; // 3.75 + +CPlateLocate::CPlateLocate() { + m_GaussianBlurSize = DEFAULT_GAUSSIANBLUR_SIZE; + m_MorphSizeWidth = DEFAULT_MORPH_SIZE_WIDTH; + m_MorphSizeHeight = DEFAULT_MORPH_SIZE_HEIGHT; + + m_error = DEFAULT_ERROR; + m_aspect = DEFAULT_ASPECT; + m_verifyMin = DEFAULT_VERIFY_MIN; + m_verifyMax = DEFAULT_VERIFY_MAX; + + m_angle = DEFAULT_ANGLE; + + m_debug = DEFAULT_DEBUG; +} + +void CPlateLocate::setLifemode(bool param) { + if (param) { + setGaussianBlurSize(5); + setMorphSizeWidth(10); + setMorphSizeHeight(3); + setVerifyError(0.75); + setVerifyAspect(4.0); + setVerifyMin(1); + setVerifyMax(200); + } else { + setGaussianBlurSize(DEFAULT_GAUSSIANBLUR_SIZE); + setMorphSizeWidth(DEFAULT_MORPH_SIZE_WIDTH); + setMorphSizeHeight(DEFAULT_MORPH_SIZE_HEIGHT); + setVerifyError(DEFAULT_ERROR); + setVerifyAspect(DEFAULT_ASPECT); + setVerifyMin(DEFAULT_VERIFY_MIN); + setVerifyMax(DEFAULT_VERIFY_MAX); + } +} + +bool CPlateLocate::verifySizes(RotatedRect mr) { + float error = m_error; + // Spain car plate size: 52x11 aspect 4,7272 + // China car plate size: 440mm*140mm,aspect 3.142857 + + // Real car plate size: 136 * 32, aspect 4 + float aspect = m_aspect; + + // Set a min and max area. All other patchs are discarded + // int min= 1*aspect*1; // minimum area + // int max= 2000*aspect*2000; // maximum area + int min = 34 * 8 * m_verifyMin; // minimum area + int max = 34 * 8 * m_verifyMax; // maximum area + + // Get only patchs that match to a respect ratio. + float rmin = aspect - aspect * error; + float rmax = aspect + aspect * error; + + float area = mr.size.height * mr.size.width; + float r = (float) mr.size.width / (float) mr.size.height; + if (r < 1) r = (float) mr.size.height / (float) mr.size.width; + + // cout << "area:" << area << endl; + // cout << "r:" << r << endl; + + if ((area < min || area > max) || (r < rmin || r > rmax)) + return false; + else + return true; +} + +//! mser search method +int CPlateLocate::mserSearch(const Mat &src, vector &out, + vector>& out_plateVec, bool usePlateMser, vector>& out_plateRRect, + int img_index, bool showDebug) { + vector match_grey; + + vector plateVec_blue; + plateVec_blue.reserve(16); + vector plateRRect_blue; + plateRRect_blue.reserve(16); + + vector plateVec_yellow; + plateVec_yellow.reserve(16); + + vector plateRRect_yellow; + plateRRect_yellow.reserve(16); + + mserCharMatch(src, match_grey, plateVec_blue, plateVec_yellow, usePlateMser, plateRRect_blue, plateRRect_yellow, img_index, showDebug); + + out_plateVec.push_back(plateVec_blue); + out_plateVec.push_back(plateVec_yellow); + + out_plateRRect.push_back(plateRRect_blue); + out_plateRRect.push_back(plateRRect_yellow); + + out = match_grey; + + return 0; +} + + +int CPlateLocate::colorSearch(const Mat &src, const Color r, Mat &out, + vector &outRects) { + Mat match_grey; + + // width is important to the final results; + const int color_morph_width = 10; + const int color_morph_height = 2; + + colorMatch(src, match_grey, r, false); + SHOW_IMAGE(match_grey, 0); + + Mat src_threshold; + threshold(match_grey, src_threshold, 0, 255, + CV_THRESH_OTSU + CV_THRESH_BINARY); + + Mat element = getStructuringElement( + MORPH_RECT, Size(color_morph_width, color_morph_height)); + morphologyEx(src_threshold, src_threshold, MORPH_CLOSE, element); + + //if (m_debug) { + // utils::imwrite("resources/image/tmp/color.jpg", src_threshold); + //} + + src_threshold.copyTo(out); + + + vector> contours; + + findContours(src_threshold, + contours, // a vector of contours + CV_RETR_EXTERNAL, + CV_CHAIN_APPROX_NONE); // all pixels of each contours + + vector>::iterator itc = contours.begin(); + while (itc != contours.end()) { + RotatedRect mr = minAreaRect(Mat(*itc)); + + if (!verifySizes(mr)) + itc = contours.erase(itc); + else { + ++itc; + outRects.push_back(mr); + } + } + + return 0; +} + + +int CPlateLocate::sobelFrtSearch(const Mat &src, + vector> &outRects) { + Mat src_threshold; + + sobelOper(src, src_threshold, m_GaussianBlurSize, m_MorphSizeWidth, + m_MorphSizeHeight); + + vector> contours; + findContours(src_threshold, + contours, // a vector of contours + CV_RETR_EXTERNAL, + CV_CHAIN_APPROX_NONE); // all pixels of each contours + + vector>::iterator itc = contours.begin(); + + vector first_rects; + + while (itc != contours.end()) { + RotatedRect mr = minAreaRect(Mat(*itc)); + + + if (verifySizes(mr)) { + first_rects.push_back(mr); + + float area = mr.size.height * mr.size.width; + float r = (float) mr.size.width / (float) mr.size.height; + if (r < 1) r = (float) mr.size.height / (float) mr.size.width; + } + + ++itc; + } + + for (size_t i = 0; i < first_rects.size(); i++) { + RotatedRect roi_rect = first_rects[i]; + + Rect_ safeBoundRect; + if (!calcSafeRect(roi_rect, src, safeBoundRect)) continue; + + outRects.push_back(safeBoundRect); + } + return 0; +} + + +int CPlateLocate::sobelSecSearchPart(Mat &bound, Point2f refpoint, + vector &outRects) { + Mat bound_threshold; + + sobelOperT(bound, bound_threshold, 3, 6, 2); + + Mat tempBoundThread = bound_threshold.clone(); + + clearLiuDingOnly(tempBoundThread); + + int posLeft = 0, posRight = 0; + if (bFindLeftRightBound(tempBoundThread, posLeft, posRight)) { + + // find left and right bounds to repair + + if (posRight != 0 && posLeft != 0 && posLeft < posRight) { + int posY = int(bound_threshold.rows * 0.5); + for (int i = posLeft + (int) (bound_threshold.rows * 0.1); + i < posRight - 4; i++) { + bound_threshold.data[posY * bound_threshold.cols + i] = 255; + } + } + + utils::imwrite("resources/image/tmp/repaireimg1.jpg", bound_threshold); + + // remove the left and right boundaries + + for (int i = 0; i < bound_threshold.rows; i++) { + bound_threshold.data[i * bound_threshold.cols + posLeft] = 0; + bound_threshold.data[i * bound_threshold.cols + posRight] = 0; + } + utils::imwrite("resources/image/tmp/repaireimg2.jpg", bound_threshold); + } + + vector> contours; + findContours(bound_threshold, + contours, // a vector of contours + CV_RETR_EXTERNAL, + CV_CHAIN_APPROX_NONE); // all pixels of each contours + + vector>::iterator itc = contours.begin(); + + vector second_rects; + while (itc != contours.end()) { + RotatedRect mr = minAreaRect(Mat(*itc)); + second_rects.push_back(mr); + ++itc; + } + + for (size_t i = 0; i < second_rects.size(); i++) { + RotatedRect roi = second_rects[i]; + if (verifySizes(roi)) { + Point2f refcenter = roi.center + refpoint; + Size2f size = roi.size; + float angle = roi.angle; + + RotatedRect refroi(refcenter, size, angle); + outRects.push_back(refroi); + } + } + + return 0; +} + + +int CPlateLocate::sobelSecSearch(Mat &bound, Point2f refpoint, + vector &outRects) { + Mat bound_threshold; + + + sobelOper(bound, bound_threshold, 3, 10, 3); + + utils::imwrite("resources/image/tmp/sobelSecSearch.jpg", bound_threshold); + + vector> contours; + findContours(bound_threshold, + contours, // a vector of contours + CV_RETR_EXTERNAL, + CV_CHAIN_APPROX_NONE); // all pixels of each contours + + vector>::iterator itc = contours.begin(); + + vector second_rects; + while (itc != contours.end()) { + RotatedRect mr = minAreaRect(Mat(*itc)); + second_rects.push_back(mr); + ++itc; + } + + for (size_t i = 0; i < second_rects.size(); i++) { + RotatedRect roi = second_rects[i]; + if (verifySizes(roi)) { + Point2f refcenter = roi.center + refpoint; + Size2f size = roi.size; + float angle = roi.angle; + + RotatedRect refroi(refcenter, size, angle); + outRects.push_back(refroi); + } + } + + return 0; +} + + +int CPlateLocate::sobelOper(const Mat &in, Mat &out, int blurSize, int morphW, + int morphH) { + Mat mat_blur; + mat_blur = in.clone(); + GaussianBlur(in, mat_blur, Size(blurSize, blurSize), 0, 0, BORDER_DEFAULT); + + Mat mat_gray; + if (mat_blur.channels() == 3) + cvtColor(mat_blur, mat_gray, CV_RGB2GRAY); + else + mat_gray = mat_blur; + + int scale = SOBEL_SCALE; + int delta = SOBEL_DELTA; + int ddepth = SOBEL_DDEPTH; + + Mat grad_x, grad_y; + Mat abs_grad_x, abs_grad_y; + + + Sobel(mat_gray, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT); + convertScaleAbs(grad_x, abs_grad_x); + + Mat grad; + addWeighted(abs_grad_x, SOBEL_X_WEIGHT, 0, 0, 0, grad); + + Mat mat_threshold; + double otsu_thresh_val = + threshold(grad, mat_threshold, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY); + + + Mat element = getStructuringElement(MORPH_RECT, Size(morphW, morphH)); + morphologyEx(mat_threshold, mat_threshold, MORPH_CLOSE, element); + + out = mat_threshold; + + return 0; +} + +void deleteNotArea(Mat &inmat, Color color = UNKNOWN) { + Mat input_grey; + cvtColor(inmat, input_grey, CV_BGR2GRAY); + + int w = inmat.cols; + int h = inmat.rows; + + Mat tmpMat = inmat(Rect_(w * 0.15, h * 0.1, w * 0.7, h * 0.7)); + + Color plateType; + if (UNKNOWN == color) { + plateType = getPlateType(tmpMat, true); + } + else { + plateType = color; + } + + Mat img_threshold; + + if (BLUE == plateType) { + img_threshold = input_grey.clone(); + Mat tmp = input_grey(Rect_(w * 0.15, h * 0.15, w * 0.7, h * 0.7)); + int threadHoldV = ThresholdOtsu(tmp); + + threshold(input_grey, img_threshold, threadHoldV, 255, CV_THRESH_BINARY); + // threshold(input_grey, img_threshold, 5, 255, CV_THRESH_OTSU + + // CV_THRESH_BINARY); + + utils::imwrite("resources/image/tmp/inputgray2.jpg", img_threshold); + + } else if (YELLOW == plateType) { + img_threshold = input_grey.clone(); + Mat tmp = input_grey(Rect_(w * 0.1, h * 0.1, w * 0.8, h * 0.8)); + int threadHoldV = ThresholdOtsu(tmp); + + threshold(input_grey, img_threshold, threadHoldV, 255, + CV_THRESH_BINARY_INV); + + utils::imwrite("resources/image/tmp/inputgray2.jpg", img_threshold); + + // threshold(input_grey, img_threshold, 10, 255, CV_THRESH_OTSU + + // CV_THRESH_BINARY_INV); + } else + threshold(input_grey, img_threshold, 10, 255, + CV_THRESH_OTSU + CV_THRESH_BINARY); + + //img_threshold = input_grey.clone(); + //spatial_ostu(img_threshold, 8, 2, plateType); + + int posLeft = 0; + int posRight = 0; + + int top = 0; + int bottom = img_threshold.rows - 1; + clearLiuDing(img_threshold, top, bottom); + + if (0) { + imshow("inmat", inmat); + waitKey(0); + destroyWindow("inmat"); + } + + if (bFindLeftRightBound1(img_threshold, posLeft, posRight)) { + inmat = inmat(Rect(posLeft, top, w - posLeft, bottom - top)); + if (0) { + imshow("inmat", inmat); + waitKey(0); + destroyWindow("inmat"); + } + } +} + + +int CPlateLocate::deskew(const Mat &src, const Mat &src_b, + vector &inRects, + vector &outPlates, bool useDeteleArea, Color color) { + Mat mat_debug; + src.copyTo(mat_debug); + + for (size_t i = 0; i < inRects.size(); i++) { + RotatedRect roi_rect = inRects[i]; + + float r = (float) roi_rect.size.width / (float) roi_rect.size.height; + float roi_angle = roi_rect.angle; + + Size roi_rect_size = roi_rect.size; + if (r < 1) { + roi_angle = 90 + roi_angle; + swap(roi_rect_size.width, roi_rect_size.height); + } + + if (m_debug) { + Point2f rect_points[4]; + roi_rect.points(rect_points); + for (int j = 0; j < 4; j++) + line(mat_debug, rect_points[j], rect_points[(j + 1) % 4], + Scalar(0, 255, 255), 1, 8); + } + + // changed + // rotation = 90 - abs(roi_angle); + // rotation < m_angel; + + // m_angle=60 + if (roi_angle - m_angle < 0 && roi_angle + m_angle > 0) { + Rect_ safeBoundRect; + bool isFormRect = calcSafeRect(roi_rect, src, safeBoundRect); + if (!isFormRect) continue; + + Mat bound_mat = src(safeBoundRect); + Mat bound_mat_b = src_b(safeBoundRect); + + if (0) { + imshow("bound_mat_b", bound_mat_b); + waitKey(0); + destroyWindow("bound_mat_b"); + } + + Point2f roi_ref_center = roi_rect.center - safeBoundRect.tl(); + + Mat deskew_mat; + if ((roi_angle - 5 < 0 && roi_angle + 5 > 0) || 90.0 == roi_angle || + -90.0 == roi_angle) { + deskew_mat = bound_mat; + } else { + Mat rotated_mat; + Mat rotated_mat_b; + + if (!rotation(bound_mat, rotated_mat, roi_rect_size, roi_ref_center, roi_angle)) + continue; + + if (!rotation(bound_mat_b, rotated_mat_b, roi_rect_size, roi_ref_center, roi_angle)) + continue; + + // we need affine for rotatioed image + double roi_slope = 0; + // imshow("1roated_mat",rotated_mat); + // imshow("rotated_mat_b",rotated_mat_b); + if (isdeflection(rotated_mat_b, roi_angle, roi_slope)) { + affine(rotated_mat, deskew_mat, roi_slope); + } else + deskew_mat = rotated_mat; + } + + Mat plate_mat; + plate_mat.create(HEIGHT, WIDTH, TYPE); + + // haitungaga add,affect 25% to full recognition. + if (useDeteleArea) + deleteNotArea(deskew_mat, color); + + if (deskew_mat.cols * 1.0 / deskew_mat.rows > 2.3 && deskew_mat.cols * 1.0 / deskew_mat.rows < 6) { + if (deskew_mat.cols >= WIDTH || deskew_mat.rows >= HEIGHT) + resize(deskew_mat, plate_mat, plate_mat.size(), 0, 0, INTER_AREA); + else + resize(deskew_mat, plate_mat, plate_mat.size(), 0, 0, INTER_CUBIC); + + CPlate plate; + plate.setPlatePos(roi_rect); + plate.setPlateMat(plate_mat); + if (color != UNKNOWN) plate.setPlateColor(color); + outPlates.push_back(plate); + } + } + } + return 0; +} + + +bool CPlateLocate::rotation(Mat &in, Mat &out, const Size rect_size, + const Point2f center, const double angle) { + if (0) { + imshow("in", in); + waitKey(0); + destroyWindow("in"); + } + + Mat in_large; + in_large.create(int(in.rows * 1.5), int(in.cols * 1.5), in.type()); + + float x = in_large.cols / 2 - center.x > 0 ? in_large.cols / 2 - center.x : 0; + float y = in_large.rows / 2 - center.y > 0 ? in_large.rows / 2 - center.y : 0; + + float width = x + in.cols < in_large.cols ? in.cols : in_large.cols - x; + float height = y + in.rows < in_large.rows ? in.rows : in_large.rows - y; + + /*assert(width == in.cols); + assert(height == in.rows);*/ + + if (width != in.cols || height != in.rows) return false; + + Mat imageRoi = in_large(Rect_(x, y, width, height)); + addWeighted(imageRoi, 0, in, 1, 0, imageRoi); + + Point2f center_diff(in.cols / 2.f, in.rows / 2.f); + Point2f new_center(in_large.cols / 2.f, in_large.rows / 2.f); + + Mat rot_mat = getRotationMatrix2D(new_center, angle, 1); + + /*imshow("in_copy", in_large); + waitKey(0);*/ + + Mat mat_rotated; + warpAffine(in_large, mat_rotated, rot_mat, Size(in_large.cols, in_large.rows), + CV_INTER_CUBIC); + + /*imshow("mat_rotated", mat_rotated); + waitKey(0);*/ + + Mat img_crop; + getRectSubPix(mat_rotated, Size(rect_size.width, rect_size.height), + new_center, img_crop); + + out = img_crop; + + if (0) { + imshow("out", out); + waitKey(0); + destroyWindow("out"); + } + + /*imshow("img_crop", img_crop); + waitKey(0);*/ + + return true; +} + +bool CPlateLocate::isdeflection(const Mat &in, const double angle, + double &slope) { /*imshow("in",in); + waitKey(0);*/ + if (0) { + imshow("in", in); + waitKey(0); + destroyWindow("in"); + } + + int nRows = in.rows; + int nCols = in.cols; + + assert(in.channels() == 1); + + int comp_index[3]; + int len[3]; + + comp_index[0] = nRows / 4; + comp_index[1] = nRows / 4 * 2; + comp_index[2] = nRows / 4 * 3; + + const uchar* p; + + for (int i = 0; i < 3; i++) { + int index = comp_index[i]; + p = in.ptr(index); + + int j = 0; + int value = 0; + while (0 == value && j < nCols) value = int(p[j++]); + + len[i] = j; + } + + // cout << "len[0]:" << len[0] << endl; + // cout << "len[1]:" << len[1] << endl; + // cout << "len[2]:" << len[2] << endl; + + // len[0]/len[1]/len[2] are used to calc the slope + + double maxlen = max(len[2], len[0]); + double minlen = min(len[2], len[0]); + double difflen = abs(len[2] - len[0]); + + double PI = 3.14159265; + + double g = tan(angle * PI / 180.0); + + if (maxlen - len[1] > nCols / 32 || len[1] - minlen > nCols / 32) { + + double slope_can_1 = + double(len[2] - len[0]) / double(comp_index[1]); + double slope_can_2 = double(len[1] - len[0]) / double(comp_index[0]); + double slope_can_3 = double(len[2] - len[1]) / double(comp_index[0]); + // cout<<"angle:"<=0) + slope = abs(slope_can_1 - g) <= abs(slope_can_2 - g) ? slope_can_1 + : slope_can_2; + // cout << "slope:" << slope << endl; + return true; + } else { + slope = 0; + } + + return false; +} + + +void CPlateLocate::affine(const Mat &in, Mat &out, const double slope) { + // imshow("in", in); + // waitKey(0); + + Point2f dstTri[3]; + Point2f plTri[3]; + + float height = (float) in.rows; + float width = (float) in.cols; + float xiff = (float) abs(slope) * height; + + if (slope > 0) { + + // right, new position is xiff/2 + + plTri[0] = Point2f(0, 0); + plTri[1] = Point2f(width - xiff - 1, 0); + plTri[2] = Point2f(0 + xiff, height - 1); + + dstTri[0] = Point2f(xiff / 2, 0); + dstTri[1] = Point2f(width - 1 - xiff / 2, 0); + dstTri[2] = Point2f(xiff / 2, height - 1); + } else { + + // left, new position is -xiff/2 + + plTri[0] = Point2f(0 + xiff, 0); + plTri[1] = Point2f(width - 1, 0); + plTri[2] = Point2f(0, height - 1); + + dstTri[0] = Point2f(xiff / 2, 0); + dstTri[1] = Point2f(width - 1 - xiff + xiff / 2, 0); + dstTri[2] = Point2f(xiff / 2, height - 1); + } + + Mat warp_mat = getAffineTransform(plTri, dstTri); + + Mat affine_mat; + affine_mat.create((int) height, (int) width, TYPE); + + if (in.rows > HEIGHT || in.cols > WIDTH) + + warpAffine(in, affine_mat, warp_mat, affine_mat.size(), + CV_INTER_AREA); + else + warpAffine(in, affine_mat, warp_mat, affine_mat.size(), CV_INTER_CUBIC); + + out = affine_mat; +} + +int CPlateLocate::plateColorLocate(Mat src, vector &candPlates, + int index) { + vector rects_color_blue; + rects_color_blue.reserve(64); + vector rects_color_yellow; + rects_color_yellow.reserve(64); + + vector plates_blue; + plates_blue.reserve(64); + vector plates_yellow; + plates_yellow.reserve(64); + + Mat src_clone = src.clone(); + + Mat src_b_blue; + Mat src_b_yellow; +#pragma omp parallel sections + { +#pragma omp section + { + colorSearch(src, BLUE, src_b_blue, rects_color_blue); + deskew(src, src_b_blue, rects_color_blue, plates_blue, true, BLUE); + } +#pragma omp section + { + colorSearch(src_clone, YELLOW, src_b_yellow, rects_color_yellow); + deskew(src_clone, src_b_yellow, rects_color_yellow, plates_yellow, true, YELLOW); + } + } + + candPlates.insert(candPlates.end(), plates_blue.begin(), plates_blue.end()); + candPlates.insert(candPlates.end(), plates_yellow.begin(), plates_yellow.end()); + + return 0; +} + + +//! MSER plate locate +int CPlateLocate::plateMserLocate(Mat src, vector &candPlates, int img_index) { + std::vector channelImages; + std::vector flags; + flags.push_back(BLUE); + flags.push_back(YELLOW); + + bool usePlateMser = false; + int scale_size = 1000; + //int scale_size = CParams::instance()->getParam1i(); + double scale_ratio = 1; + + // only conside blue plate + if (1) { + Mat grayImage; + cvtColor(src, grayImage, COLOR_BGR2GRAY); + channelImages.push_back(grayImage); + } + + for (size_t i = 0; i < channelImages.size(); ++i) { + vector> plateRRectsVec; + vector> platesVec; + vector src_b_vec; + + Mat channelImage = channelImages.at(i); + Mat image = scaleImage(channelImage, Size(scale_size, scale_size), scale_ratio); + + // vector rects; + mserSearch(image, src_b_vec, platesVec, usePlateMser, plateRRectsVec, img_index, false); + + for (size_t j = 0; j < flags.size(); j++) { + vector& plates = platesVec.at(j); + Mat& src_b = src_b_vec.at(j); + Color color = flags.at(j); + + vector rects_mser; + rects_mser.reserve(64); + std::vector deskewPlate; + deskewPlate.reserve(64); + std::vector mserPlate; + mserPlate.reserve(64); + + // deskew for rotation and slope image + for (auto plate : plates) { + RotatedRect rrect = plate.getPlatePos(); + RotatedRect scaleRect = scaleBackRRect(rrect, (float)scale_ratio); + plate.setPlatePos(scaleRect); + plate.setPlateColor(color); + + rects_mser.push_back(scaleRect); + mserPlate.push_back(plate); + } + + Mat resize_src_b; + resize(src_b, resize_src_b, Size(channelImage.cols, channelImage.rows)); + + deskew(src, resize_src_b, rects_mser, deskewPlate, false, color); + + for (auto dplate : deskewPlate) { + RotatedRect drect = dplate.getPlatePos(); + Mat dmat = dplate.getPlateMat(); + + for (auto splate : mserPlate) { + RotatedRect srect = splate.getPlatePos(); + float iou = 0.f; + bool isSimilar = computeIOU(drect, srect, src.cols, src.rows, 0.95f, iou); + if (isSimilar) { + splate.setPlateMat(dmat); + candPlates.push_back(splate); + break; + } + } + } + } + } + + if (0) { + imshow("src", src); + waitKey(0); + destroyWindow("src"); + } + + return 0; +} + +int CPlateLocate::sobelOperT(const Mat &in, Mat &out, int blurSize, int morphW, + int morphH) { + Mat mat_blur; + mat_blur = in.clone(); + GaussianBlur(in, mat_blur, Size(blurSize, blurSize), 0, 0, BORDER_DEFAULT); + + Mat mat_gray; + if (mat_blur.channels() == 3) + cvtColor(mat_blur, mat_gray, CV_BGR2GRAY); + else + mat_gray = mat_blur; + + utils::imwrite("resources/image/tmp/grayblure.jpg", mat_gray); + + // equalizeHist(mat_gray, mat_gray); + + int scale = SOBEL_SCALE; + int delta = SOBEL_DELTA; + int ddepth = SOBEL_DDEPTH; + + Mat grad_x, grad_y; + Mat abs_grad_x, abs_grad_y; + + Sobel(mat_gray, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT); + convertScaleAbs(grad_x, abs_grad_x); + + Mat grad; + addWeighted(abs_grad_x, 1, 0, 0, 0, grad); + + utils::imwrite("resources/image/tmp/graygrad.jpg", grad); + + Mat mat_threshold; + double otsu_thresh_val = + threshold(grad, mat_threshold, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY); + + utils::imwrite("resources/image/tmp/grayBINARY.jpg", mat_threshold); + + Mat element = getStructuringElement(MORPH_RECT, Size(morphW, morphH)); + morphologyEx(mat_threshold, mat_threshold, MORPH_CLOSE, element); + + utils::imwrite("resources/image/tmp/phologyEx.jpg", mat_threshold); + + out = mat_threshold; + + return 0; +} + +int CPlateLocate::plateSobelLocate(Mat src, vector &candPlates, + int index) { + vector rects_sobel_all; + rects_sobel_all.reserve(256); + + vector plates; + plates.reserve(32); + + vector> bound_rects; + bound_rects.reserve(256); + + sobelFrtSearch(src, bound_rects); + + vector> bound_rects_part; + bound_rects_part.reserve(256); + + // enlarge area + for (size_t i = 0; i < bound_rects.size(); i++) { + float fRatio = bound_rects[i].width * 1.0f / bound_rects[i].height; + if (fRatio < 3.0 && fRatio > 1.0 && bound_rects[i].height < 120) { + Rect_ itemRect = bound_rects[i]; + + itemRect.x = itemRect.x - itemRect.height * (4 - fRatio); + if (itemRect.x < 0) { + itemRect.x = 0; + } + itemRect.width = itemRect.width + itemRect.height * 2 * (4 - fRatio); + if (itemRect.width + itemRect.x >= src.cols) { + itemRect.width = src.cols - itemRect.x; + } + + itemRect.y = itemRect.y - itemRect.height * 0.08f; + itemRect.height = itemRect.height * 1.16f; + + bound_rects_part.push_back(itemRect); + } + } + + // second processing to split one +#pragma omp parallel for + for (int i = 0; i < (int)bound_rects_part.size(); i++) { + Rect_ bound_rect = bound_rects_part[i]; + Point2f refpoint(bound_rect.x, bound_rect.y); + + float x = bound_rect.x > 0 ? bound_rect.x : 0; + float y = bound_rect.y > 0 ? bound_rect.y : 0; + + float width = + x + bound_rect.width < src.cols ? bound_rect.width : src.cols - x; + float height = + y + bound_rect.height < src.rows ? bound_rect.height : src.rows - y; + + Rect_ safe_bound_rect(x, y, width, height); + Mat bound_mat = src(safe_bound_rect); + + vector rects_sobel; + rects_sobel.reserve(128); + sobelSecSearchPart(bound_mat, refpoint, rects_sobel); + +#pragma omp critical + { + rects_sobel_all.insert(rects_sobel_all.end(), rects_sobel.begin(), rects_sobel.end()); + } + } + +#pragma omp parallel for + for (int i = 0; i < (int)bound_rects.size(); i++) { + Rect_ bound_rect = bound_rects[i]; + Point2f refpoint(bound_rect.x, bound_rect.y); + + float x = bound_rect.x > 0 ? bound_rect.x : 0; + float y = bound_rect.y > 0 ? bound_rect.y : 0; + + float width = + x + bound_rect.width < src.cols ? bound_rect.width : src.cols - x; + float height = + y + bound_rect.height < src.rows ? bound_rect.height : src.rows - y; + + Rect_ safe_bound_rect(x, y, width, height); + Mat bound_mat = src(safe_bound_rect); + + vector rects_sobel; + rects_sobel.reserve(128); + sobelSecSearch(bound_mat, refpoint, rects_sobel); + +#pragma omp critical + { + rects_sobel_all.insert(rects_sobel_all.end(), rects_sobel.begin(), rects_sobel.end()); + } + } + + Mat src_b; + sobelOper(src, src_b, 3, 10, 3); + + deskew(src, src_b, rects_sobel_all, plates); + + //for (size_t i = 0; i < plates.size(); i++) + // candPlates.push_back(plates[i]); + + candPlates.insert(candPlates.end(), plates.begin(), plates.end()); + + return 0; +} + + +int CPlateLocate::plateLocate(Mat src, vector &resultVec, int index) { + vector all_result_Plates; + + plateColorLocate(src, all_result_Plates, index); + plateSobelLocate(src, all_result_Plates, index); + plateMserLocate(src, all_result_Plates, index); + + for (size_t i = 0; i < all_result_Plates.size(); i++) { + CPlate plate = all_result_Plates[i]; + resultVec.push_back(plate.getPlateMat()); + } + + return 0; +} + +int CPlateLocate::plateLocate(Mat src, vector &resultVec, int index) { + vector all_result_Plates; + + plateColorLocate(src, all_result_Plates, index); + plateSobelLocate(src, all_result_Plates, index); + plateMserLocate(src, all_result_Plates, index); + + for (size_t i = 0; i < all_result_Plates.size(); i++) { + resultVec.push_back(all_result_Plates[i]); + } + + return 0; +} + +} diff --git a/src/main/resources/easypr/core/plate_recognize.cpp b/src/main/resources/easypr/core/plate_recognize.cpp new file mode 100644 index 00000000..a351709d --- /dev/null +++ b/src/main/resources/easypr/core/plate_recognize.cpp @@ -0,0 +1,105 @@ +#include "easypr/core/plate_recognize.h" +#include "easypr/config.h" +#include "thirdparty/textDetect/erfilter.hpp" + +namespace easypr { + +CPlateRecognize::CPlateRecognize() { + m_showResult = false; +} + + +// main method, plate recognize, contain two parts +// 1. plate detect +// 2. chars recognize +int CPlateRecognize::plateRecognize(const Mat& src, std::vector &plateVecOut, int img_index) { + // resize to uniform sizes + float scale = 1.f; + Mat img = uniformResize(src, scale); + + // 1. plate detect + std::vector plateVec; + int resultPD = plateDetect(img, plateVec, img_index); + if (resultPD == 0) { + size_t num = plateVec.size(); + for (size_t j = 0; j < num; j++) { + CPlate& item = plateVec.at(j); + Mat plateMat = item.getPlateMat(); + SHOW_IMAGE(plateMat, 0); + + // scale the rect to src; + item.setPlateScale(scale); + RotatedRect rect = item.getPlatePos(); + item.setPlatePos(scaleBackRRect(rect, 1.f / scale)); + + // get plate color + Color color = item.getPlateColor(); + if (color == UNKNOWN) { + color = getPlateType(plateMat, true); + item.setPlateColor(color); + } + std::string plateColor = getPlateColor(color); + if (0) { + std::cout << "plateColor:" << plateColor << std::endl; + } + + // 2. chars recognize + std::string plateIdentify = ""; + int resultCR = charsRecognise(item, plateIdentify); + if (resultCR == 0) { + std::string license = plateColor + ":" + plateIdentify; + item.setPlateStr(license); + plateVecOut.push_back(item); + if (0) std::cout << "resultCR:" << resultCR << std::endl; + } + else { + std::string license = plateColor; + item.setPlateStr(license); + plateVecOut.push_back(item); + if (0) std::cout << "resultCR:" << resultCR << std::endl; + } + } + if (getResultShow()) { + // param type: 0 detect, 1 recognize; + int showType = 1; + if (0 == showType) + showDectectResults(img, plateVec, num); + else + showDectectResults(img, plateVecOut, num); + } + } + return resultPD; +} + +void CPlateRecognize::LoadSVM(std::string path) { + PlateJudge::instance()->LoadModel(path); +} + +void CPlateRecognize::LoadANN(std::string path) { + CharsIdentify::instance()->LoadModel(path); +} + +void CPlateRecognize::LoadChineseANN(std::string path) { + CharsIdentify::instance()->LoadChineseModel(path); +} + +void CPlateRecognize::LoadGrayChANN(std::string path) { + CharsIdentify::instance()->LoadGrayChANN(path); +} + +void CPlateRecognize::LoadChineseMapping(std::string path) { + CharsIdentify::instance()->LoadChineseMapping(path); +} + +// deprected +int CPlateRecognize::plateRecognize(const Mat& src, std::vector &licenseVec) { + vector plates; + int resultPR = plateRecognize(src, plates, 0); + + for (auto plate : plates) { + licenseVec.push_back(plate.getPlateStr()); + } + return resultPR; +} + +} \ No newline at end of file