@@ -0,0 +1,55 @@ | |||||
cmake_minimum_required(VERSION 3.6) | |||||
project(platescan) | |||||
set(OpenCV_DIR /usr/platescan/opencv-3.3.0/release/) | |||||
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -g -O2 -std=c++11 -fpic -o") | |||||
#set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) | |||||
set(CMAKE_CXX_STANDARD 11) | |||||
set(PHP_CONFIG_DIR /etc/php.d/) | |||||
set(PHP_CONFIG php-config) | |||||
set(POST_COMPILE_COMMAND systemctl restart php-fpm) | |||||
find_package(OpenCV 3.3.0 REQUIRED) | |||||
include_directories( ${OpenCV_INCLUDE_DIRS}) | |||||
include_directories(include) | |||||
add_library( # Sets the name of the library. | |||||
platescan | |||||
# Sets the library as a shared library. | |||||
SHARED | |||||
# Provides a relative path to your source file(s). | |||||
main.cpp | |||||
lpr/CNNRecognizer.cpp | |||||
lpr/FastDeskew.cpp | |||||
lpr/FineMapping.cpp | |||||
lpr/Pipeline.cpp | |||||
lpr/PlateDetection.cpp | |||||
lpr/PlateSegmentation.cpp | |||||
lpr/Recognizer.cpp | |||||
lpr/SegmentationFreeRecognizer.cpp | |||||
) | |||||
target_link_libraries( | |||||
platescan | |||||
phpcpp | |||||
${OpenCV_LIBS} | |||||
) | |||||
execute_process(COMMAND ${PHP_CONFIG} --extension-dir | |||||
OUTPUT_VARIABLE LIBRARY_DIR) | |||||
message("Test ${LIBRARY_DIR} failed.") | |||||
add_custom_command(TARGET platescan | |||||
POST_BUILD | |||||
COMMAND mv ./libplatescan.so ./platescan.so | |||||
COMMAND cp -f ./platescan.so ${LIBRARY_DIR} | |||||
COMMAND ${POST_COMPILE_COMMAND} | |||||
) |
@@ -0,0 +1,16 @@ | |||||
# Prj-PHP | |||||
HyperLPR 在PHP扩展程序中的实现,核心代码拷贝了 Prj-Linux 中庾金科大牛的代码。我做的这部分工作主要是配置编译成PHP扩展程序。 | |||||
```php | |||||
$path = realpath("demo.png"); | |||||
$res = platescan($path); | |||||
var_dump($res); // string(9) "苏ED0N19" | |||||
``` | |||||
最终实现上边这样的PHP调用 | |||||
![image](./tests/demo.png) | |||||
#### 感谢各位大牛! |
@@ -0,0 +1,24 @@ | |||||
// | |||||
// Created by 庾金科 on 21/10/2017. | |||||
// | |||||
#ifndef SWIFTPR_CNNRECOGNIZER_H | |||||
#define SWIFTPR_CNNRECOGNIZER_H | |||||
#include "Recognizer.h" | |||||
namespace pr{ | |||||
class CNNRecognizer: public GeneralRecognizer{ | |||||
public: | |||||
const int CHAR_INPUT_W = 14; | |||||
const int CHAR_INPUT_H = 30; | |||||
CNNRecognizer(std::string prototxt,std::string caffemodel); | |||||
label recognizeCharacter(cv::Mat character); | |||||
private: | |||||
cv::dnn::Net net; | |||||
}; | |||||
} | |||||
#endif //SWIFTPR_CNNRECOGNIZER_H |
@@ -0,0 +1,18 @@ | |||||
// | |||||
// Created by 庾金科 on 22/09/2017. | |||||
// | |||||
#ifndef SWIFTPR_FASTDESKEW_H | |||||
#define SWIFTPR_FASTDESKEW_H | |||||
#include <math.h> | |||||
#include <opencv2/opencv.hpp> | |||||
namespace pr{ | |||||
cv::Mat fastdeskew(cv::Mat skewImage,int blockSize); | |||||
// cv::Mat spatialTransformer(cv::Mat skewImage); | |||||
}//namepace pr | |||||
#endif //SWIFTPR_FASTDESKEW_H |
@@ -0,0 +1,32 @@ | |||||
// | |||||
// Created by 庾金科 on 22/09/2017. | |||||
// | |||||
#ifndef SWIFTPR_FINEMAPPING_H | |||||
#define SWIFTPR_FINEMAPPING_H | |||||
#include <opencv2/opencv.hpp> | |||||
#include <opencv2/dnn.hpp> | |||||
#include <string> | |||||
namespace pr{ | |||||
class FineMapping{ | |||||
public: | |||||
FineMapping(); | |||||
FineMapping(std::string prototxt,std::string caffemodel); | |||||
static cv::Mat FineMappingVertical(cv::Mat InputProposal,int sliceNum=15,int upper=0,int lower=-50,int windows_size=17); | |||||
cv::Mat FineMappingHorizon(cv::Mat FinedVertical,int leftPadding,int rightPadding); | |||||
private: | |||||
cv::dnn::Net net; | |||||
}; | |||||
} | |||||
#endif //SWIFTPR_FINEMAPPING_H |
@@ -0,0 +1,60 @@ | |||||
// | |||||
// Created by 庾金科 on 22/10/2017. | |||||
// | |||||
#ifndef SWIFTPR_PIPLINE_H | |||||
#define SWIFTPR_PIPLINE_H | |||||
#include "PlateDetection.h" | |||||
#include "PlateSegmentation.h" | |||||
#include "CNNRecognizer.h" | |||||
#include "PlateInfo.h" | |||||
#include "FastDeskew.h" | |||||
#include "FineMapping.h" | |||||
#include "Recognizer.h" | |||||
#include "SegmentationFreeRecognizer.h" | |||||
namespace pr{ | |||||
const std::vector<std::string> CH_PLATE_CODE{"京", "沪", "津", "渝", "冀", "晋", "蒙", "辽", "吉", "黑", "苏", "浙", "皖", "闽", "赣", "鲁", "豫", "鄂", "湘", "粤", "桂", | |||||
"琼", "川", "贵", "云", "藏", "陕", "甘", "青", "宁", "新", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", | |||||
"B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R", "S", "T", "U", "V", "W", "X", | |||||
"Y", "Z","港","学","使","警","澳","挂","军","北","南","广","沈","兰","成","济","海","民","航","空"}; | |||||
const int SEGMENTATION_FREE_METHOD = 0; | |||||
const int SEGMENTATION_BASED_METHOD = 1; | |||||
class PipelinePR{ | |||||
public: | |||||
GeneralRecognizer *generalRecognizer; | |||||
PlateDetection *plateDetection; | |||||
PlateSegmentation *plateSegmentation; | |||||
FineMapping *fineMapping; | |||||
SegmentationFreeRecognizer *segmentationFreeRecognizer; | |||||
PipelinePR(std::string detector_filename, | |||||
std::string finemapping_prototxt,std::string finemapping_caffemodel, | |||||
std::string segmentation_prototxt,std::string segmentation_caffemodel, | |||||
std::string charRecognization_proto,std::string charRecognization_caffemodel, | |||||
std::string segmentationfree_proto,std::string segmentationfree_caffemodel | |||||
); | |||||
~PipelinePR(); | |||||
std::vector<std::string> plateRes; | |||||
std::vector<PlateInfo> RunPiplineAsImage(cv::Mat plateImage,int method); | |||||
}; | |||||
} | |||||
#endif //SWIFTPR_PIPLINE_H |
@@ -0,0 +1,33 @@ | |||||
// | |||||
// Created by 庾金科 on 20/09/2017. | |||||
// | |||||
#ifndef SWIFTPR_PLATEDETECTION_H | |||||
#define SWIFTPR_PLATEDETECTION_H | |||||
#include <opencv2/opencv.hpp> | |||||
#include <PlateInfo.h> | |||||
#include <vector> | |||||
namespace pr{ | |||||
class PlateDetection{ | |||||
public: | |||||
PlateDetection(std::string filename_cascade); | |||||
PlateDetection(); | |||||
void LoadModel(std::string filename_cascade); | |||||
void plateDetectionRough(cv::Mat InputImage,std::vector<pr::PlateInfo> &plateInfos,int min_w=36,int max_w=800); | |||||
// std::vector<pr::PlateInfo> plateDetectionRough(cv::Mat InputImage,int min_w= 60,int max_h = 400); | |||||
// std::vector<pr::PlateInfo> plateDetectionRoughByMultiScaleEdge(cv::Mat InputImage); | |||||
private: | |||||
cv::CascadeClassifier cascade; | |||||
}; | |||||
}// namespace pr | |||||
#endif //SWIFTPR_PLATEDETECTION_H |
@@ -0,0 +1,126 @@ | |||||
// | |||||
// Created by 庾金科 on 20/09/2017. | |||||
// | |||||
#ifndef SWIFTPR_PLATEINFO_H | |||||
#define SWIFTPR_PLATEINFO_H | |||||
#include <opencv2/opencv.hpp> | |||||
namespace pr { | |||||
typedef std::vector<cv::Mat> Character; | |||||
enum PlateColor { BLUE, YELLOW, WHITE, GREEN, BLACK,UNKNOWN}; | |||||
enum CharType {CHINESE,LETTER,LETTER_NUMS,INVALID}; | |||||
class PlateInfo { | |||||
public: | |||||
std::vector<std::pair<CharType,cv::Mat>> plateChars; | |||||
std::vector<std::pair<CharType,cv::Mat>> plateCoding; | |||||
float confidence = 0; | |||||
PlateInfo(const cv::Mat &plateData, std::string plateName, cv::Rect plateRect, PlateColor plateType) { | |||||
licensePlate = plateData; | |||||
name = plateName; | |||||
ROI = plateRect; | |||||
Type = plateType; | |||||
} | |||||
PlateInfo(const cv::Mat &plateData, cv::Rect plateRect, PlateColor plateType) { | |||||
licensePlate = plateData; | |||||
ROI = plateRect; | |||||
Type = plateType; | |||||
} | |||||
PlateInfo(const cv::Mat &plateData, cv::Rect plateRect) { | |||||
licensePlate = plateData; | |||||
ROI = plateRect; | |||||
} | |||||
PlateInfo() { | |||||
} | |||||
cv::Mat getPlateImage() { | |||||
return licensePlate; | |||||
} | |||||
void setPlateImage(cv::Mat plateImage){ | |||||
licensePlate = plateImage; | |||||
} | |||||
cv::Rect getPlateRect() { | |||||
return ROI; | |||||
} | |||||
void setPlateRect(cv::Rect plateRect) { | |||||
ROI = plateRect; | |||||
} | |||||
cv::String getPlateName() { | |||||
return name; | |||||
} | |||||
void setPlateName(cv::String plateName) { | |||||
name = plateName; | |||||
} | |||||
int getPlateType() { | |||||
return Type; | |||||
} | |||||
void appendPlateChar(const std::pair<CharType,cv::Mat> &plateChar) | |||||
{ | |||||
plateChars.push_back(plateChar); | |||||
} | |||||
void appendPlateCoding(const std::pair<CharType,cv::Mat> &charProb){ | |||||
plateCoding.push_back(charProb); | |||||
} | |||||
// cv::Mat getPlateChars(int id) { | |||||
// if(id<PlateChars.size()) | |||||
// return PlateChars[id]; | |||||
// } | |||||
std::string decodePlateNormal(std::vector<std::string> mappingTable) { | |||||
std::string decode; | |||||
for(auto plate:plateCoding) { | |||||
float *prob = (float *)plate.second.data; | |||||
if(plate.first == CHINESE) { | |||||
decode += mappingTable[std::max_element(prob,prob+31) - prob]; | |||||
confidence+=*std::max_element(prob,prob+31); | |||||
// std::cout<<*std::max_element(prob,prob+31)<<std::endl; | |||||
} | |||||
else if(plate.first == LETTER) { | |||||
decode += mappingTable[std::max_element(prob+41,prob+65)- prob]; | |||||
confidence+=*std::max_element(prob+41,prob+65); | |||||
} | |||||
else if(plate.first == LETTER_NUMS) { | |||||
decode += mappingTable[std::max_element(prob+31,prob+65)- prob]; | |||||
confidence+=*std::max_element(prob+31,prob+65); | |||||
// std::cout<<*std::max_element(prob+31,prob+65)<<std::endl; | |||||
} | |||||
else if(plate.first == INVALID) | |||||
{ | |||||
decode+='*'; | |||||
} | |||||
} | |||||
name = decode; | |||||
confidence/=7; | |||||
return decode; | |||||
} | |||||
private: | |||||
cv::Mat licensePlate; | |||||
cv::Rect ROI; | |||||
std::string name ; | |||||
PlateColor Type; | |||||
}; | |||||
} | |||||
#endif //SWIFTPR_PLATEINFO_H |
@@ -0,0 +1,39 @@ | |||||
// | |||||
// Created by 庾金科 on 16/10/2017. | |||||
// | |||||
#ifndef SWIFTPR_PLATESEGMENTATION_H | |||||
#define SWIFTPR_PLATESEGMENTATION_H | |||||
#include "opencv2/opencv.hpp" | |||||
#include <opencv2/dnn.hpp> | |||||
#include "PlateInfo.h" | |||||
namespace pr{ | |||||
class PlateSegmentation{ | |||||
public: | |||||
const int PLATE_NORMAL = 6; | |||||
const int PLATE_NORMAL_GREEN = 7; | |||||
const int DEFAULT_WIDTH = 20; | |||||
PlateSegmentation(std::string phototxt,std::string caffemodel); | |||||
PlateSegmentation(){} | |||||
void segmentPlatePipline(PlateInfo &plateInfo,int stride,std::vector<cv::Rect> &Char_rects); | |||||
void segmentPlateBySlidingWindows(cv::Mat &plateImage,int windowsWidth,int stride,cv::Mat &respones); | |||||
void templateMatchFinding(const cv::Mat &respones,int windowsWidth,std::pair<float,std::vector<int>> &candidatePts); | |||||
void refineRegion(cv::Mat &plateImage,const std::vector<int> &candidatePts,const int padding,std::vector<cv::Rect> &rects); | |||||
void ExtractRegions(PlateInfo &plateInfo,std::vector<cv::Rect> &rects); | |||||
cv::Mat classifyResponse(const cv::Mat &cropped); | |||||
private: | |||||
cv::dnn::Net net; | |||||
// RefineRegion() | |||||
}; | |||||
}//namespace pr | |||||
#endif //SWIFTPR_PLATESEGMENTATION_H |
@@ -0,0 +1,23 @@ | |||||
// | |||||
// Created by 庾金科 on 20/10/2017. | |||||
// | |||||
#ifndef SWIFTPR_RECOGNIZER_H | |||||
#define SWIFTPR_RECOGNIZER_H | |||||
#include "PlateInfo.h" | |||||
#include "opencv2/dnn.hpp" | |||||
namespace pr{ | |||||
typedef cv::Mat label; | |||||
class GeneralRecognizer{ | |||||
public: | |||||
virtual label recognizeCharacter(cv::Mat character) = 0; | |||||
// virtual cv::Mat SegmentationFreeForSinglePlate(cv::Mat plate) = 0; | |||||
void SegmentBasedSequenceRecognition(PlateInfo &plateinfo); | |||||
void SegmentationFreeSequenceRecognition(PlateInfo &plateInfo); | |||||
}; | |||||
} | |||||
#endif //SWIFTPR_RECOGNIZER_H |
@@ -0,0 +1,28 @@ | |||||
// | |||||
// Created by 庾金科 on 28/11/2017. | |||||
// | |||||
#ifndef SWIFTPR_SEGMENTATIONFREERECOGNIZER_H | |||||
#define SWIFTPR_SEGMENTATIONFREERECOGNIZER_H | |||||
#include "Recognizer.h" | |||||
namespace pr{ | |||||
class SegmentationFreeRecognizer{ | |||||
public: | |||||
const int CHAR_INPUT_W = 14; | |||||
const int CHAR_INPUT_H = 30; | |||||
const int CHAR_LEN = 84; | |||||
SegmentationFreeRecognizer(std::string prototxt,std::string caffemodel); | |||||
std::pair<std::string,float> SegmentationFreeForSinglePlate(cv::Mat plate,std::vector<std::string> mapping_table); | |||||
private: | |||||
cv::dnn::Net net; | |||||
}; | |||||
} | |||||
#endif //SWIFTPR_SEGMENTATIONFREERECOGNIZER_H |
@@ -0,0 +1,107 @@ | |||||
// | |||||
// Created by 庾金科 on 26/10/2017. | |||||
// | |||||
#ifndef SWIFTPR_NIBLACKTHRESHOLD_H | |||||
#define SWIFTPR_NIBLACKTHRESHOLD_H | |||||
#include <opencv2/opencv.hpp> | |||||
using namespace cv; | |||||
enum LocalBinarizationMethods{ | |||||
BINARIZATION_NIBLACK = 0, //!< Classic Niblack binarization. See @cite Niblack1985 . | |||||
BINARIZATION_SAUVOLA = 1, //!< Sauvola's technique. See @cite Sauvola1997 . | |||||
BINARIZATION_WOLF = 2, //!< Wolf's technique. See @cite Wolf2004 . | |||||
BINARIZATION_NICK = 3 //!< NICK technique. See @cite Khurshid2009 . | |||||
}; | |||||
void niBlackThreshold( InputArray _src, OutputArray _dst, double maxValue, | |||||
int type, int blockSize, double k, int binarizationMethod ) | |||||
{ | |||||
// Input grayscale image | |||||
Mat src = _src.getMat(); | |||||
CV_Assert(src.channels() == 1); | |||||
CV_Assert(blockSize % 2 == 1 && blockSize > 1); | |||||
if (binarizationMethod == BINARIZATION_SAUVOLA) { | |||||
CV_Assert(src.depth() == CV_8U); | |||||
} | |||||
type &= THRESH_MASK; | |||||
// Compute local threshold (T = mean + k * stddev) | |||||
// using mean and standard deviation in the neighborhood of each pixel | |||||
// (intermediate calculations are done with floating-point precision) | |||||
Mat test; | |||||
Mat thresh; | |||||
{ | |||||
// note that: Var[X] = E[X^2] - E[X]^2 | |||||
Mat mean, sqmean, variance, stddev, sqrtVarianceMeanSum; | |||||
double srcMin, stddevMax; | |||||
boxFilter(src, mean, CV_32F, Size(blockSize, blockSize), | |||||
Point(-1,-1), true, BORDER_REPLICATE); | |||||
sqrBoxFilter(src, sqmean, CV_32F, Size(blockSize, blockSize), | |||||
Point(-1,-1), true, BORDER_REPLICATE); | |||||
variance = sqmean - mean.mul(mean); | |||||
sqrt(variance, stddev); | |||||
switch (binarizationMethod) | |||||
{ | |||||
case BINARIZATION_NIBLACK: | |||||
thresh = mean + stddev * static_cast<float>(k); | |||||
break; | |||||
case BINARIZATION_SAUVOLA: | |||||
thresh = mean.mul(1. + static_cast<float>(k) * (stddev / 128.0 - 1.)); | |||||
break; | |||||
case BINARIZATION_WOLF: | |||||
minMaxIdx(src, &srcMin,NULL); | |||||
minMaxIdx(stddev, NULL, &stddevMax); | |||||
thresh = mean - static_cast<float>(k) * (mean - srcMin - stddev.mul(mean - srcMin) / stddevMax); | |||||
break; | |||||
case BINARIZATION_NICK: | |||||
sqrt(variance + sqmean, sqrtVarianceMeanSum); | |||||
thresh = mean + static_cast<float>(k) * sqrtVarianceMeanSum; | |||||
break; | |||||
default: | |||||
CV_Error( CV_StsBadArg, "Unknown binarization method" ); | |||||
break; | |||||
} | |||||
thresh.convertTo(thresh, src.depth()); | |||||
thresh.convertTo(test, src.depth()); | |||||
// | |||||
// cv::imshow("imagex",test); | |||||
// cv::waitKey(0); | |||||
} | |||||
// Prepare output image | |||||
_dst.create(src.size(), src.type()); | |||||
Mat dst = _dst.getMat(); | |||||
CV_Assert(src.data != dst.data); // no inplace processing | |||||
// Apply thresholding: ( pixel > threshold ) ? foreground : background | |||||
Mat mask; | |||||
switch (type) | |||||
{ | |||||
case THRESH_BINARY: // dst = (src > thresh) ? maxval : 0 | |||||
case THRESH_BINARY_INV: // dst = (src > thresh) ? 0 : maxval | |||||
compare(src, thresh, mask, (type == THRESH_BINARY ? CMP_GT : CMP_LE)); | |||||
dst.setTo(0); | |||||
dst.setTo(maxValue, mask); | |||||
break; | |||||
case THRESH_TRUNC: // dst = (src > thresh) ? thresh : src | |||||
compare(src, thresh, mask, CMP_GT); | |||||
src.copyTo(dst); | |||||
thresh.copyTo(dst, mask); | |||||
break; | |||||
case THRESH_TOZERO: // dst = (src > thresh) ? src : 0 | |||||
case THRESH_TOZERO_INV: // dst = (src > thresh) ? 0 : src | |||||
compare(src, thresh, mask, (type == THRESH_TOZERO ? CMP_GT : CMP_LE)); | |||||
dst.setTo(0); | |||||
src.copyTo(dst, mask); | |||||
break; | |||||
default: | |||||
CV_Error( CV_StsBadArg, "Unknown threshold type" ); | |||||
break; | |||||
} | |||||
} | |||||
#endif //SWIFTPR_NIBLACKTHRESHOLD_H |
@@ -0,0 +1,19 @@ | |||||
// | |||||
// Created by 庾金科 on 21/10/2017. | |||||
// | |||||
#include "CNNRecognizer.h" | |||||
namespace pr{ | |||||
CNNRecognizer::CNNRecognizer(std::string prototxt,std::string caffemodel){ | |||||
net = cv::dnn::readNetFromCaffe(prototxt, caffemodel); | |||||
} | |||||
label CNNRecognizer::recognizeCharacter(cv::Mat charImage){ | |||||
if(charImage.channels()== 3) | |||||
cv::cvtColor(charImage,charImage,cv::COLOR_BGR2GRAY); | |||||
cv::Mat inputBlob = cv::dnn::blobFromImage(charImage, 1/255.0, cv::Size(CHAR_INPUT_W,CHAR_INPUT_H), cv::Scalar(0,0,0),false); | |||||
net.setInput(inputBlob,"data"); | |||||
return net.forward(); | |||||
} | |||||
} |
@@ -0,0 +1,108 @@ | |||||
// | |||||
// Created by Jack Yu on 02/10/2017. | |||||
// | |||||
#include "FastDeskew.h" | |||||
namespace pr{ | |||||
const int ANGLE_MIN = 30 ; | |||||
const int ANGLE_MAX = 150 ; | |||||
const int PLATE_H = 36; | |||||
const int PLATE_W = 136; | |||||
int angle(float x,float y) | |||||
{ | |||||
return atan2(x,y)*180/3.1415; | |||||
} | |||||
std::vector<float> avgfilter(std::vector<float> angle_list,int windowsSize) { | |||||
std::vector<float> angle_list_filtered(angle_list.size() - windowsSize + 1); | |||||
for (int i = 0; i < angle_list.size() - windowsSize + 1; i++) { | |||||
float avg = 0.00f; | |||||
for (int j = 0; j < windowsSize; j++) { | |||||
avg += angle_list[i + j]; | |||||
} | |||||
avg = avg / windowsSize; | |||||
angle_list_filtered[i] = avg; | |||||
} | |||||
return angle_list_filtered; | |||||
} | |||||
void drawHist(std::vector<float> seq){ | |||||
cv::Mat image(300,seq.size(),CV_8U); | |||||
image.setTo(0); | |||||
for(int i = 0;i<seq.size();i++) | |||||
{ | |||||
float l = *std::max_element(seq.begin(),seq.end()); | |||||
int p = int(float(seq[i])/l*300); | |||||
cv::line(image,cv::Point(i,300),cv::Point(i,300-p),cv::Scalar(255,255,255)); | |||||
} | |||||
cv::imshow("vis",image); | |||||
} | |||||
cv::Mat correctPlateImage(cv::Mat skewPlate,float angle,float maxAngle) | |||||
{ | |||||
cv::Mat dst; | |||||
cv::Size size_o(skewPlate.cols,skewPlate.rows); | |||||
int extend_padding = 0; | |||||
extend_padding = static_cast<int>(skewPlate.rows*tan(cv::abs(angle)/180* 3.14) ); | |||||
cv::Size size(skewPlate.cols + extend_padding ,skewPlate.rows); | |||||
float interval = abs(sin((angle /180) * 3.14)* skewPlate.rows); | |||||
cv::Point2f pts1[4] = {cv::Point2f(0,0),cv::Point2f(0,size_o.height),cv::Point2f(size_o.width,0),cv::Point2f(size_o.width,size_o.height)}; | |||||
if(angle>0) { | |||||
cv::Point2f pts2[4] = {cv::Point2f(interval, 0), cv::Point2f(0, size_o.height), | |||||
cv::Point2f(size_o.width, 0), cv::Point2f(size_o.width - interval, size_o.height)}; | |||||
cv::Mat M = cv::getPerspectiveTransform(pts1,pts2); | |||||
cv::warpPerspective(skewPlate,dst,M,size); | |||||
} | |||||
else { | |||||
cv::Point2f pts2[4] = {cv::Point2f(0, 0), cv::Point2f(interval, size_o.height), cv::Point2f(size_o.width-interval, 0), | |||||
cv::Point2f(size_o.width, size_o.height)}; | |||||
cv::Mat M = cv::getPerspectiveTransform(pts1,pts2); | |||||
cv::warpPerspective(skewPlate,dst,M,size,cv::INTER_CUBIC); | |||||
} | |||||
return dst; | |||||
} | |||||
cv::Mat fastdeskew(cv::Mat skewImage,int blockSize){ | |||||
const int FILTER_WINDOWS_SIZE = 5; | |||||
std::vector<float> angle_list(180); | |||||
memset(angle_list.data(),0,angle_list.size()*sizeof(int)); | |||||
cv::Mat bak; | |||||
skewImage.copyTo(bak); | |||||
if(skewImage.channels() == 3) | |||||
cv::cvtColor(skewImage,skewImage,cv::COLOR_RGB2GRAY); | |||||
if(skewImage.channels() == 1) | |||||
{ | |||||
cv::Mat eigen; | |||||
cv::cornerEigenValsAndVecs(skewImage,eigen,blockSize,5); | |||||
for( int j = 0; j < skewImage.rows; j+=blockSize ) | |||||
{ for( int i = 0; i < skewImage.cols; i+=blockSize ) | |||||
{ | |||||
float x2 = eigen.at<cv::Vec6f>(j, i)[4]; | |||||
float y2 = eigen.at<cv::Vec6f>(j, i)[5]; | |||||
int angle_cell = angle(x2,y2); | |||||
angle_list[(angle_cell + 180)%180]+=1.0; | |||||
} | |||||
} | |||||
} | |||||
std::vector<float> filtered = avgfilter(angle_list,5); | |||||
int maxPos = std::max_element(filtered.begin(),filtered.end()) - filtered.begin() + FILTER_WINDOWS_SIZE/2; | |||||
if(maxPos>ANGLE_MAX) | |||||
maxPos = (-maxPos+90+180)%180; | |||||
if(maxPos<ANGLE_MIN) | |||||
maxPos-=90; | |||||
maxPos=90-maxPos; | |||||
cv::Mat deskewed = correctPlateImage(bak, static_cast<float>(maxPos),60.0f); | |||||
return deskewed; | |||||
} | |||||
}//namespace pr |
@@ -0,0 +1,170 @@ | |||||
#include "FineMapping.h" | |||||
namespace pr{ | |||||
const int FINEMAPPING_H = 60 ; | |||||
const int FINEMAPPING_W = 140; | |||||
const int PADDING_UP_DOWN = 30; | |||||
void drawRect(cv::Mat image,cv::Rect rect) | |||||
{ | |||||
cv::Point p1(rect.x,rect.y); | |||||
cv::Point p2(rect.x+rect.width,rect.y+rect.height); | |||||
cv::rectangle(image,p1,p2,cv::Scalar(0,255,0),1); | |||||
} | |||||
FineMapping::FineMapping(std::string prototxt,std::string caffemodel) { | |||||
net = cv::dnn::readNetFromCaffe(prototxt, caffemodel); | |||||
} | |||||
cv::Mat FineMapping::FineMappingHorizon(cv::Mat FinedVertical,int leftPadding,int rightPadding) | |||||
{ | |||||
// if(FinedVertical.channels()==1) | |||||
// cv::cvtColor(FinedVertical,FinedVertical,cv::COLOR_GRAY2BGR); | |||||
cv::Mat inputBlob = cv::dnn::blobFromImage(FinedVertical, 1/255.0, cv::Size(66,16), | |||||
cv::Scalar(0,0,0),false); | |||||
net.setInput(inputBlob,"data"); | |||||
cv::Mat prob = net.forward(); | |||||
int front = static_cast<int>(prob.at<float>(0,0)*FinedVertical.cols); | |||||
int back = static_cast<int>(prob.at<float>(0,1)*FinedVertical.cols); | |||||
front -= leftPadding ; | |||||
if(front<0) front = 0; | |||||
back +=rightPadding; | |||||
if(back>FinedVertical.cols-1) back=FinedVertical.cols - 1; | |||||
cv::Mat cropped = FinedVertical.colRange(front,back).clone(); | |||||
return cropped; | |||||
} | |||||
std::pair<int,int> FitLineRansac(std::vector<cv::Point> pts,int zeroadd = 0 ) | |||||
{ | |||||
std::pair<int,int> res; | |||||
if(pts.size()>2) | |||||
{ | |||||
cv::Vec4f line; | |||||
cv::fitLine(pts,line,cv::DIST_HUBER,0,0.01,0.01); | |||||
float vx = line[0]; | |||||
float vy = line[1]; | |||||
float x = line[2]; | |||||
float y = line[3]; | |||||
int lefty = static_cast<int>((-x * vy / vx) + y); | |||||
int righty = static_cast<int>(((136- x) * vy / vx) + y); | |||||
res.first = lefty+PADDING_UP_DOWN+zeroadd; | |||||
res.second = righty+PADDING_UP_DOWN+zeroadd; | |||||
return res; | |||||
} | |||||
res.first = zeroadd; | |||||
res.second = zeroadd; | |||||
return res; | |||||
} | |||||
cv::Mat FineMapping::FineMappingVertical(cv::Mat InputProposal,int sliceNum,int upper,int lower,int windows_size){ | |||||
cv::Mat PreInputProposal; | |||||
cv::Mat proposal; | |||||
cv::resize(InputProposal,PreInputProposal,cv::Size(FINEMAPPING_W,FINEMAPPING_H)); | |||||
if(InputProposal.channels() == 3) | |||||
cv::cvtColor(PreInputProposal,proposal,cv::COLOR_BGR2GRAY); | |||||
else | |||||
PreInputProposal.copyTo(proposal); | |||||
// this will improve some sen | |||||
cv::Mat kernal = cv::getStructuringElement(cv::MORPH_ELLIPSE,cv::Size(1,3)); | |||||
float diff = static_cast<float>(upper-lower); | |||||
diff/=static_cast<float>(sliceNum-1); | |||||
cv::Mat binary_adaptive; | |||||
std::vector<cv::Point> line_upper; | |||||
std::vector<cv::Point> line_lower; | |||||
int contours_nums=0; | |||||
for(int i = 0 ; i < sliceNum ; i++) | |||||
{ | |||||
std::vector<std::vector<cv::Point> > contours; | |||||
float k =lower + i*diff; | |||||
cv::adaptiveThreshold(proposal,binary_adaptive,255,cv::ADAPTIVE_THRESH_MEAN_C,cv::THRESH_BINARY,windows_size,k); | |||||
cv::Mat draw; | |||||
binary_adaptive.copyTo(draw); | |||||
cv::findContours(binary_adaptive,contours,cv::RETR_EXTERNAL,cv::CHAIN_APPROX_SIMPLE); | |||||
for(auto contour: contours) | |||||
{ | |||||
cv::Rect bdbox =cv::boundingRect(contour); | |||||
float lwRatio = bdbox.height/static_cast<float>(bdbox.width); | |||||
int bdboxAera = bdbox.width*bdbox.height; | |||||
if (( lwRatio>0.7&&bdbox.width*bdbox.height>100 && bdboxAera<300) | |||||
|| (lwRatio>3.0 && bdboxAera<100 && bdboxAera>10)) | |||||
{ | |||||
cv::Point p1(bdbox.x, bdbox.y); | |||||
cv::Point p2(bdbox.x + bdbox.width, bdbox.y + bdbox.height); | |||||
line_upper.push_back(p1); | |||||
line_lower.push_back(p2); | |||||
contours_nums+=1; | |||||
} | |||||
} | |||||
} | |||||
if(contours_nums<41) | |||||
{ | |||||
cv::bitwise_not(InputProposal,InputProposal); | |||||
cv::Mat kernal = cv::getStructuringElement(cv::MORPH_ELLIPSE,cv::Size(1,5)); | |||||
cv::Mat bak; | |||||
cv::resize(InputProposal,bak,cv::Size(FINEMAPPING_W,FINEMAPPING_H)); | |||||
cv::erode(bak,bak,kernal); | |||||
if(InputProposal.channels() == 3) | |||||
cv::cvtColor(bak,proposal,cv::COLOR_BGR2GRAY); | |||||
else | |||||
proposal = bak; | |||||
int contours_nums=0; | |||||
for(int i = 0 ; i < sliceNum ; i++) | |||||
{ | |||||
std::vector<std::vector<cv::Point> > contours; | |||||
float k =lower + i*diff; | |||||
cv::adaptiveThreshold(proposal,binary_adaptive,255,cv::ADAPTIVE_THRESH_MEAN_C,cv::THRESH_BINARY,windows_size,k); | |||||
cv::Mat draw; | |||||
binary_adaptive.copyTo(draw); | |||||
cv::findContours(binary_adaptive,contours,cv::RETR_EXTERNAL,cv::CHAIN_APPROX_SIMPLE); | |||||
for(auto contour: contours) | |||||
{ | |||||
cv::Rect bdbox =cv::boundingRect(contour); | |||||
float lwRatio = bdbox.height/static_cast<float>(bdbox.width); | |||||
int bdboxAera = bdbox.width*bdbox.height; | |||||
if (( lwRatio>0.7&&bdbox.width*bdbox.height>120 && bdboxAera<300) | |||||
|| (lwRatio>3.0 && bdboxAera<100 && bdboxAera>10)) | |||||
{ | |||||
cv::Point p1(bdbox.x, bdbox.y); | |||||
cv::Point p2(bdbox.x + bdbox.width, bdbox.y + bdbox.height); | |||||
line_upper.push_back(p1); | |||||
line_lower.push_back(p2); | |||||
contours_nums+=1; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
cv::Mat rgb; | |||||
cv::copyMakeBorder(PreInputProposal, rgb, PADDING_UP_DOWN, PADDING_UP_DOWN, 0, 0, cv::BORDER_REPLICATE); | |||||
std::pair<int, int> A; | |||||
std::pair<int, int> B; | |||||
A = FitLineRansac(line_upper, -1); | |||||
B = FitLineRansac(line_lower, 1); | |||||
int leftyB = A.first; | |||||
int rightyB = A.second; | |||||
int leftyA = B.first; | |||||
int rightyA = B.second; | |||||
int cols = rgb.cols; | |||||
int rows = rgb.rows; | |||||
std::vector<cv::Point2f> corners(4); | |||||
corners[0] = cv::Point2f(cols - 1, rightyA); | |||||
corners[1] = cv::Point2f(0, leftyA); | |||||
corners[2] = cv::Point2f(cols - 1, rightyB); | |||||
corners[3] = cv::Point2f(0, leftyB); | |||||
std::vector<cv::Point2f> corners_trans(4); | |||||
corners_trans[0] = cv::Point2f(136, 36); | |||||
corners_trans[1] = cv::Point2f(0, 36); | |||||
corners_trans[2] = cv::Point2f(136, 0); | |||||
corners_trans[3] = cv::Point2f(0, 0); | |||||
cv::Mat transform = cv::getPerspectiveTransform(corners, corners_trans); | |||||
cv::Mat quad = cv::Mat::zeros(36, 136, CV_8UC3); | |||||
cv::warpPerspective(rgb, quad, transform, quad.size()); | |||||
return quad; | |||||
} | |||||
} | |||||
@@ -0,0 +1,101 @@ | |||||
// | |||||
// Created by 庾金科 on 23/10/2017. | |||||
// | |||||
#include "Pipeline.h" | |||||
namespace pr { | |||||
const int HorizontalPadding = 4; | |||||
PipelinePR::PipelinePR(std::string detector_filename, | |||||
std::string finemapping_prototxt, std::string finemapping_caffemodel, | |||||
std::string segmentation_prototxt, std::string segmentation_caffemodel, | |||||
std::string charRecognization_proto, std::string charRecognization_caffemodel, | |||||
std::string segmentationfree_proto,std::string segmentationfree_caffemodel) { | |||||
plateDetection = new PlateDetection(detector_filename); | |||||
fineMapping = new FineMapping(finemapping_prototxt, finemapping_caffemodel); | |||||
plateSegmentation = new PlateSegmentation(segmentation_prototxt, segmentation_caffemodel); | |||||
generalRecognizer = new CNNRecognizer(charRecognization_proto, charRecognization_caffemodel); | |||||
segmentationFreeRecognizer = new SegmentationFreeRecognizer(segmentationfree_proto,segmentationfree_caffemodel); | |||||
} | |||||
PipelinePR::~PipelinePR() { | |||||
delete plateDetection; | |||||
delete fineMapping; | |||||
delete plateSegmentation; | |||||
delete generalRecognizer; | |||||
delete segmentationFreeRecognizer; | |||||
} | |||||
std::vector<PlateInfo> PipelinePR:: RunPiplineAsImage(cv::Mat plateImage,int method) { | |||||
std::vector<PlateInfo> results; | |||||
std::vector<pr::PlateInfo> plates; | |||||
plateDetection->plateDetectionRough(plateImage,plates,36,700); | |||||
for (pr::PlateInfo plateinfo:plates) { | |||||
cv::Mat image_finemapping = plateinfo.getPlateImage(); | |||||
image_finemapping = fineMapping->FineMappingVertical(image_finemapping); | |||||
image_finemapping = pr::fastdeskew(image_finemapping, 5); | |||||
//Segmentation-based | |||||
if(method==SEGMENTATION_BASED_METHOD) | |||||
{ | |||||
image_finemapping = fineMapping->FineMappingHorizon(image_finemapping, 2, HorizontalPadding); | |||||
cv::resize(image_finemapping, image_finemapping, cv::Size(136+HorizontalPadding, 36)); | |||||
// cv::imshow("image_finemapping",image_finemapping); | |||||
// cv::waitKey(0); | |||||
plateinfo.setPlateImage(image_finemapping); | |||||
std::vector<cv::Rect> rects; | |||||
plateSegmentation->segmentPlatePipline(plateinfo, 1, rects); | |||||
plateSegmentation->ExtractRegions(plateinfo, rects); | |||||
cv::copyMakeBorder(image_finemapping, image_finemapping, 0, 0, 0, 20, cv::BORDER_REPLICATE); | |||||
plateinfo.setPlateImage(image_finemapping); | |||||
generalRecognizer->SegmentBasedSequenceRecognition(plateinfo); | |||||
plateinfo.decodePlateNormal(pr::CH_PLATE_CODE); | |||||
} | |||||
//Segmentation-free | |||||
else if(method==SEGMENTATION_FREE_METHOD) | |||||
{ | |||||
image_finemapping = fineMapping->FineMappingHorizon(image_finemapping, 4, HorizontalPadding+3); | |||||
cv::resize(image_finemapping, image_finemapping, cv::Size(136+HorizontalPadding, 36)); | |||||
// cv::imwrite("./test.png",image_finemapping); | |||||
// cv::imshow("image_finemapping",image_finemapping); | |||||
// cv::waitKey(0); | |||||
plateinfo.setPlateImage(image_finemapping); | |||||
// std::vector<cv::Rect> rects; | |||||
std::pair<std::string,float> res = segmentationFreeRecognizer->SegmentationFreeForSinglePlate(plateinfo.getPlateImage(),pr::CH_PLATE_CODE); | |||||
plateinfo.confidence = res.second; | |||||
plateinfo.setPlateName(res.first); | |||||
} | |||||
results.push_back(plateinfo); | |||||
} | |||||
// for (auto str:results) { | |||||
// std::cout << str << std::endl; | |||||
// } | |||||
return results; | |||||
}//namespace pr | |||||
} |
@@ -0,0 +1,32 @@ | |||||
#include "PlateDetection.h" | |||||
#include "util.h" | |||||
namespace pr{ | |||||
PlateDetection::PlateDetection(std::string filename_cascade){ | |||||
cascade.load(filename_cascade); | |||||
}; | |||||
void PlateDetection::plateDetectionRough(cv::Mat InputImage,std::vector<pr::PlateInfo> &plateInfos,int min_w,int max_w){ | |||||
cv::Mat processImage; | |||||
cv::cvtColor(InputImage,processImage,cv::COLOR_BGR2GRAY); | |||||
std::vector<cv::Rect> platesRegions; | |||||
cv::Size minSize(min_w,min_w/4); | |||||
cv::Size maxSize(max_w,max_w/4); | |||||
cascade.detectMultiScale( processImage, platesRegions, | |||||
1.1, 3, cv::CASCADE_SCALE_IMAGE,minSize,maxSize); | |||||
for(auto plate:platesRegions) | |||||
{ | |||||
int zeroadd_w = static_cast<int>(plate.width*0.30); | |||||
int zeroadd_h = static_cast<int>(plate.height*2); | |||||
int zeroadd_x = static_cast<int>(plate.width*0.15); | |||||
int zeroadd_y = static_cast<int>(plate.height*1); | |||||
plate.x-=zeroadd_x; | |||||
plate.y-=zeroadd_y; | |||||
plate.height += zeroadd_h; | |||||
plate.width += zeroadd_w; | |||||
cv::Mat plateImage = util::cropFromImage(InputImage,plate); | |||||
PlateInfo plateInfo(plateImage,plate); | |||||
plateInfos.push_back(plateInfo); | |||||
} | |||||
} | |||||
}//namespace pr |
@@ -0,0 +1,404 @@ | |||||
// | |||||
// Created by 庾金科 on 16/10/2017. | |||||
// | |||||
#include "PlateSegmentation.h" | |||||
#include "niBlackThreshold.h" | |||||
//#define DEBUG | |||||
namespace pr{ | |||||
PlateSegmentation::PlateSegmentation(std::string prototxt,std::string caffemodel) { | |||||
net = cv::dnn::readNetFromCaffe(prototxt, caffemodel); | |||||
} | |||||
cv::Mat PlateSegmentation::classifyResponse(const cv::Mat &cropped){ | |||||
cv::Mat inputBlob = cv::dnn::blobFromImage(cropped, 1/255.0, cv::Size(22,22), cv::Scalar(0,0,0),false); | |||||
net.setInput(inputBlob,"data"); | |||||
return net.forward(); | |||||
} | |||||
void drawHist(float* seq,int size,const char* name){ | |||||
cv::Mat image(300,size,CV_8U); | |||||
image.setTo(0); | |||||
float* start =seq; | |||||
float* end = seq+size; | |||||
float l = *std::max_element(start,end); | |||||
for(int i = 0;i<size;i++) | |||||
{ | |||||
int p = int(float(seq[i])/l*300); | |||||
cv::line(image,cv::Point(i,300),cv::Point(i,300-p),cv::Scalar(255,255,255)); | |||||
} | |||||
cv::resize(image,image,cv::Size(600,100)); | |||||
cv::imshow(name,image); | |||||
} | |||||
inline void computeSafeMargin(int &val,const int &rows){ | |||||
val = std::min(val,rows); | |||||
val = std::max(val,0); | |||||
} | |||||
cv::Rect boxFromCenter(const cv::Point center,int left,int right,int top,int bottom,cv::Size bdSize) | |||||
{ | |||||
cv::Point p1(center.x - left ,center.y - top); | |||||
cv::Point p2( center.x + right, center.y + bottom); | |||||
p1.x = std::max(0,p1.x); | |||||
p1.y = std::max(0,p1.y); | |||||
p2.x = std::min(p2.x,bdSize.width-1); | |||||
p2.y = std::min(p2.y,bdSize.height-1); | |||||
cv::Rect rect(p1,p2); | |||||
return rect; | |||||
} | |||||
cv::Rect boxPadding(cv::Rect rect,int left,int right,int top,int bottom,cv::Size bdSize) | |||||
{ | |||||
cv::Point center(rect.x+(rect.width>>1),rect.y + (rect.height>>1)); | |||||
int rebuildLeft = (rect.width>>1 )+ left; | |||||
int rebuildRight = (rect.width>>1 )+ right; | |||||
int rebuildTop = (rect.height>>1 )+ top; | |||||
int rebuildBottom = (rect.height>>1 )+ bottom; | |||||
return boxFromCenter(center,rebuildLeft,rebuildRight,rebuildTop,rebuildBottom,bdSize); | |||||
} | |||||
void PlateSegmentation:: refineRegion(cv::Mat &plateImage,const std::vector<int> &candidatePts,const int padding,std::vector<cv::Rect> &rects){ | |||||
int w = candidatePts[5] - candidatePts[4]; | |||||
int cols = plateImage.cols; | |||||
int rows = plateImage.rows; | |||||
for(int i = 0 ; i < candidatePts.size() ; i++) | |||||
{ | |||||
int left = 0; | |||||
int right = 0 ; | |||||
if(i == 0 ){ | |||||
left= candidatePts[i]; | |||||
right = left+w+padding; | |||||
} | |||||
else { | |||||
left = candidatePts[i] - padding; | |||||
right = left + w + padding * 2; | |||||
} | |||||
computeSafeMargin(right,cols); | |||||
computeSafeMargin(left,cols); | |||||
cv::Rect roi(left,0,right - left,rows-1); | |||||
cv::Mat roiImage; | |||||
plateImage(roi).copyTo(roiImage); | |||||
if (i>=1) | |||||
{ | |||||
cv::Mat roi_thres; | |||||
// cv::threshold(roiImage,roi_thres,0,255,cv::THRESH_OTSU|cv::THRESH_BINARY); | |||||
niBlackThreshold(roiImage,roi_thres,255,cv::THRESH_BINARY,15,0.27,BINARIZATION_NIBLACK); | |||||
std::vector<std::vector<cv::Point>> contours; | |||||
cv::findContours(roi_thres,contours,cv::RETR_LIST,cv::CHAIN_APPROX_SIMPLE); | |||||
cv::Point boxCenter(roiImage.cols>>1,roiImage.rows>>1); | |||||
cv::Rect final_bdbox; | |||||
cv::Point final_center; | |||||
int final_dist = INT_MAX; | |||||
for(auto contour:contours) | |||||
{ | |||||
cv::Rect bdbox = cv::boundingRect(contour); | |||||
cv::Point center(bdbox.x+(bdbox.width>>1),bdbox.y + (bdbox.height>>1)); | |||||
int dist = (center.x - boxCenter.x)*(center.x - boxCenter.x); | |||||
if(dist<final_dist and bdbox.height > rows>>1) | |||||
{ final_dist =dist; | |||||
final_center = center; | |||||
final_bdbox = bdbox; | |||||
} | |||||
} | |||||
//rebuild box | |||||
if(final_bdbox.height/ static_cast<float>(final_bdbox.width) > 3.5 && final_bdbox.width*final_bdbox.height<10) | |||||
final_bdbox = boxFromCenter(final_center,8,8,(rows>>1)-3 , (rows>>1) - 2,roiImage.size()); | |||||
else { | |||||
if(i == candidatePts.size()-1) | |||||
final_bdbox = boxPadding(final_bdbox, padding/2, padding, padding/2, padding/2, roiImage.size()); | |||||
else | |||||
final_bdbox = boxPadding(final_bdbox, padding, padding, padding, padding, roiImage.size()); | |||||
// std::cout<<final_bdbox<<std::endl; | |||||
// std::cout<<roiImage.size()<<std::endl; | |||||
#ifdef DEBUG | |||||
cv::imshow("char_thres",roi_thres); | |||||
cv::imshow("char",roiImage(final_bdbox)); | |||||
cv::waitKey(0); | |||||
#endif | |||||
} | |||||
final_bdbox.x += left; | |||||
rects.push_back(final_bdbox); | |||||
// | |||||
} | |||||
else | |||||
{ | |||||
rects.push_back(roi); | |||||
} | |||||
// else | |||||
// { | |||||
// | |||||
// } | |||||
// cv::GaussianBlur(roiImage,roiImage,cv::Size(7,7),3); | |||||
// | |||||
// cv::imshow("image",roiImage); | |||||
// cv::waitKey(0); | |||||
} | |||||
} | |||||
void avgfilter(float *angle_list,int size,int windowsSize) { | |||||
float *filterd = new float[size]; | |||||
for(int i = 0 ; i < size ; i++) filterd [i] = angle_list[i]; | |||||
// memcpy(filterd,angle_list,size); | |||||
cv::Mat kernal_gaussian = cv::getGaussianKernel(windowsSize,3,CV_32F); | |||||
float *kernal = (float*)kernal_gaussian.data; | |||||
// kernal+=windowsSize; | |||||
int r = windowsSize/2; | |||||
for (int i = 0; i < size; i++) { | |||||
float avg = 0.00f; | |||||
for (int j = 0; j < windowsSize; j++) { | |||||
if(i+j-r>0&&i+j+r<size-1) | |||||
avg += filterd[i + j-r]*kernal[j]; | |||||
} | |||||
// avg = avg / windowsSize; | |||||
angle_list[i] = avg; | |||||
} | |||||
delete filterd; | |||||
} | |||||
void PlateSegmentation::templateMatchFinding(const cv::Mat &respones,int windowsWidth,std::pair<float,std::vector<int>> &candidatePts){ | |||||
int rows = respones.rows; | |||||
int cols = respones.cols; | |||||
float *data = (float*)respones.data; | |||||
float *engNum_prob = data; | |||||
float *false_prob = data+cols; | |||||
float *ch_prob = data+cols*2; | |||||
avgfilter(engNum_prob,cols,5); | |||||
avgfilter(false_prob,cols,5); | |||||
// avgfilter(ch_prob,cols,5); | |||||
std::vector<int> candidate_pts(7); | |||||
#ifdef DEBUG | |||||
drawHist(engNum_prob,cols,"engNum_prob"); | |||||
drawHist(false_prob,cols,"false_prob"); | |||||
drawHist(ch_prob,cols,"ch_prob"); | |||||
cv::waitKey(0); | |||||
#endif | |||||
int cp_list[7]; | |||||
float loss_selected = -10; | |||||
for(int start = 0 ; start < 20 ; start+=2) | |||||
for(int width = windowsWidth-5; width < windowsWidth+5 ; width++ ){ | |||||
for(int interval = windowsWidth/2; interval < windowsWidth; interval++) | |||||
{ | |||||
int cp1_ch = start; | |||||
int cp2_p0 = cp1_ch+ width; | |||||
int cp3_p1 = cp2_p0+ width + interval; | |||||
int cp4_p2 = cp3_p1 + width; | |||||
int cp5_p3 = cp4_p2 + width+1; | |||||
int cp6_p4 = cp5_p3 + width+2; | |||||
int cp7_p5= cp6_p4+ width+2; | |||||
int md1 = (cp1_ch+cp2_p0)>>1; | |||||
int md2 = (cp2_p0+cp3_p1)>>1; | |||||
int md3 = (cp3_p1+cp4_p2)>>1; | |||||
int md4 = (cp4_p2+cp5_p3)>>1; | |||||
int md5 = (cp5_p3+cp6_p4)>>1; | |||||
int md6 = (cp6_p4+cp7_p5)>>1; | |||||
if(cp7_p5>=cols) | |||||
continue; | |||||
// float loss = ch_prob[cp1_ch]+ | |||||
// engNum_prob[cp2_p0] +engNum_prob[cp3_p1]+engNum_prob[cp4_p2]+engNum_prob[cp5_p3]+engNum_prob[cp6_p4] +engNum_prob[cp7_p5] | |||||
// + (false_prob[md2]+false_prob[md3]+false_prob[md4]+false_prob[md5]+false_prob[md5] + false_prob[md6]); | |||||
float loss = ch_prob[cp1_ch]*3 -(false_prob[cp3_p1]+false_prob[cp4_p2]+false_prob[cp5_p3]+false_prob[cp6_p4]+false_prob[cp7_p5]); | |||||
if(loss>loss_selected) | |||||
{ | |||||
loss_selected = loss; | |||||
cp_list[0]= cp1_ch; | |||||
cp_list[1]= cp2_p0; | |||||
cp_list[2]= cp3_p1; | |||||
cp_list[3]= cp4_p2; | |||||
cp_list[4]= cp5_p3; | |||||
cp_list[5]= cp6_p4; | |||||
cp_list[6]= cp7_p5; | |||||
} | |||||
} | |||||
} | |||||
candidate_pts[0] = cp_list[0]; | |||||
candidate_pts[1] = cp_list[1]; | |||||
candidate_pts[2] = cp_list[2]; | |||||
candidate_pts[3] = cp_list[3]; | |||||
candidate_pts[4] = cp_list[4]; | |||||
candidate_pts[5] = cp_list[5]; | |||||
candidate_pts[6] = cp_list[6]; | |||||
candidatePts.first = loss_selected; | |||||
candidatePts.second = candidate_pts; | |||||
}; | |||||
void PlateSegmentation::segmentPlateBySlidingWindows(cv::Mat &plateImage,int windowsWidth,int stride,cv::Mat &respones){ | |||||
// cv::resize(plateImage,plateImage,cv::Size(136,36)); | |||||
cv::Mat plateImageGray; | |||||
cv::cvtColor(plateImage,plateImageGray,cv::COLOR_BGR2GRAY); | |||||
int padding = plateImage.cols-136 ; | |||||
// int padding = 0 ; | |||||
int height = plateImage.rows - 1; | |||||
int width = plateImage.cols - 1 - padding; | |||||
for(int i = 0 ; i < width - windowsWidth +1 ; i +=stride) | |||||
{ | |||||
cv::Rect roi(i,0,windowsWidth,height); | |||||
cv::Mat roiImage = plateImageGray(roi); | |||||
cv::Mat response = classifyResponse(roiImage); | |||||
respones.push_back(response); | |||||
} | |||||
respones = respones.t(); | |||||
// std::pair<float,std::vector<int>> images ; | |||||
// | |||||
// | |||||
// std::cout<<images.first<<" "; | |||||
// for(int i = 0 ; i < images.second.size() ; i++) | |||||
// { | |||||
// std::cout<<images.second[i]<<" "; | |||||
//// cv::line(plateImageGray,cv::Point(images.second[i],0),cv::Point(images.second[i],36),cv::Scalar(255,255,255),1); //DEBUG | |||||
// } | |||||
// int w = images.second[5] - images.second[4]; | |||||
// cv::line(plateImageGray,cv::Point(images.second[5]+w,0),cv::Point(images.second[5]+w,36),cv::Scalar(255,255,255),1); //DEBUG | |||||
// cv::line(plateImageGray,cv::Point(images.second[5]+2*w,0),cv::Point(images.second[5]+2*w,36),cv::Scalar(255,255,255),1); //DEBUG | |||||
// RefineRegion(plateImageGray,images.second,5); | |||||
// std::cout<<w<<std::endl; | |||||
// std::cout<<<<std::endl; | |||||
// cv::resize(plateImageGray,plateImageGray,cv::Size(600,100)); | |||||
} | |||||
// void filterGaussian(cv::Mat &respones,float sigma){ | |||||
// | |||||
// } | |||||
void PlateSegmentation::segmentPlatePipline(PlateInfo &plateInfo,int stride,std::vector<cv::Rect> &Char_rects){ | |||||
cv::Mat plateImage = plateInfo.getPlateImage(); // get src image . | |||||
cv::Mat plateImageGray; | |||||
cv::cvtColor(plateImage,plateImageGray,cv::COLOR_BGR2GRAY); | |||||
//do binarzation | |||||
// | |||||
std::pair<float,std::vector<int>> sections ; // segment points variables . | |||||
cv::Mat respones; //three response of every sub region from origin image . | |||||
segmentPlateBySlidingWindows(plateImage,DEFAULT_WIDTH,1,respones); | |||||
templateMatchFinding(respones,DEFAULT_WIDTH/stride,sections); | |||||
for(int i = 0; i < sections.second.size() ; i++) | |||||
{ | |||||
sections.second[i]*=stride; | |||||
} | |||||
// std::cout<<sections<<std::endl; | |||||
refineRegion(plateImageGray,sections.second,5,Char_rects); | |||||
#ifdef DEBUG | |||||
for(int i = 0 ; i < sections.second.size() ; i++) | |||||
{ | |||||
std::cout<<sections.second[i]<<" "; | |||||
cv::line(plateImageGray,cv::Point(sections.second[i],0),cv::Point(sections.second[i],36),cv::Scalar(255,255,255),1); //DEBUG | |||||
} | |||||
cv::imshow("plate",plateImageGray); | |||||
cv::waitKey(0); | |||||
#endif | |||||
// cv::waitKey(0); | |||||
} | |||||
void PlateSegmentation::ExtractRegions(PlateInfo &plateInfo,std::vector<cv::Rect> &rects){ | |||||
cv::Mat plateImage = plateInfo.getPlateImage(); | |||||
for(int i = 0 ; i < rects.size() ; i++){ | |||||
cv::Mat charImage; | |||||
plateImage(rects[i]).copyTo(charImage); | |||||
if(charImage.channels()) | |||||
cv::cvtColor(charImage,charImage,cv::COLOR_BGR2GRAY); | |||||
// cv::imshow("image",charImage); | |||||
// cv::waitKey(0); | |||||
cv::equalizeHist(charImage,charImage); | |||||
// | |||||
// | |||||
std::pair<CharType,cv::Mat> char_instance; | |||||
if(i == 0 ){ | |||||
char_instance.first = CHINESE; | |||||
} else if(i == 1){ | |||||
char_instance.first = LETTER; | |||||
} | |||||
else{ | |||||
char_instance.first = LETTER_NUMS; | |||||
} | |||||
char_instance.second = charImage; | |||||
plateInfo.appendPlateChar(char_instance); | |||||
} | |||||
} | |||||
}//namespace pr |
@@ -0,0 +1,23 @@ | |||||
// | |||||
// Created by Jack Yu on 22/10/2017. | |||||
// | |||||
#include "Recognizer.h" | |||||
namespace pr{ | |||||
void GeneralRecognizer::SegmentBasedSequenceRecognition(PlateInfo &plateinfo){ | |||||
for(auto char_instance:plateinfo.plateChars) | |||||
{ | |||||
std::pair<CharType,cv::Mat> res; | |||||
if(char_instance.second.rows*char_instance.second.cols>40) { | |||||
label code_table = recognizeCharacter(char_instance.second); | |||||
res.first = char_instance.first; | |||||
code_table.copyTo(res.second); | |||||
plateinfo.appendPlateCoding(res); | |||||
} else{ | |||||
res.first = INVALID; | |||||
plateinfo.appendPlateCoding(res); | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,89 @@ | |||||
// | |||||
// Created by Jack Yu on 28/11/2017. | |||||
// | |||||
#include "SegmentationFreeRecognizer.h" | |||||
namespace pr { | |||||
SegmentationFreeRecognizer::SegmentationFreeRecognizer(std::string prototxt, std::string caffemodel) { | |||||
net = cv::dnn::readNetFromCaffe(prototxt, caffemodel); | |||||
} | |||||
inline int judgeCharRange(int id) | |||||
{return id<31 || id>63; | |||||
} | |||||
std::pair<std::string,float> decodeResults(cv::Mat code_table,std::vector<std::string> mapping_table,float thres) | |||||
{ | |||||
cv::MatSize mtsize = code_table.size; | |||||
int sequencelength = mtsize[2]; | |||||
int labellength = mtsize[1]; | |||||
cv::transpose(code_table.reshape(1,1).reshape(1,labellength),code_table); | |||||
std::string name = ""; | |||||
std::vector<int> seq(sequencelength); | |||||
std::vector<std::pair<int,float>> seq_decode_res; | |||||
for(int i = 0 ; i < sequencelength; i++) { | |||||
float *fstart = ((float *) (code_table.data) + i * labellength ); | |||||
int id = std::max_element(fstart,fstart+labellength) - fstart; | |||||
seq[i] =id; | |||||
} | |||||
float sum_confidence = 0; | |||||
int plate_lenghth = 0 ; | |||||
for(int i = 0 ; i< sequencelength ; i++) | |||||
{ | |||||
if(seq[i]!=labellength-1 && (i==0 || seq[i]!=seq[i-1])) | |||||
{ | |||||
float *fstart = ((float *) (code_table.data) + i * labellength ); | |||||
float confidence = *(fstart+seq[i]); | |||||
std::pair<int,float> pair_(seq[i],confidence); | |||||
seq_decode_res.push_back(pair_); | |||||
} | |||||
} | |||||
int i = 0; | |||||
if (seq_decode_res.size()>1 && judgeCharRange(seq_decode_res[0].first) && judgeCharRange(seq_decode_res[1].first)) | |||||
{ | |||||
i=2; | |||||
int c = seq_decode_res[0].second<seq_decode_res[1].second; | |||||
name+=mapping_table[seq_decode_res[c].first]; | |||||
sum_confidence+=seq_decode_res[c].second; | |||||
plate_lenghth++; | |||||
} | |||||
for(; i < seq_decode_res.size();i++) | |||||
{ | |||||
name+=mapping_table[seq_decode_res[i].first]; | |||||
sum_confidence +=seq_decode_res[i].second; | |||||
plate_lenghth++; | |||||
} | |||||
std::pair<std::string,float> res; | |||||
res.second = sum_confidence/plate_lenghth; | |||||
res.first = name; | |||||
return res; | |||||
} | |||||
std::string decodeResults(cv::Mat code_table,std::vector<std::string> mapping_table) | |||||
{ | |||||
cv::MatSize mtsize = code_table.size; | |||||
int sequencelength = mtsize[2]; | |||||
int labellength = mtsize[1]; | |||||
cv::transpose(code_table.reshape(1,1).reshape(1,labellength),code_table); | |||||
std::string name = ""; | |||||
std::vector<int> seq(sequencelength); | |||||
for(int i = 0 ; i < sequencelength; i++) { | |||||
float *fstart = ((float *) (code_table.data) + i * labellength ); | |||||
int id = std::max_element(fstart,fstart+labellength) - fstart; | |||||
seq[i] =id; | |||||
} | |||||
for(int i = 0 ; i< sequencelength ; i++) | |||||
{ | |||||
if(seq[i]!=labellength-1 && (i==0 || seq[i]!=seq[i-1])) | |||||
name+=mapping_table[seq[i]]; | |||||
} | |||||
return name; | |||||
} | |||||
std::pair<std::string,float> SegmentationFreeRecognizer::SegmentationFreeForSinglePlate(cv::Mat Image,std::vector<std::string> mapping_table) { | |||||
cv::transpose(Image,Image); | |||||
cv::Mat inputBlob = cv::dnn::blobFromImage(Image, 1 / 255.0, cv::Size(40,160)); | |||||
net.setInput(inputBlob, "data"); | |||||
cv::Mat char_prob_mat = net.forward(); | |||||
return decodeResults(char_prob_mat,mapping_table,0.00); | |||||
} | |||||
} |
@@ -0,0 +1,67 @@ | |||||
// | |||||
// Created by Jack Yu on 04/04/2017. | |||||
// | |||||
#include <opencv2/opencv.hpp> | |||||
namespace util{ | |||||
template <class T> void swap ( T& a, T& b ) | |||||
{ | |||||
T c(a); a=b; b=c; | |||||
} | |||||
template <class T> T min(T& a,T& b ) | |||||
{ | |||||
return a>b?b:a; | |||||
} | |||||
cv::Mat cropFromImage(const cv::Mat &image,cv::Rect rect){ | |||||
int w = image.cols-1; | |||||
int h = image.rows-1; | |||||
rect.x = std::max(rect.x,0); | |||||
rect.y = std::max(rect.y,0); | |||||
rect.height = std::min(rect.height,h-rect.y); | |||||
rect.width = std::min(rect.width,w-rect.x); | |||||
cv::Mat temp(rect.size(), image.type()); | |||||
cv::Mat cropped; | |||||
temp = image(rect); | |||||
temp.copyTo(cropped); | |||||
return cropped; | |||||
} | |||||
cv::Mat cropBox2dFromImage(const cv::Mat &image,cv::RotatedRect rect) | |||||
{ | |||||
cv::Mat M, rotated, cropped; | |||||
float angle = rect.angle; | |||||
cv::Size rect_size(rect.size.width,rect.size.height); | |||||
if (rect.angle < -45.) { | |||||
angle += 90.0; | |||||
swap(rect_size.width, rect_size.height); | |||||
} | |||||
M = cv::getRotationMatrix2D(rect.center, angle, 1.0); | |||||
cv::warpAffine(image, rotated, M, image.size(), cv::INTER_CUBIC); | |||||
cv::getRectSubPix(rotated, rect_size, rect.center, cropped); | |||||
return cropped; | |||||
} | |||||
cv::Mat calcHist(const cv::Mat &image) | |||||
{ | |||||
cv::Mat hsv; | |||||
std::vector<cv::Mat> hsv_planes; | |||||
cv::cvtColor(image,hsv,cv::COLOR_BGR2HSV); | |||||
cv::split(hsv,hsv_planes); | |||||
cv::Mat hist; | |||||
int histSize = 256; | |||||
float range[] = {0,255}; | |||||
const float* histRange = {range}; | |||||
cv::calcHist( &hsv_planes[0], 1, 0, cv::Mat(), hist, 1, &histSize, &histRange,true, true); | |||||
return hist; | |||||
} | |||||
float computeSimilir(const cv::Mat &A,const cv::Mat &B) | |||||
{ | |||||
cv::Mat histA,histB; | |||||
histA = calcHist(A); | |||||
histB = calcHist(B); | |||||
return cv::compareHist(histA,histB,CV_COMP_CORREL); | |||||
} | |||||
}//namespace util |
@@ -0,0 +1,121 @@ | |||||
// | |||||
// Created by Coleflowers on 20/06/2018. | |||||
// | |||||
#include <phpcpp.h> | |||||
#include <iostream> | |||||
#include "PlateSegmentation.h" | |||||
#include "CNNRecognizer.h" | |||||
#include "Recognizer.h" | |||||
#include "PlateDetection.h" | |||||
#include "FastDeskew.h" | |||||
#include "FineMapping.h" | |||||
std::vector<std::string> chars{"京","沪","津","渝","冀","晋","蒙","辽","吉","黑","苏","浙","皖","闽","赣","鲁","豫","鄂","湘","粤","桂","琼","川","贵","云","藏","陕","甘","青","宁","新","0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F","G","H","J","K","L","M","N","P","Q","R","S","T","U","V","W","X","Y","Z"}; | |||||
/** | |||||
* 车牌图片识别 | |||||
* @param imgpath 图片的绝对路径 | |||||
* @return 车牌号 图片路径的图片不存在的话 返回空值 | |||||
*/ | |||||
cv::String scan(const char *imgpath){ | |||||
// 读取图片获取车牌的粗略部分 | |||||
cv::Mat image = cv::imread(imgpath); | |||||
if(!image.data) { | |||||
printf("No img data!\n"); | |||||
return ""; | |||||
} | |||||
pr::PlateDetection plateDetection("/usr/platescan/src/lpr/model/cascade.xml"); | |||||
std::vector<pr::PlateInfo> plates; | |||||
plateDetection.plateDetectionRough(image,plates); | |||||
// todo 处理发现的每一个车牌信息 | |||||
// 目前只处理了一组 | |||||
cv::Mat img; | |||||
for(pr::PlateInfo platex:plates) { | |||||
// 暂时不保存了 | |||||
//cv::imwrite("res/mmm.png",platex.getPlateImage()); | |||||
img = platex.getPlateImage(); | |||||
} | |||||
// 车牌部分 | |||||
cv::Mat image_finemapping = pr::FineMapping::FineMappingVertical(img); | |||||
pr::FineMapping finemapper = pr::FineMapping("/usr/platescan/src/lpr/model/HorizonalFinemapping.prototxt","/usr/platescan/src/lpr/model/HorizonalFinemapping.caffemodel"); | |||||
// | |||||
image_finemapping = pr::fastdeskew(image_finemapping, 5); | |||||
// image_finemapping = finemapper.FineMappingHorizon(image_finemapping,0,-3); | |||||
// Android 代码里这个是 | |||||
// 改了之后部分图片数据精度发生变化 ,比如 字母精度有了,但是汉字又错了 | |||||
image_finemapping = finemapper.FineMappingHorizon(image_finemapping,2,5); | |||||
// 暂时不保存了 | |||||
//cv::imwrite("res/m222222222.png",image_finemapping); | |||||
// 设置尺寸 识别 | |||||
cv::Mat demo = image_finemapping; | |||||
cv::resize(demo,demo,cv::Size(136,36)); | |||||
//cv::imwrite("res/m333333.png",demo); | |||||
cv::Mat respones; | |||||
pr::PlateSegmentation plateSegmentation("/usr/platescan/src/lpr/model/Segmentation.prototxt","/usr/platescan/src/lpr/model/Segmentation.caffemodel"); | |||||
pr::PlateInfo plate; | |||||
plate.setPlateImage(demo); | |||||
std::vector<cv::Rect> rects; | |||||
plateSegmentation.segmentPlatePipline(plate,1,rects); | |||||
plateSegmentation.ExtractRegions(plate,rects); | |||||
pr::GeneralRecognizer *recognizer = new pr::CNNRecognizer("/usr/platescan/src/lpr/model/CharacterRecognization.prototxt","/usr/platescan/src/lpr/model/CharacterRecognization.caffemodel"); | |||||
recognizer->SegmentBasedSequenceRecognition(plate); | |||||
//std::cout<<plate.decodePlateNormal(chars)<<std::endl; | |||||
plate.decodePlateNormal(chars); | |||||
cv::String txt = plate.getPlateName(); | |||||
// printf("res:%s\n", txt.c_str()); | |||||
// | |||||
// 写入结果到文件 | |||||
// FILE *fp; | |||||
// fp = fopen(resSaveFile, "wb+"); | |||||
// fprintf(fp, "%s", txt.c_str()); | |||||
// fclose(fp); | |||||
// | |||||
delete(recognizer); | |||||
return txt; | |||||
} | |||||
Php::Value myFunction(Php::Parameters ¶ms) { | |||||
std::string str = params[0]; | |||||
const char *str_c = str.c_str(); | |||||
cv::String res = scan(str_c); | |||||
return res.c_str(); | |||||
} | |||||
/** | |||||
* tell the compiler that the get_module is a pure C function | |||||
*/ | |||||
extern "C" { | |||||
/** | |||||
* Function that is called by PHP right after the PHP process | |||||
* has started, and that returns an address of an internal PHP | |||||
* strucure with all the details and features of your extension | |||||
* | |||||
* @return void* a pointer to an address that is understood by PHP | |||||
*/ | |||||
PHPCPP_EXPORT void *get_module() { | |||||
// static(!) Php::Extension object that should stay in memory | |||||
// for the entire duration of the process (that's why it's static) | |||||
static Php::Extension extension("platescan", "1.0"); | |||||
// @todo add your own functions, classes, namespaces to the extension | |||||
extension.add<myFunction>("platescan", {Php::ByRef("string", Php::Type::String)}); | |||||
// return the extension | |||||
return extension; | |||||
} | |||||
} |
@@ -0,0 +1,2 @@ | |||||
extension=platescan.so | |||||
@@ -0,0 +1,13 @@ | |||||
<?php | |||||
// | |||||
// Created by Coleflowers on 20/06/2018. | |||||
// | |||||
/** | |||||
* 车牌识别PHP扩展程序测试 | |||||
*/ | |||||
$path = realpath("demo.png"); | |||||
$res = platescan($path); | |||||
var_dump($res); | |||||
?> |