Browse Source

Merge pull request #80 from justid/master

PHP版代码重构了一下,新增可信度要求和模型路径参数
pull/1/MERGE
syan GitHub 6 years ago
parent
commit
0596aaaffd
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 12867 additions and 88 deletions
  1. +1
    -0
      .gitignore
  2. +4
    -13
      Prj-PHP/CMakeLists.txt
  3. +37
    -3
      Prj-PHP/README.MD
  4. +56
    -70
      Prj-PHP/main.cpp
  5. BIN
      Prj-PHP/tests/model/CharacterRecognization.caffemodel
  6. +123
    -0
      Prj-PHP/tests/model/CharacterRecognization.prototxt
  7. BIN
      Prj-PHP/tests/model/HorizonalFinemapping.caffemodel
  8. +95
    -0
      Prj-PHP/tests/model/HorizonalFinemapping.prototxt
  9. BIN
      Prj-PHP/tests/model/Segmentation.caffemodel
  10. +114
    -0
      Prj-PHP/tests/model/Segmentation.prototxt
  11. BIN
      Prj-PHP/tests/model/SegmentationFree.caffemodel
  12. +318
    -0
      Prj-PHP/tests/model/SegmentationFree.prototxt
  13. +12117
    -0
      Prj-PHP/tests/model/cascade.xml
  14. +2
    -2
      Prj-PHP/tests/platescan.php

+ 1
- 0
.gitignore View File

@@ -6,3 +6,4 @@
Prj-Linux/lpr/TEST_*
Prj-Linux/*build*/
*.pyc
/Prj-PHP/build

+ 4
- 13
Prj-PHP/CMakeLists.txt View File

@@ -1,21 +1,12 @@
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)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
find_package(OpenCV 3.3.0 REQUIRED)
include_directories( ${OpenCV_INCLUDE_DIRS})
include_directories(include)


add_library( # Sets the name of the library.
platescan

@@ -42,10 +33,10 @@ target_link_libraries(
${OpenCV_LIBS}
)

execute_process(COMMAND ${PHP_CONFIG} --extension-dir
execute_process(COMMAND php-config --extension-dir
OUTPUT_VARIABLE LIBRARY_DIR)

message("Test ${LIBRARY_DIR} failed.")
message("PHP_EXTENSIONS_DIR ${LIBRARY_DIR}")

add_custom_command(TARGET platescan
POST_BUILD


+ 37
- 3
Prj-PHP/README.MD View File

@@ -1,9 +1,29 @@

# Prj-PHP

HyperLPR 在PHP扩展程序中的实现,核心代码拷贝了 Prj-Linux 中庾金科大牛的代码。我做的这部分工作主要是配置编译成PHP扩展程序。

## CPP 依赖

+ Opencv 3.3
+ PHPCPP

## Linux/Mac 编译

```bash
mkdir build
cd build
cmake ../
make -j
```

## DEMO

```php
$path = realpath("demo.png");
$res = platescan($path);
// 也可编译完后直接在tests目录直接运行 php platescan.php
$path = realpath("demo.png"); // 图片文件所在路径,realpath转为绝对路径
$model = realpath("model"); // 模型资源所在文件夹
$res = platescan($path, $model, 0.8);
var_dump($res); // string(9) "苏ED0N19"

```
@@ -12,5 +32,19 @@ var_dump($res); // string(9) "苏ED0N19"

![image](./tests/demo.png)

## 测试备忘

经多次测试发现部分图片难以识别或者容易识别错误,经过筛选反推出如下规律:

### 识别精度高的图片

+ 图片中包含有整个车身
+ 图片清晰度较高,不模糊
+ 图片拍摄角度为正面拍摄,车牌在图片中没有较大幅度的倾斜
+ 图片中的车牌在整个图片中所占位置较小

### 识别精度低的图片

#### 感谢各位大牛!
+ 图片模糊,程序未能裁切出正确的车牌,或者裁切后因图片太模糊而无法识别(此处应该可以通过训练更多样本解决)
+ 图片中的车牌在整个图片中占了1/3的位置甚至更高
+ 图片拍摄角度为俯角拍摄,车牌在图片中是倾斜着的

+ 56
- 70
Prj-PHP/main.cpp View File

@@ -1,5 +1,6 @@
//
// Created by Coleflowers on 20/06/2018.
// Updated by Justid on 02/07/2018.
//
#include <phpcpp.h>
#include <iostream>
@@ -10,88 +11,69 @@
#include "PlateDetection.h"
#include "FastDeskew.h"
#include "FineMapping.h"
#include "Pipeline.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"};
/* 关闭原opencv报错输出 */
int handleError( int status, const char* func_name,
const char* err_msg, const char* file_name,
int line, void* userdata )
{
//Do nothing -- will suppress console output
return 0; //Return value is not used
}

/**
* 车牌图片识别
* @param imgpath 图片的绝对路径
* @params imgpath 图片的绝对路径
* @params modelpath 识别模型所在文件夹路径
* @params confidence 可信度要求,可选参数,默认值0.75
* @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::String scan(std::string imgpath, std::string modelpath, double confidence){
cv::redirectError(handleError);
try {
pr::PipelinePR prc(modelpath+"/cascade.xml",
modelpath+"/HorizonalFinemapping.prototxt",modelpath+"/HorizonalFinemapping.caffemodel",
modelpath+"/Segmentation.prototxt",modelpath+"/Segmentation.caffemodel",
modelpath+"/CharacterRecognization.prototxt",modelpath+"/CharacterRecognization.caffemodel",
modelpath+"/SegmentationFree.prototxt",modelpath+"/SegmentationFree.caffemodel"
);
cv::Mat image = cv::imread(imgpath);
std::vector<pr::PlateInfo> res = prc.RunPiplineAsImage(image,pr::SEGMENTATION_FREE_METHOD);

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);
cv::String platenum = "";
for(auto st:res) {
if(st.confidence>confidence) {
platenum = st.getPlateName();
break;
}
}

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());
return platenum;
}
catch( cv::Exception& e )
{
const char* err_msg = e.what();
throw Php::Exception(err_msg);
}

//
// 写入结果到文件
// FILE *fp;
// fp = fopen(resSaveFile, "wb+");
// fprintf(fp, "%s", txt.c_str());
// fclose(fp);
//
delete(recognizer);
return txt;
}

Php::Value myFunction(Php::Parameters &params) {

std::string str = params[0];
const char *str_c = str.c_str();
Php::Value funcWrapper(Php::Parameters &params) {
// 图片路径
std::string img = params[0];
// 模型路径(文件夹)
std::string model = params[1];
// 可信度要求
double confidence = 0.75;

if (params.size() == 3){
confidence = (double)params[2];
}

cv::String res = scan(str_c);
return res.c_str();
cv::String res = scan(img, model, confidence);
return res.c_str();
}


@@ -113,7 +95,11 @@ extern "C" {
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)});
extension.add<funcWrapper>("platescan", {
Php::ByVal("imgpath", Php::Type::String, true),
Php::ByVal("modelpath", Php::Type::String, true),
Php::ByVal("confidence", Php::Type::Float, false)
});
// return the extension
return extension;


BIN
Prj-PHP/tests/model/CharacterRecognization.caffemodel View File


+ 123
- 0
Prj-PHP/tests/model/CharacterRecognization.prototxt View File

@@ -0,0 +1,123 @@
input: "data"
input_dim: 1
input_dim: 1
input_dim: 30
input_dim: 14
layer {
name: "conv2d_1"
type: "Convolution"
bottom: "data"
top: "conv2d_1"
convolution_param {
num_output: 32
bias_term: true
pad: 0
kernel_size: 3
stride: 1
}
}
layer {
name: "activation_1"
type: "ReLU"
bottom: "conv2d_1"
top: "activation_1"
}
layer {
name: "max_pooling2d_1"
type: "Pooling"
bottom: "activation_1"
top: "max_pooling2d_1"
pooling_param {
pool: MAX
kernel_size: 2
stride: 2
pad: 0
}
}
layer {
name: "conv2d_2"
type: "Convolution"
bottom: "max_pooling2d_1"
top: "conv2d_2"
convolution_param {
num_output: 64
bias_term: true
pad: 0
kernel_size: 3
stride: 1
}
}
layer {
name: "activation_2"
type: "ReLU"
bottom: "conv2d_2"
top: "activation_2"
}
layer {
name: "max_pooling2d_2"
type: "Pooling"
bottom: "activation_2"
top: "max_pooling2d_2"
pooling_param {
pool: MAX
kernel_size: 2
stride: 2
pad: 0
}
}
layer {
name: "conv2d_3"
type: "Convolution"
bottom: "max_pooling2d_2"
top: "conv2d_3"
convolution_param {
num_output: 128
bias_term: true
pad: 0
kernel_size: 2
stride: 1
}
}
layer {
name: "activation_3"
type: "ReLU"
bottom: "conv2d_3"
top: "activation_3"
}
layer {
name: "flatten_1"
type: "Flatten"
bottom: "activation_3"
top: "flatten_1"
}
layer {
name: "dense_1"
type: "InnerProduct"
bottom: "flatten_1"
top: "dense_1"
inner_product_param {
num_output: 256
}
}
layer {
name: "relu2"
type: "ReLU"
bottom: "dense_1"
top: "relu2"
}
layer {
name: "dense2"
type: "InnerProduct"
bottom: "relu2"
top: "dense2"
inner_product_param {
num_output: 65
}
}

layer {
name: "prob"
type: "Softmax"
bottom: "dense2"
top: "prob"
}

BIN
Prj-PHP/tests/model/HorizonalFinemapping.caffemodel View File


+ 95
- 0
Prj-PHP/tests/model/HorizonalFinemapping.prototxt View File

@@ -0,0 +1,95 @@
input: "data"
input_dim: 1
input_dim: 3
input_dim: 16
input_dim: 66
layer {
name: "conv1"
type: "Convolution"
bottom: "data"
top: "conv1"
convolution_param {
num_output: 10
bias_term: true
pad: 0
kernel_size: 3
stride: 1
}
}
layer {
name: "relu1"
type: "ReLU"
bottom: "conv1"
top: "conv1"
}
layer {
name: "max_pooling2d_3"
type: "Pooling"
bottom: "conv1"
top: "max_pooling2d_3"
pooling_param {
pool: MAX
kernel_size: 2
stride: 2
pad: 0
}
}
layer {
name: "conv2"
type: "Convolution"
bottom: "max_pooling2d_3"
top: "conv2"
convolution_param {
num_output: 16
bias_term: true
pad: 0
kernel_size: 3
stride: 1
}
}
layer {
name: "relu2"
type: "ReLU"
bottom: "conv2"
top: "conv2"
}
layer {
name: "conv3"
type: "Convolution"
bottom: "conv2"
top: "conv3"
convolution_param {
num_output: 32
bias_term: true
pad: 0
kernel_size: 3
stride: 1
}
}
layer {
name: "relu3"
type: "ReLU"
bottom: "conv3"
top: "conv3"
}
layer {
name: "flatten_2"
type: "Flatten"
bottom: "conv3"
top: "flatten_2"
}
layer {
name: "dense"
type: "InnerProduct"
bottom: "flatten_2"
top: "dense"
inner_product_param {
num_output: 2
}
}
layer {
name: "relu4"
type: "ReLU"
bottom: "dense"
top: "dense"
}

BIN
Prj-PHP/tests/model/Segmentation.caffemodel View File


+ 114
- 0
Prj-PHP/tests/model/Segmentation.prototxt View File

@@ -0,0 +1,114 @@
input: "data"
input_dim: 1
input_dim: 1
input_dim: 22
input_dim: 22
layer {
name: "conv2d_12"
type: "Convolution"
bottom: "data"
top: "conv2d_12"
convolution_param {
num_output: 16
bias_term: true
pad: 0
kernel_size: 3
stride: 1
}
}
layer {
name: "activation_18"
type: "ReLU"
bottom: "conv2d_12"
top: "activation_18"
}
layer {
name: "max_pooling2d_10"
type: "Pooling"
bottom: "activation_18"
top: "max_pooling2d_10"
pooling_param {
pool: MAX
kernel_size: 2
stride: 2
pad: 0
}
}
layer {
name: "conv2d_13"
type: "Convolution"
bottom: "max_pooling2d_10"
top: "conv2d_13"
convolution_param {
num_output: 16
bias_term: true
pad: 0
kernel_size: 3
stride: 1
}
}
layer {
name: "activation_19"
type: "ReLU"
bottom: "conv2d_13"
top: "activation_19"
}
layer {
name: "max_pooling2d_11"
type: "Pooling"
bottom: "activation_19"
top: "max_pooling2d_11"
pooling_param {
pool: MAX
kernel_size: 2
stride: 2
pad: 0
}
}
layer {
name: "flatten_6"
type: "Flatten"
bottom: "max_pooling2d_11"
top: "flatten_6"
}
layer {
name: "dense_9"
type: "InnerProduct"
bottom: "flatten_6"
top: "dense_9"
inner_product_param {
num_output: 256
}
}
layer {
name: "dropout_9"
type: "Dropout"
bottom: "dense_9"
top: "dropout_9"
dropout_param {
dropout_ratio: 0.5
}
}
layer {
name: "activation_20"
type: "ReLU"
bottom: "dropout_9"
top: "activation_20"
}
layer {
name: "dense_10"
type: "InnerProduct"
bottom: "activation_20"
top: "dense_10"
inner_product_param {
num_output: 3
}
}


layer {
name: "prob"
type: "Softmax"
bottom: "dense_10"
top: "prob"
}

BIN
Prj-PHP/tests/model/SegmentationFree.caffemodel View File


+ 318
- 0
Prj-PHP/tests/model/SegmentationFree.prototxt View File

@@ -0,0 +1,318 @@
input: "data"
input_dim: 1
input_dim: 3
input_dim: 160
input_dim: 40
layer {
name: "conv0"
type: "Convolution"
bottom: "data"
top: "conv0"
convolution_param {
num_output: 32
bias_term: true
pad_h: 1
pad_w: 1
kernel_h: 3
kernel_w: 3
stride_h: 1
stride_w: 1
}
}
layer {
name: "bn0"
type: "BatchNorm"
bottom: "conv0"
top: "bn0"
batch_norm_param {
moving_average_fraction: 0.99
eps: 0.001
}
}
layer {
name: "bn0_scale"
type: "Scale"
bottom: "bn0"
top: "bn0"
scale_param {
bias_term: true
}
}
layer {
name: "relu0"
type: "ReLU"
bottom: "bn0"
top: "bn0"
}
layer {
name: "pool0"
type: "Pooling"
bottom: "bn0"
top: "pool0"
pooling_param {
pool: MAX
kernel_h: 2
kernel_w: 2
stride_h: 2
stride_w: 2
pad_h: 0
pad_w: 0
}
}
layer {
name: "conv1"
type: "Convolution"
bottom: "pool0"
top: "conv1"
convolution_param {
num_output: 64
bias_term: true
pad_h: 1
pad_w: 1
kernel_h: 3
kernel_w: 3
stride_h: 1
stride_w: 1
}
}
layer {
name: "bn1"
type: "BatchNorm"
bottom: "conv1"
top: "bn1"
batch_norm_param {
moving_average_fraction: 0.99
eps: 0.001
}
}
layer {
name: "bn1_scale"
type: "Scale"
bottom: "bn1"
top: "bn1"
scale_param {
bias_term: true
}
}
layer {
name: "relu1"
type: "ReLU"
bottom: "bn1"
top: "bn1"
}
layer {
name: "pool1"
type: "Pooling"
bottom: "bn1"
top: "pool1"
pooling_param {
pool: MAX
kernel_h: 2
kernel_w: 2
stride_h: 2
stride_w: 2
pad_h: 0
pad_w: 0
}
}
layer {
name: "conv2"
type: "Convolution"
bottom: "pool1"
top: "conv2"
convolution_param {
num_output: 128
bias_term: true
pad_h: 1
pad_w: 1
kernel_h: 3
kernel_w: 3
stride_h: 1
stride_w: 1
}
}
layer {
name: "bn2"
type: "BatchNorm"
bottom: "conv2"
top: "bn2"
batch_norm_param {
moving_average_fraction: 0.99
eps: 0.001
}
}
layer {
name: "bn2_scale"
type: "Scale"
bottom: "bn2"
top: "bn2"
scale_param {
bias_term: true
}
}
layer {
name: "relu2"
type: "ReLU"
bottom: "bn2"
top: "bn2"
}
layer {
name: "pool2"
type: "Pooling"
bottom: "bn2"
top: "pool2"
pooling_param {
pool: MAX
kernel_h: 2
kernel_w: 2
stride_h: 2
stride_w: 2
pad_h: 0
pad_w: 0
}
}
layer {
name: "conv_512_15"
type: "Convolution"
bottom: "pool2"
top: "conv_512_15"
convolution_param {
num_output: 512
bias_term: true
pad_h: 0
pad_w: 0
kernel_h: 1
kernel_w: 5
stride_h: 1
stride_w: 1
}
}
layer {
name: "batch_normalization_1"
type: "BatchNorm"
bottom: "conv_512_15"
top: "batch_normalization_1"
batch_norm_param {
moving_average_fraction: 0.99
eps: 0.001
}
}
layer {
name: "batch_normalization_1_scale"
type: "Scale"
bottom: "batch_normalization_1"
top: "batch_normalization_1"
scale_param {
bias_term: true
}
}
layer {
name: "activation_1"
type: "ReLU"
bottom: "batch_normalization_1"
top: "batch_normalization_1"
}
layer {
name: "conv_512_51"
type: "Convolution"
bottom: "batch_normalization_1"
top: "conv_512_51"
convolution_param {
num_output: 512
bias_term: true
pad_h: 0
pad_w: 0
kernel_h: 5
kernel_w: 1
stride_h: 1
stride_w: 1
}
}
layer {
name: "batch_normalization_2"
type: "BatchNorm"
bottom: "conv_512_51"
top: "batch_normalization_2"
batch_norm_param {
moving_average_fraction: 0.99
eps: 0.001
}
}
layer {
name: "batch_normalization_2_scale"
type: "Scale"
bottom: "batch_normalization_2"
top: "batch_normalization_2"
scale_param {
bias_term: true
}
}
layer {
name: "activation_2"
type: "ReLU"
bottom: "batch_normalization_2"
top: "batch_normalization_2"
}
layer {
name: "conv_1024_11"
type: "Convolution"
bottom: "batch_normalization_2"
top: "conv_1024_11"
convolution_param {
num_output: 1024
bias_term: true
pad_h: 0
pad_w: 0
kernel_h: 1
kernel_w: 1
stride_h: 1
stride_w: 1
}
}
layer {
name: "batch_normalization_3"
type: "BatchNorm"
bottom: "conv_1024_11"
top: "batch_normalization_3"
batch_norm_param {
moving_average_fraction: 0.99
eps: 0.001
}
}
layer {
name: "batch_normalization_3_scale"
type: "Scale"
bottom: "batch_normalization_3"
top: "batch_normalization_3"
scale_param {
bias_term: true
}
}
layer {
name: "activation_3"
type: "ReLU"
bottom: "batch_normalization_3"
top: "batch_normalization_3"
}
layer {
name: "conv_class_11"
type: "Convolution"
bottom: "batch_normalization_3"
top: "conv_class_11"
convolution_param {
num_output: 84
bias_term: true
pad_h: 0
pad_w: 0
kernel_h: 1
kernel_w: 1
stride_h: 1
stride_w: 1
}
}
layer {
name: "prob"
type: "Softmax"
bottom: "conv_class_11"
top: "prob"
}

+ 12117
- 0
Prj-PHP/tests/model/cascade.xml
File diff suppressed because it is too large
View File


+ 2
- 2
Prj-PHP/tests/platescan.php View File

@@ -1,13 +1,13 @@
<?php
//
// Created by Coleflowers on 20/06/2018.
// Updated by JustID on 03/07/2018.
//

/**
* 车牌识别PHP扩展程序测试
*/
$path = realpath("demo.png");
$res = platescan($path);
$res = platescan(realpath("demo.png"),realpath("model"),0.8);
var_dump($res);

?>

Loading…
Cancel
Save