2023年11月28日发(作者:海马s5质量怎么样)

OpenCVC++案例实战?《车牌号识别》

OpenCV C++案例实战?《车牌号识别》

前?

本?将使?OpenCV C++ 进?车牌号识别。

?、车牌检测

原图如图所?。本案例的需求是进?车牌号码识别。所以,?先我们得定位车牌所在的位置,然后将车牌切割出来。接下来我们就来看看是

如何实现。

1.1.图像预处理

?先经过?些常规的图像预处理,我们可以提取出图像的?致轮廓。然后根据轮廓的特征进?步确定我们所需要查找的轮廓。在这?,不同

的图像需要根据本?图像特征设定预处理算法。所以,本案例的?个缺点就是不具有鲁棒性,只针对特定需求。

1

2

3

4

5

6

7

8

9

10

Mat gray;

cvtColor(src, gray, COLOR_BGR2GRAY);

Mat thresh;

threshold(gray, thresh, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);

//

使?形态学开操作去除?些?轮廓

Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));

Mat open;

morphologyEx(thresh, open, MORPH_OPEN, kernel);

如图为经过?值化后的图像,接下来我们就可以使?findContours寻找我们需要的轮廓。根据图像的轮廓特征就可以定位到车牌所在位

置,然后将其从原图中切割出来,以便后续的识别?作。在这?,我定义了?个License结构体,?于存储ROI图像,以及其相对于原图所

在位置。这样在后续的绘制?作中,我们就可以定位到ROI所在位置。

1.2.轮廓提取

1

2

3

4

5

6

//

?定义车牌结构体

struct License

{

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

// RETR_EXTERNAL

使?找到最外轮廓

vector<vector<Point>>contours;

findContours(open, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);

vector<vector<Point>>conPoly(contours.size());

for (int i = 0; i < contours.size(); i++)

{

double area = contourArea(contours[i]);

double peri = arcLength(contours[i], true);

//

根据?积筛选出可能属于车牌区域的轮廓

if (area > 1000)

{

//

使?多边形近似,进?步确定车牌区域轮廓

approxPolyDP(contours[i], conPoly[i], 0.02*peri, true);

if (conPoly[i].size() == 4)

{

//

计算矩形区域宽??

Rect box = boundingRect(contours[i]);

double ratio = double(box.width) / double(box.height);

if (ratio > 2 && ratio < 4)

{

//ROI

截取区域

Rect rect = boundingRect(contours[i]);

License_ROI = { src(rect),rect };

}

}

}

}

1.3.功能效果

如图为从汽车上定位到的车牌,并将其切割出来以便下?的识别?作。

1.4.功能源码

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

//ROI--

获取车牌所在区域车牌定位

bool Get_License_ROI(Mat src, License &License_ROI)

{

Mat gray;

cvtColor(src, gray, COLOR_BGR2GRAY);

Mat thresh;

threshold(gray, thresh, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);

//

使?形态学开操作去除?些?轮廓

Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));

Mat open;

morphologyEx(thresh, open, MORPH_OPEN, kernel);

// RETR_EXTERNAL

使?找到最外轮廓

vector<vector<Point>>contours;

findContours(open, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);

vector<vector<Point>>conPoly(contours.size());

for (int i = 0; i < contours.size(); i++)

{

double area = contourArea(contours[i]);

double peri = arcLength(contours[i], true);

//

根据?积筛选出可能属于车牌区域的轮廓

if (area > 1000)

{

//

使?多边形近似,进?步确定车牌区域轮廓

approxPolyDP(contours[i], conPoly[i], 0.02*peri, true);

if (conPoly[i].size() == 4)

{

//

计算矩形区域宽??

Rect box = boundingRect(contours[i]);

double ratio = double(box.width) / double(box.height);

if (ratio > 2 && ratio < 4)

{

//ROI

截取区域

Rect rect = boundingRect(contours[i]);

License_ROI = { src(rect),rect };

}

}

}

}

if (License_ROI.mat.empty())

{

return false;

}

return true;

}

?、字符切割

2.1.图像预处理

通过刚才的车牌定位,我们已经将车牌从原图中切割出来了。接下来,我们还需要将车牌上的字符??切割出来,以便进?后续的识别?

作。同理,我们也需要对车牌做同样的预处理操作。

1

2

3

4

5

6

7

8

9

Mat gray;

cvtColor(License_ROI.mat, gray, COLOR_BGR2GRAY);

Mat thresh;

threshold(gray, thresh, 0, 255, THRESH_BINARY | THRESH_OTSU);

Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));

Mat close;

morphologyEx(thresh, close, MORPH_CLOSE, kernel);

经过灰度、阈值、形态学操作后的图像如下图所?。

2.2.轮廓提取

接下来我们进?轮廓提取就可以提取出车牌上的每?个字符了。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

vector<vector<Point>>contours;

findContours(close, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);

for (int i = 0; i < contours.size(); i++)

{

double area = contourArea(contours[i]);

//

由于我们筛选出来的轮廓是?序的,故后续我们需要将字符重新排序

if (area > 200)

{

Rect rect = boundingRect(contours[i]);

//

计算外接矩形宽??

double ratio = double(rect.height) / double(rect.width);

if (ratio > 1)

{

Mat roi = License_ROI.mat(rect);

resize(roi, roi, Size(50, 100), 1, 1, INTER_LINEAR);

Character_ROI.push_back({ roi ,rect });

}

}

}

如图为切

割出来的字符。不过这?有?个?问题就是,我们切割出来的字符并不是按车牌号码那样顺序排列。所以,在这?我们还得对其重新进?排

序,使其按车牌顺序排列。

1

2

3

4

5

6

7

8

9

10

11

12

13

//

冒泡排序

for (int i = 0; i < Character_ROI.size()-1; i++)

{

for (int j = 0; j < Character_ROI.size() - 1 - i; j++)

{

if (Character_ROI[j].rect.x > Character_ROI[j + 1].rect.x)

{

License temp = Character_ROI[j];

Character_ROI[j] = Character_ROI[j + 1];

Character_ROI[j + 1] = temp;

}

}

}

2.3.功能效果

2.4.功能源码

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

//ROI

获取车牌每?个字符区域

bool Get_Character_ROI(License &License_ROI, vector<License>&Character_ROI)

{

Mat gray;

cvtColor(License_ROI.mat, gray, COLOR_BGR2GRAY);

Mat thresh;

threshold(gray, thresh, 0, 255, THRESH_BINARY | THRESH_OTSU);

Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));

Mat close;

morphologyEx(thresh, close, MORPH_CLOSE, kernel);

vector<vector<Point>>contours;

findContours(close, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);

for (int i = 0; i < contours.size(); i++)

{

double area = contourArea(contours[i]);

//

由于我们筛选出来的轮廓是?序的,故后续我们需要将字符重新排序

if (area > 200)

{

Rect rect = boundingRect(contours[i]);

//

计算外接矩形宽??

double ratio = double(rect.height) / double(rect.width);

if (ratio > 1)

{

Mat roi = License_ROI.mat(rect);

resize(roi, roi, Size(50, 100), 1, 1, INTER_LINEAR);

Character_ROI.push_back({ roi ,rect });

}

}

}

//

将筛选出来的字符轮廓按照其左上?点坐标从左到右依次顺序排列

//

冒泡排序

for (int i = 0; i < Character_ROI.size()-1; i++)

{

for (int j = 0; j < Character_ROI.size() - 1 - i; j++)

{

if (Character_ROI[j].rect.x > Character_ROI[j + 1].rect.x)

{

License temp = Character_ROI[j];

Character_ROI[j] = Character_ROI[j + 1];

Character_ROI[j + 1] = temp;

}

}

}

if (Character_ROI.size() != 7)

{

return false;

}

return true;

}

三、字符识别

3.1.读取?件

如图所?,为模板图像以及对应的label。我们需要读取?件,进?匹配。在这?我使?UTF8ToGB函数实现读取txt?件,?的是为了在控

制台显?中?时,不会出现乱码情况。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

//

读取?件图?

bool Read_Data(string filename,vector<Mat>&dataset)

{

vector<String>imagePathList;

glob(filename, imagePathList);

if (imagePathList.empty())return false;

for (int i = 0; i < imagePathList.size(); i++)

{

Mat image = imread(imagePathList[i]);

resize(image, image, Size(50, 100), 1, 1, INTER_LINEAR);

dataset.push_back(image);

}

return true;

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

//

读取?件标签

bool Read_Data(string filename, vector<string>&data_name)

{

fstream fin;

fin.open(filename, ios::in);

if (!fin.is_open())

{

cout << \"can not open the file!\" << endl;

return false;

}

string s;

while (std::getline(fin, s))

{

string str = UTF8ToGB(s.c_str()).c_str();

data_name.push_back(str);

}

fin.close();

return true;

}

3.2.字符匹配

在这?,我的思路是:使??个for循环,将我们切割出来的字符与现有的模板进?匹配。?这个匹配算法是求两张图像的像素差,以此来

判断图像的相似程度。具体是使?OpenCV absdiff函数计算两张图像的像素差.。

如图为使?absdiff得到的效果图。接下来,我们只需要计算图像中灰度值为0的像素点个数就可以了。像素点个数最少的那个label即为我

们的匹配结果。当然,此?法肯定是会存在误识别的情况的。进?字符匹配的?法还有:模板匹配,基于Hu矩轮廓匹配。?家可以试试。

3.3.功能源码

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

//

识别车牌字符

bool License_Recognition(vector<License>&Character_ROI, vector<int>&result_index)

{

string filename = \"data/\";

vector<Mat>dataset;

if (!Read_Data(filename, dataset)) return false;

for (int i = 0; i < Character_ROI.size(); i++)

{

Mat roi_gray;

cvtColor(Character_ROI[i].mat, roi_gray, COLOR_BGR2GRAY);

Mat roi_thresh;

threshold(roi_gray, roi_thresh, 0, 255, THRESH_BINARY | THRESH_OTSU);

int minCount = 1000000;

int index = 0;

for (int j = 0; j < dataset.size(); j++)

{

Mat temp_gray;

cvtColor(dataset[j], temp_gray, COLOR_BGR2GRAY);

Mat temp_thresh;

threshold(temp_gray, temp_thresh, 0, 255, THRESH_BINARY | THRESH_OTSU);

//

计算两张图?的像素差,以此判断两张图?是否相同

Mat dst;

absdiff(roi_thresh, temp_thresh, dst);

int count = pixCount(dst);

if (count < minCount)

{

minCount = count;

index = j;

}

}

result_index.push_back(index);

}

return true;

}

四、效果显?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

//

显?最终效果

bool Draw_Result(Mat src, License &License_ROI, vector<License>&Character_ROI,vector<int>&result_index)

{

rectangle(src, License_ROI.rect, Scalar(0, 255, 0), 2);

vector<string>data_name;

if (!Read_Data(\"data_\", data_name))return false;

for (int i = 0; i < Character_ROI.size(); i++)

{

cout << data_name[result_index[i]] << \" \";

//putText

中?显?会乱码,所以采?下?代码

CvxText text(\"C://Windows/Fonts/?正粗?宋简体.ttf\");//

字体

string str = data_name[result_index[i]]; //string char

const char*msg = str.data();

IplImage *temp; //Mat IplImage

temp = &IplImage(src);

text.putText(temp, msg, Point(License_ROI.rect.x + Character_ROI[i].rect.x, License_ROI.rect.y + Character_ROI[i].rect.y),Scalar(0,0,255));

}

return true;

}

在这?,为了使?putText显?中?,我这?加了?些额外的代码。如果需要使?putText显?中?效果的朋友可以??百度?下如何配置

五、源码

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

#include

#include

#include //

?本读写

#include //

控制台输出中?乱码

#include\"CvxText.h\" //putText

显?中?乱码

using namespace std;

using namespace cv;

//

?定义车牌结构体

struct License

{

Mat mat; //ROI

图?

Rect rect; //ROI

所在矩形

};

//ROI--

获取车牌所在区域车牌定位

bool Get_License_ROI(Mat src, License &License_ROI)

{

Mat gray;

cvtColor(src, gray, COLOR_BGR2GRAY);

Mat thresh;

threshold(gray, thresh, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);

//

使?形态学开操作去除?些?轮廓

Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));

Mat open;

morphologyEx(thresh, open, MORPH_OPEN, kernel);

// RETR_EXTERNAL

使?找到最外轮廓

vector<vector<Point>>contours;

findContours(open, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);

vector<vector<Point>>conPoly(contours.size());

for (int i = 0; i < contours.size(); i++)

{

double area = contourArea(contours[i]);

double peri = arcLength(contours[i], true);

//

根据?积筛选出可能属于车牌区域的轮廓

if (area > 1000)

{

//

使?多边形近似,进?步确定车牌区域轮廓

approxPolyDP(contours[i], conPoly[i], 0.02*peri, true);

if (conPoly[i].size() == 4)

{

//

计算矩形区域宽??

Rect box = boundingRect(contours[i]);

double ratio = double(box.width) / double(box.height);

if (ratio > 2 && ratio < 4)

{

//ROI

截取区域

Rect rect = boundingRect(contours[i]);

License_ROI = { src(rect),rect };

}

}

}

}

if (License_ROI.mat.empty())

{

return false;

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

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

return false;

}

return true;

}

//ROI

获取车牌每?个字符区域

bool Get_Character_ROI(License &License_ROI, vector<License>&Character_ROI)

{

Mat gray;

cvtColor(License_ROI.mat, gray, COLOR_BGR2GRAY);

Mat thresh;

threshold(gray, thresh, 0, 255, THRESH_BINARY | THRESH_OTSU);

Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));

Mat close;

morphologyEx(thresh, close, MORPH_CLOSE, kernel);

vector<vector<Point>>contours;

findContours(close, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);

for (int i = 0; i < contours.size(); i++)

{

double area = contourArea(contours[i]);

//

由于我们筛选出来的轮廓是?序的,故后续我们需要将字符重新排序

if (area > 200)

{

Rect rect = boundingRect(contours[i]);

//

计算外接矩形宽??

double ratio = double(rect.height) / double(rect.width);

if (ratio > 1)

{

Mat roi = License_ROI.mat(rect);

resize(roi, roi, Size(50, 100), 1, 1, INTER_LINEAR);

Character_ROI.push_back({ roi ,rect });

}

}

}

//

将筛选出来的字符轮廓按照其左上?点坐标从左到右依次顺序排列

//

冒泡排序

for (int i = 0; i < Character_ROI.size()-1; i++)

{

for (int j = 0; j < Character_ROI.size() - 1 - i; j++)

{

if (Character_ROI[j].rect.x > Character_ROI[j + 1].rect.x)

{

License temp = Character_ROI[j];

Character_ROI[j] = Character_ROI[j + 1];

Character_ROI[j + 1] = temp;

}

}

}

if (Character_ROI.size() != 7)

{

return false;

}

return true;

}

//txt

?件中读取中?,防?乱码

string UTF8ToGB(const char* str)

{

string result;

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

string result;

WCHAR *strSrc;

LPSTR szRes;

//

获得临时变量的??

int i = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);

strSrc = new WCHAR[i + 1];

MultiByteToWideChar(CP_UTF8, 0, str, -1, strSrc, i);

//

获得临时变量的??

i = WideCharToMultiByte(CP_ACP, 0, strSrc, -1, NULL, 0, NULL, NULL);

szRes = new CHAR[i + 1];

WideCharToMultiByte(CP_ACP, 0, strSrc, -1, szRes, i, NULL, NULL);

result = szRes;

delete[]strSrc;

delete[]szRes;

return result;

}

//

读取?件图?

bool Read_Data(string filename,vector<Mat>&dataset)

{

vector<String>imagePathList;

glob(filename, imagePathList);

if (imagePathList.empty())return false;

for (int i = 0; i < imagePathList.size(); i++)

{

Mat image = imread(imagePathList[i]);

resize(image, image, Size(50, 100), 1, 1, INTER_LINEAR);

dataset.push_back(image);

}

return true;

}

//

读取?件标签

bool Read_Data(string filename, vector<string>&data_name)

{

fstream fin;

fin.open(filename, ios::in);

if (!fin.is_open())

{

cout << \"can not open the file!\" << endl;

return false;

}

string s;

while (std::getline(fin, s))

{

string str = UTF8ToGB(s.c_str()).c_str();

data_name.push_back(str);

}

fin.close();

return true;

}

//

计算像素点个数

int pixCount(Mat image)

{

int count = 0;

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

int count = 0;

if (image.channels() == 1)

{

for (int i = 0; i < image.rows; i++)

{

for (int j = 0; j < image.cols; j++)

{

if (image.at<uchar>(i, j) == 0)

{

count++;

}

}

}

return count;

}

else

{

return -1;

}

}

//

识别车牌字符

bool License_Recognition(vector<License>&Character_ROI, vector<int>&result_index)

{

string filename = \"data/\";

vector<Mat>dataset;

if (!Read_Data(filename, dataset)) return false;

for (int i = 0; i < Character_ROI.size(); i++)

{

Mat roi_gray;

cvtColor(Character_ROI[i].mat, roi_gray, COLOR_BGR2GRAY);

Mat roi_thresh;

threshold(roi_gray, roi_thresh, 0, 255, THRESH_BINARY | THRESH_OTSU);

int minCount = 1000000;

int index = 0;

for (int j = 0; j < dataset.size(); j++)

{

Mat temp_gray;

cvtColor(dataset[j], temp_gray, COLOR_BGR2GRAY);

Mat temp_thresh;

threshold(temp_gray, temp_thresh, 0, 255, THRESH_BINARY | THRESH_OTSU);

//

计算两张图?的像素差,以此判断两张图?是否相同

Mat dst;

absdiff(roi_thresh, temp_thresh, dst);

int count = pixCount(dst);

if (count < minCount)

{

minCount = count;

index = j;

}

}

result_index.push_back(index);

}

return true;

}

//

显?最终效果

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

//

显?最终效果

bool Draw_Result(Mat src, License &License_ROI, vector<License>&Character_ROI,vector<int>&result_index)

{

rectangle(src, License_ROI.rect, Scalar(0, 255, 0), 2);

vector<string>data_name;

if (!Read_Data(\"data_\", data_name))return false;

for (int i = 0; i < Character_ROI.size(); i++)

{

cout << data_name[result_index[i]] << \" \";

//putText

中?显?会乱码,所以采?下?代码

CvxText text(\"C://Windows/Fonts/?正粗?宋简体.ttf\");//

字体

string str = data_name[result_index[i]]; //string char

const char*msg = str.data();

IplImage *temp; //Mat IplImage

temp = &IplImage(src);

text.putText(temp, msg, Point(License_ROI.rect.x + Character_ROI[i].rect.x, License_ROI.rect.y + Character_ROI[i].rect.y),Scalar(0,0,255));

}

return true;

}

int main()

{

Mat src = imread(\"\");

if (src.empty())

{

cout << \"No image!\" << endl;

system(\"pause\");

return -1;

}

License License_ROI;

if (Get_License_ROI(src, License_ROI))

{

vector<License>Character_ROI;

if (Get_Character_ROI(License_ROI, Character_ROI))

{

vector<int>result_index;

if (License_Recognition(Character_ROI, result_index))

{

Draw_Result(src, License_ROI, Character_ROI,result_index);

}

else

{

cout << \"未能识别字符!\" << endl;

system(\"pause\");

return -1;

}

}

else

{

cout << \"未能切割出字符!\" << endl;

system(\"pause\");

return -1;

}

}

else

{

cout << \"未定位到车牌位置!\" << endl;

system(\"pause\");

return -1;

}

323

324

325

326

327

328

imshow(\"src\", src);

waitKey(0);

system(\"pause\");

return 0;

}

总结

本?使?OpenCV C++进?车牌号识别,关键步骤有以下?点。

1、车牌定位。案例需求是进?车牌识别。那么我们就得知道车牌在什么位置。将车牌找到之后,需要将车牌切割出来,作为?个整体进?

下??作。

2、字符分割。我们得到了车牌,需要将车牌上的字符??分割出来才能进?下?的识别?作。有个?细节就是需要将字符重新排序。


更多推荐

车牌识别