停车场自动收费、交通违章抓拍、小区门禁管理…这些场景背后都依赖一个核心技术——车牌识别。本文将带你用C++和OpenCV实现一套完整的车牌识别系统,涵盖图像预处理、字符分割、KNN模型训练等关键环节,并提供可直接运行的代码示例。
车牌识别系统通常包含四个关键模块:
cpp复制// 示例:基础处理流程框架
int main() {
Mat input = imread("car_plate.jpg");
Mat plate_region = locatePlate(input); // 车牌定位
vector<Mat> chars = segmentChars(plate_region); // 字符分割
string result = recognizeChars(chars); // 字符识别
cout << "识别结果:" << result << endl;
return 0;
}
实际场景中的车牌图像往往存在光照不均、角度倾斜等问题。我们需要通过以下步骤进行优化:
关键预处理技术:
GaussianBlur)Canny)morphologyEx)cpp复制Mat preprocessPlate(Mat input) {
Mat gray, blur, edges;
cvtColor(input, gray, COLOR_BGR2GRAY);
GaussianBlur(gray, blur, Size(5,5), 0);
Canny(blur, edges, 50, 150);
// 形态学闭合操作连接边缘
Mat kernel = getStructuringElement(MORPH_RECT, Size(5,5));
morphologyEx(edges, edges, MORPH_CLOSE, kernel);
return edges;
}
提示:实际项目中建议结合颜色空间分析(如HSV)来增强车牌区域的定位准确率
成功定位车牌区域后,需要将各个字符分离。这里采用轮廓分析结合几何特征的方法:
threshold)findContours)cpp复制vector<Mat> segmentCharacters(Mat plate) {
vector<Mat> chars;
Mat binary;
threshold(plate, binary, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
vector<vector<Point>> contours;
findContours(binary.clone(), contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
vector<Rect> boxes;
for (auto &contour : contours) {
Rect rect = boundingRect(contour);
// 过滤非字符区域(基于经验值)
if (rect.height > plate.rows*0.3 &&
rect.width < plate.cols*0.2) {
boxes.push_back(rect);
}
}
// 按x坐标排序确保字符顺序正确
sort(boxes.begin(), boxes.end(),
[](const Rect &a, const Rect &b) { return a.x < b.x; });
for (auto &box : boxes) {
Mat charImg = binary(box);
resize(charImg, charImg, Size(20,30)); // 统一尺寸
chars.push_back(charImg);
}
return chars;
}
K最近邻(KNN)算法因其简单高效的特点,非常适合字符识别任务。下面是关键实现步骤:
数据集准备要点:
cpp复制void trainKNN(const vector<Mat> &samples, const vector<int> &labels) {
Mat trainData(samples.size(), samples[0].total(), CV_32F);
Mat trainLabels(labels.size(), 1, CV_32S);
// 转换样本数据格式
for (size_t i=0; i<samples.size(); i++) {
Mat floatImg;
samples[i].convertTo(floatImg, CV_32F);
floatImg.reshape(1,1).copyTo(trainData.row(i));
trainLabels.at<int>(i) = labels[i];
}
Ptr<ml::KNearest> knn = ml::KNearest::create();
knn->setDefaultK(3); // 设置K值
knn->train(trainData, ml::ROW_SAMPLE, trainLabels);
knn->save("knn_model.xml");
}
模型参数优化建议:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| K值 | 3-5 | 通常取奇数避免平票 |
| 距离度量 | 欧式距离 | 默认选项,效果稳定 |
| 样本尺寸 | 20×30 | 平衡识别率和计算量 |
将各模块组合成完整系统时,需要注意以下关键点:
错误处理机制:
性能优化技巧:
cpp复制// 示例:带置信度检查的识别流程
string recognizePlate(Mat input) {
Mat plate = locatePlate(input);
if (plate.empty()) return "定位失败";
vector<Mat> chars = segmentCharacters(plate);
if (chars.size() != 7) return "字符分割异常"; // 假设车牌7个字符
Ptr<ml::KNearest> knn = Algorithm::load<ml::KNearest>("knn_model.xml");
string result;
for (auto &ch : chars) {
Mat floatImg;
ch.convertTo(floatImg, CV_32F);
floatImg = floatImg.reshape(1,1);
Mat response;
float confidence = knn->findNearest(floatImg, 3, response);
if (confidence < 0.8) { // 置信度阈值
result += "?";
} else {
result += char(response.at<float>(0));
}
}
return result;
}
在真实场景部署时会遇到各种意外情况,这里分享几个实战经验:
光照条件差:
ADAPTIVE_THRESH_GAUSSIAN_C)equalizeHist)车牌倾斜:
warpAffine)校正cpp复制Mat correctSkew(Mat input) {
Mat edges;
Canny(input, edges, 50, 200);
vector<Vec4i> lines;
HoughLinesP(edges, lines, 1, CV_PI/180, 50, 50, 10);
// 计算平均旋转角度
double angle = 0;
for (auto &l : lines) {
angle += atan2(l[3]-l[1], l[2]-l[0]);
}
angle /= lines.size();
Point2f center(input.cols/2., input.rows/2.);
Mat rot = getRotationMatrix2D(center, angle*180/CV_PI, 1.0);
Mat corrected;
warpAffine(input, corrected, rot, input.size());
return corrected;
}
字符粘连问题:
完成基础版本后,可以考虑以下增强功能:
cpp复制// 示例:视频流处理框架
void processVideo(string videoPath) {
VideoCapture cap(videoPath);
Mat frame;
while (cap.read(frame)) {
string plate = recognizePlate(frame);
if (!plate.empty()) {
putText(frame, plate, Point(50,50),
FONT_HERSHEY_SIMPLEX, 1, Scalar(0,255,0), 2);
}
imshow("Result", frame);
if (waitKey(30) == 27) break;
}
}
在开发过程中,我发现在复杂背景下,结合边缘检测和颜色分析的双重验证机制能显著提高车牌定位的准确率。另外,对KNN模型加入字符几何特征(宽高比、笔画密度等)作为额外维度,也能改善相似字符(如'8'和'B')的区分度。