关于EasyPR的调用说明

未经允许,严禁外传
请预先阅读 Ubuntu与opencv的配置

0.写在前面

本文最后更新于:2022/5/2 此后不再更新

0.1 预先工作

你需要安装完毕虚拟机VMware然后安装Ubuntu

0.2 版本说明

Ubuntu为18.xx版本

VMware为16版本

主机系统Win11

0.3注意

本文档基本没有废话!

请您仔细阅读,你感觉没用的都是要做铺垫的。

1.前期准备

首先打开你的Ubuntu

1.1了解你的home

首先打开你的文件图标

![[关于EasyPR识别车牌的调用/文件.png]]

打开后你就可以看到侧边栏

![[关于EasyPR识别车牌的调用/文件目录.png]]

你的主目录就是你的home,而home不是桌面,你可以暂且把home理解为你的C盘。

注意:Linux是没有分区的!这里只是一个比喻

强烈建议你把你的东西放在你的home里面而不是你的桌面

1.2了解你的终端

![[关于EasyPR识别车牌的调用/终端图标.png]]

这是你的终端,就是你大一上常说的黑框框。

1.2.1了解终端命令(重要)

cd /home/wjj/opencvdemo

意思:切换到/home/wjj/opencvdemo文件夹内(直接进入到opencvdemo)

注意:

你也可以直接使用右键,打开文件夹,但你的终端就默认执行了 cd /…(你的文件夹)的命令,你可以这样理解。

注意cd后面的路径会有一些讲究,只能逐级访问,不能越级访问!

比如你的/home 下有A与B两个文件夹,A下有c、d两个子文件夹。

那么在c文件夹下直接输入cd /home/B是无效的,系统会提示找不到。

你可以使用 cd ../B

这里的..表示本文件夹的上一级别文件夹,这样可以越级访问了。

你也可以用.表示当前文件夹

迭代使用...是允许的,你可用../../表示上一级的上一级

如果你不能理解,建议直接使用手动鼠标切换文件夹再右键打开终端解决

比如:cmake .表示用cmake编译当前目录

注意:cd后需要跟上一个空格才输入文件路径

sudo 命令

相当于Windows下的管理员模式

cp

复制命令,当然我建议你直接鼠标复制粘贴

注意

命令的每一个换行和空格都很重要,请在使用前仔细检查!

1.2.2 VIM编辑器

相当于Windows下的txt编辑器,但其使用比较麻烦,如果你要使用,请自行搜索。

1.2. TXT文本编辑器

如果你上的是老白的课,那么在wsl下,你需要使用vim编辑器来编辑txt文档。但你选择了虚拟机,它提供了一个可视化页面,你可以完全使用可视化页面下的txt文本编辑器来编辑你的c++文本。注意你需要把后缀.txt修改为.cpp

如何找到你的txt文本编辑器呢?首先在你的桌面找到下图左下角那个九宫格小点并点击

随后将会展示你的所有应用

如果你只有很少几个应用,请检查你的最下方是否是“全部”而不是“常用”

第二排第三个就是你的txt编辑器,你可以右键把他放到你的快捷方式栏

你将会用它编写cmakelist.txt和你的代码文本。

2. Make的简单说明

2.1简单介绍

我们编写的大型程序往往由多个编译单元(或者说,多个源代码文件)构成。因此,构建应用时,发出的编译命令可能会比较长。

另外,在开发过程中,可能只修改了一些而不是全部的编译单元,因此在构建时,只需重新编译修改过的编译单元,再重新链接即可。

为达到此目的,推荐的构建方式是使用make工具。

如果你看不懂,咱们不用管

2.2我采用的方法&&原理介绍

——声明——

我不会写make!

我不会写make!

我不会写make!

但是easypr在源文件里面已经提供了一个cmakelist.txt用以构建demo

什么是demo呢?

如果你还没有解压EasyPR-master,请先解压,并放在/home下

现在请你打开你的EasyPR-master(以下称easypr主文件夹

右键打开终端

依次输入:

1
2
./build.sh
./demo

如果你已经按照PPT编译了easypr,请不要输入第一条

你会看到如下内容:

1
2
3
4
5
6
7
8
9
10
11
///////////////////////////////////
EasyPR Option:
1. 测试;
2. 批量测试(推荐);
3. SVM训练;
4. ANN训练;
5. 中文字符训练;
6. 生成字符;
7. 感谢名单;
8. 退出;
////////////////////////////////////

在其后输入数字可以体验识别车牌的快乐。

其实demo就是一个运行程序,他跟你的c语言大作业编译出来的xxx.exe文件一样,但Ubuntu不支持双击运行,且它没有后缀。

这个demo是怎么来的呢?

请你打开easypr主文件夹下的CMakeList.txt文件(下称cmakelist

往下滑,你可看到

1
2
3
....(省略一大堆内容)
# test cases
add_executable(${EXECUTABLE_NAME} test/main.cpp)

这句话的意思是你要编译的主程序是test文件夹下的main.cpp文件。现在你可以打开easypr主文件夹下的test文件夹,就会发现main.cpp这个文件,这就是demo的主文件。

介绍一下:cpp就是c++,按照老白的新书里面所讲,c++就是一个崭新的c,你大可不必担心自己不会c++,c已经足够了,况且我后面也会介绍c++的特殊用法。声明:我不会c++!我只是随便看了一点点!!!只能处理本课程的作业。

当然,你需要注意的是,如此庞大的工程必定不可能直接用一个main写出来,一定还有其他cpp文件。他们通过cmake的链接、头文件的链接………………一同参与编译,最终形成程序。相当于main是主程序,其他是辅助。

你看到的cmakelist可能是下面这个东西,那么我们怎么实现编译我们自己的写的代码呢?

其实只需要告诉cmakelist,别tm的编译test文件的main.cpp了,去编译我们的文件

在cmakelist里面,#才是注释,//不是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
cmake_minimum_required(VERSION 3.0.0)
project(easypr)
#请无视上方两排内容
#本文档仅供参考!请自己修改你自己的camkelist!不要直接复制粘贴!!!!!!!!!!否则后果很严重(我也解决不了)
#本文档仅供参考!请自己修改你自己的camkelist!不要直接复制粘贴!!!!!!!!!!否则后果很严重(我也解决不了)
#本文档仅供参考!请自己修改你自己的camkelist!不要直接复制粘贴!!!!!!!!!!否则后果很严重(我也解决不了)
#本文档仅供参考!请自己修改你自己的camkelist!不要直接复制粘贴!!!!!!!!!!否则后果很严重(我也解决不了)

# c++11 required

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

if (CMAKE_SYSTEM_NAME MATCHES "Darwin")
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} "/usr/local/opt/opencv-3.4.8")
#请根据你自己的版本修改后面的版本号,前面的/usr/local/opt等不要修改!即便你的opencv放在home里面的。
#本文档仅供参考!请自己修改你自己的camkelist!不要直接复制粘贴!!!!!!!!!!否则后果很严重(我也解决不了)
endif ()

# OpenVC3 required

find_package(OpenCV 3.4.8 REQUIRED)
#请根据你自己的版本修改后面的版本号
# where to find header files

include_directories(.)
include_directories(include)
include_directories(${OpenCV_INCLUDE_DIRS})
#请保证你的cmakelist在easypr主文件夹,否则可能出现找不到头文件的错误。上面的代码意思是添加头文件。请不要修改
# sub directories

add_subdirectory(thirdparty)

# sources to be compiled

set(SOURCE_FILES
src/core/core_func.cpp
src/core/chars_identify.cpp
src/core/chars_recognise.cpp
src/core/chars_segment.cpp
src/core/feature.cpp
src/core/plate_detect.cpp
src/core/plate_judge.cpp
src/core/plate_locate.cpp
src/core/plate_recognize.cpp
src/core/params.cpp

src/train/ann_train.cpp
src/train/annCh_train.cpp
src/train/svm_train.cpp
src/train/train.cpp
src/train/create_data.cpp

src/util/util.cpp
src/util/program_options.cpp
src/util/kv.cpp
)
#上方代码的意思是修改库文件,请勿修改,如果报错,请自行修改为相对地址(运用.或..)
#上方代码的意思是修改库文件,请勿修改,如果报错,请自行修改为相对地址(运用.或..)
#上方代码的意思是修改库文件,请勿修改,如果报错,请自行修改为相对地址(运用.或..)
#上方代码的意思是修改库文件,请勿修改,如果报错,请自行修改为相对地址(运用.或..)
# pack objects to static library

add_library(easypr STATIC ${SOURCE_FILES})

if (CMAKE_SYSTEM_NAME MATCHES "Darwin")
set(EXECUTABLE_NAME "demo")
elseif (CMAKE_SYSTEM_NAME MATCHES "Linux")
set(EXECUTABLE_NAME "demo")
endif ()
#上方代码的意思是根据你的操作系统命名你的.exe文件,默认是demo,你可以换一个。比如ylg's-rec等等。


# test cases

add_executable(${EXECUTABLE_NAME} xxx.cpp)
#上方代码的意思是查找你的主文件,就是你写代码的文件。请您修改!!!
#上方代码的意思是查找你的主文件,就是你写代码的文件。请您修改!!!
#上方代码的意思是查找你的主文件,就是你写代码的文件。请您修改!!!
#上方代码的意思是查找你的主文件,就是你写代码的文件。请您修改!!!
#如果你的代码写在另外的地方,请注意用绝对地址或者相对地址。
#如果就写在主文件夹里面,请直接使用文件名.cpp即可
#如果写在主文件的子文件里面,(例如test文件夹)你需要将xxx改为test/xxx.cpp
#请注意你的名字不要与其他重复,不建议用main或test作为你的文件名。
# link opencv libs

target_link_libraries(${EXECUTABLE_NAME} easypr thirdparty ${OpenCV_LIBS})
#上方代码的意思是链接opencv库,如果cmake时,终端对此反复报错,请重新编译opencv
# MESSAGE(${CMAKE_BINARY_DIR}/../)

SET_TARGET_PROPERTIES(${EXECUTABLE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/../")
#上方代码咱们也不懂

#本文档仅供参考!请自己修改你自己的camkelist!不要直接复制粘贴!!!!!!!!!!否则后果很严重(我也解决不了)
#本文档仅供参考!请自己修改你自己的camkelist!不要直接复制粘贴!!!!!!!!!!否则后果很严重(我也解决不了)
#本文档仅供参考!请自己修改你自己的camkelist!不要直接复制粘贴!!!!!!!!!!否则后果很严重(我也解决不了)
#本文档仅供参考!请自己修改你自己的camkelist!不要直接复制粘贴!!!!!!!!!!否则后果很严重(我也解决不了)

请务必仔细阅读上方中文注释内容!

建议直接修改主文件里面的cmakelist文件并保存在easypr主文件夹里面,不要另外创建,否则会引发冲突。

3. 代码编写的说明

提示:我并不会c++,以下只是基于我对其粗浅的理解,其中有很多不严谨的地方。

注意:我们将学习c++的类,它跟我们c语言的顺序表很像,所以,请您先仔细看看顺序表章节。(注意顺序表!不是链表!)

本节会比较难,但也是最难的部分。

我把cmakelist都发给你了,编代码不会还要我直接一个字一个字给你打吧……

还是提醒一句:请自己修改你自己的camkelist!不要直接复制粘贴!!!!!!!!!!否则后果很严重(我也解决不了)

3.1 c++的类和申请空间

1
2
using namespace std;
using namespace easypr;

你需要使用以上两行代码来申请内存空间,来存放一个类。

你可以理解为c语言的typedef+结构体,也可以说说定义了一个顺序表的结构体类型(请注意区分结构体结构体类型,结构体里面存放着形如数组元素和last值的参数信息(如果你看不懂这个,建议看看数据结构的书),咱们的顺序表结构体类型就是一个类。

但深层理解我可不会。

如果你不申请,会导致内存访问出错。

1
2
terminate called after throwing an instance of 'std::out_of_range'
what(): basic_string::substr:__pos

形如上方的都是内存访问出错的报错

using namespace建议放在main函数前面。

3.2 Easypr的一些参数与vector

easypr提供的文档里面有这样一段代码

1
2
3
CPlateRecognize pr;
pr.setResultShow(false);
pr.setDetectType(PR_DETECT_CMSER);

第一行的CPlateRecognize就是一个类,然后它定义了一个pr的对象(你可以理解为变量),这个变量是CPlateRecognize类型的

pr这个变量其实是一个结构体

pr.xxx就是对pr这个“结构体”的内容值进行赋值。换个方式理解一下,用顺序表的代码就形如L->elem[1]=0;

1
vector<CPlate> plateVec;

这里的CPlate也是一个类,vector表明一个容器盒子,用以存储CPlate这个类的数据,你也可以理解为申请一个空间

这是他的官方解释:

向量(Vector)是一个封装了动态大小数组的顺序容器(Sequence Container)。跟任意其它类型容器一样,它能够存放各种类型的对象。可以简单的认为,vector是一个能够存放任意类型的动态数组。

你可以理解为我们定义了一个动态顺序表叫做plateVec。

为什么要用动态顺序表呢?因为程序不知道你要是别多少个车牌。动态顺序表可以动态扩展存储空间来存放更多的车牌。

我知道你这段可能看不懂,再捋一下。我们向内存申请了一段空间,这个空间大小是”CPlate“这个类的大小。然后,我们的车牌信息将会存储在这个plateVec这个动态顺序表内。如果你要访问这个信息直接输入”plateVec.at(i)“即可实现,就类似于顺序表的的”L->elem[i]

使用vector需要使用其相关头文件,在后面会提到。

1
Mat src = imread(filepath);

这是opencv提供的,读取文件的函数,src变量用以存储图像信息,类比c语言文件章节的fp指针。

filepath需要你自己填写,代表要识别的图片的地址。请注意填写时需要加上双引号(如“../test/zzz.cpp”

1
int result = pr.plateRecognize(src, plateVec);

这段代码的意思是,把③中src的图像通过easypr识别了,其识别数据传入到你在②定义的 plateVec的结构体

需要注意的是,result并不是识别的车牌信息,而是0与非0值,0代表识别成功(至少一个车牌)

那么如何获取识别到的信息呢?

请别忘了,我们定义了一个顺序表叫做plateVec,他的类型是CPlate。

plateVec这个动态顺序表下就包含了车牌的众多信息

CPlate plate = plateVec.at(i);
Mat plateMat = plate.getPlateMat();
RotatedRect rrect = plate.getPlatePos();
string license = plate.getPlateStr();

你唯一需要用到的参数是license(即最后一排),它代表车牌字符串,例如“蓝牌:苏EUK722”。这就是你的识别结果了。

你只需要输出license即可。

这段话给爱钻研的有能力看的想ball研的室友看:

第一排咱们又定义了一个CPlate类型的变量plate。

我们把识别的第i个车牌的所有信息(plateVec.at(i))存储到这个plate里面,这个plate也是一个结构体。后续通过访问plate这个结构体的的具体数据如getPlateStr()获取这个车牌信息的详细内容

比如识别第1个车牌,plateVec.at(1)的内容(第一个车牌的所有信息)就赋值给了plate结构体

第二三排plateMat代表车牌图像,rrect代表车牌的可旋转矩形位置

其他的(前三排)无需修改,但你需要将他们加入到代码里面。

再介绍一个

plateVec.size()是一个函数,直接调用会返回一个int值,表示easypr实际识别到了多少个车牌。

我们在①介绍了pr这个变量,pr也是个结构体,其可以设置识别时候的各种参数

比如

1
pr.setResultShow(false);

这句话设置EasyPR是否打开结果展示窗口,如下图。设置为true就是打开,否则就是关闭。

在需要观看定位结果时,建议打开,快速运行时(查看识别结果时)关闭。

1
2
pr.setDetectType(PR_DETECT_CMSER);
pr.setDetectType(PR_DETECT_COLOR | PR_DETECT_SOBEL);

这句话设置EasyPR采用的车牌定位算法。CMER代表文字定位方法,SOBEL和COLOR分别代表边缘和颜色定位方法。可以通过”|”符号结合。随便你改不改。以上两个设置其中一个即可

1
pr.setLifemode(true);

这句话设置开启生活模式,这个能增大搜索范围,推荐打开。

1
pr.setMaxPlates(4);

这句话设置EasyPR最多查找多少个车牌。当一副图中有大于n个车牌时,EasyPR最终只会输出可能性最高的n个。

注意:pr.setMaxPlates(4)不保证一定能识别到4个车牌!建议调大一点,比如要是别5个车牌就改成7-8。

3.3 c++的输出语句

1
std::cout << "识别第"<<num<<"个车牌为: " << license << endl;

输出的固定内容用" "包括,上面语句中num和license都是变量,前者是int值,后者是字符串。变量和固定内容间用<<链接。endl表示换行。

比如: 上面的num为1,license为 川A123456

输出:

1
2
识别第1个车牌为: 川A123456

4.代码编写实操

头文件和申请空间是固定的

1
2
3
4
5
6
7
#include <iostream>
#include <easypr.h>
#include <vector>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace easypr;

主函数仍然存在

1
2
3
4
int main(){


}

程序内部(仅供参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
int main(){
CPlateRecognize pr;
pr.setResultShow( );
pr.setDetectType( );
pr.setLifemode( );
pr.setResultShow( );
pr.setMaxPlates( );
//TODO:我们首先定义一个pr变量,设置识别时的参数。各个参数前面已经介绍了,请你自行删除修改。

vector<CPlate> plateVec;
//我们定义了一个plateVec的顺序表
Mat src = imread( );
//TODO:图片的地址,仔细阅读相对地址与绝对地址等内容(在1.2.1)。注意地址需要用引号引用
int result = pr.plateRecognize(src, plateVec);
//识别结果返回给result

if ( ) //TODO:什么才表示识别成功了呢?
{


int total= //TODO:一共实际识别到多少个车牌?这个函数我前面提到了


for( )
//TODO:利用循环逐个输出被识别到的车牌。循环的条件是啥?
{

CPlate plate = plateVec.at( /*TODO:这个填啥呢?你需要根据你的for循环定义的变量填写*/ );

Mat plateMat = plate.getPlateMat();
RotatedRect rrect = plate.getPlatePos();
string license = plate.getPlateStr();
//TODO:你需要一个输出语句。
}

}

return 0;

}

注意:为了防止抄袭,每一处TODO的内容都需要自己填写,另外我设置了两个不显而易见的语法错误,请自行阅读报错信息修改。

所有的参数文档都介绍了

5.编译与运行

在easypr主文件夹右键打开终端。

依次输入

1
2
cmake .
make

如果任何一步出现报错,那么你修改你的程序并需要重新编译!

重新编译只需要删除CMakeCache.txt再运行上方内容即可。

如果成功创立,你的.exe程序大概率会放在easypr主文件夹的上一级文件夹里面(一般也就是是你的home),你需要把那个.exe文件移动到easypr主文件里面。

然后在终端输入

1
./xxx

xxx为你的.exe文件的名字

再次提醒:Ubuntu 下这样编写的运行程序其实没有后缀,为了方便我只是把运行程序叫成 .exe

如果你忘了你的程序名字,你可以打开你的cmakelist,看到这段话

1
2
3
4
5
6
if (CMAKE_SYSTEM_NAME MATCHES "Darwin")
set(EXECUTABLE_NAME "demo")
elseif (CMAKE_SYSTEM_NAME MATCHES "Linux")
set(EXECUTABLE_NAME "demo")
endif ()
#上方代码的意思是根据你的操作系统命名你的.exe文件,默认是demo,你可以换一个。比如ylg's-rec等等。

这段代码的demo处就是你的程序名字。

然后你就会看到识别结果

6.图片

easypr识别3个以上车牌时略显不不靠谱

推荐使用高清拼接图片。

拼接图片可以用PPT拼接

搜索关键词建议:

?还想看呢,自己搜,我不会告诉你的!

7.常见错误

  1. 如果头文件easypr.h报错找不到,请换成相对地址或绝对地址。注意不能用< >而是用" "
  2. 如果编译代码时提示”xxx is not declar in this scope”(在这个范围里未定义xxx),首先你检查一下你的拼写拼写错没?包括大小写。有无未定义的情形?若以上都没有,且这个变量凭借你的第六感时easypr给出的,或者这个类名它找不到。那么请在这个变量前加上easypr:: 例如:easypr::CPlateRecognize
  3. 如果有提示
    1
    2
    terminate called after throwing an instance of 'std::out_of_range'
    what(): basic_string::substr:__pos
    或者
    1
    核心存储已转移
    等字样,说明的内存使用错误

请检查:

①图片的地址是否正确?

②for循环是否超出界限?

③输出的车牌个数必须小于等于plateVec.size()而不是pr.setMaxPlates(x);

④数组的使用

⑤有特殊情况,可能车牌一个都识别不了,建议在for前方输出实际车牌识别的个数值。

  1. 如果发现复制我的代码,然后出现中文输出异常(例如为:xxxx形式的乱码),请你重新手输中文内容,不要复制粘贴。

8.完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <iostream>
#include<easypr.h>
#include<vector>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace easypr;

int main(){
easypr::CPlateRecognize pr;
pr.setResultShow(false);
pr.setDetectType(easypr::PR_DETECT_CMSER);
pr.setLifemode(true);
//启用生活模式,以增大识别范围
pr.setResultShow(false);
pr.setMaxPlates(5);
//最大车牌识别量

vector<easypr::CPlate> plateVec;
Mat src = imread("/home/jjq1/EasyPR-master/car/5cars.jpg");
//图片的地址
int result = pr.plateRecognize(src, plateVec);

if (result == 0)
{
int total=plateVec.size();
std::cout << "车牌识别成功!一共识别到"<<total<<"个车牌" << std::endl;

for(int i=0;i<plateVec.size();i++)
//利用循环逐个输出被识别到的车牌。某些车牌不能被识别就不输出
{
int num=i+1; //数组下标从0开始,大意了在这里踩了坑hhh
CPlate plate = plateVec.at(i);
Mat plateMat = plate.getPlateMat();
RotatedRect rrect = plate.getPlatePos();
string license = plate.getPlateStr();
std::cout << "识别第"<<num<<"个车牌为: " << license << endl;
}

}


return 0;
}