首页 > 代码库 > 基于‘匹配’技术的车牌自动识别系统
基于‘匹配’技术的车牌自动识别系统
随着智能停车场系统的兴起,车牌识别技术又着实火了一把。我也来了兴趣,大致浏览了下当前的现状,发现目前流行的车牌识别技术是基于神经网络的(这方面可参见Mastering OpenCV with Practical Computer Vision Projects一书,由机械工业出版社发行)。人工神经网络这么高深的东西,笔者不太懂,但是作为图像匹配技术出身,我认为这种小case需要那么复杂的东西来完成么?并且训练人工神经网络也是一件麻烦的事情。。。总之在各种不服气之下,笔者基于图像匹配技术编写了一个车牌自动识别算法,自己测试了几十张图像,自我感觉效果不错,所以拿出来显摆显摆,希望大家批评指正。
先交代一下开发环境吧,本人基于OpenCV2.4.10,用VS2010开发,配合使用了WinGSL数学库,这些东西网上都有的下,我就不多说了。接下来言归正传。
我设计的车牌自动识别算法可大致分为7个大步骤,其中3个主要步骤中都使用了图像匹配技术,具体步骤如下:
1,基于颜色的车牌分割得到二值化的图像,这一步主要基于绝大多数的车牌都是蓝色背景这一特点(非标非蓝底车牌的识别,需要相对复杂,本文暂不讨论)。
//////////////////////////////////////////////////////////////////////////// Step1: segment blue background//////////////////////////////////////////////////////////////////////////void SegmentBackground2(Mat& img, Mat& binary){ binary.create(2, img.size, CV_8UC1); unsigned char *pSrcData, * pBinData; int width = img.cols; int height= img.rows; unsigned char* data = http://www.mamicode.com/binary.ptr();>
2. 对分割的结果进行同时进行连通区域标记和轮廓跟踪,根据轮廓对于每一个标记区域计算最小包围盒;根据连通区域面积对各独立区域进行排序。然后从大到小,按一定的准则开始合并这些连通区域,最终确定车牌区域。
int LabelBackgroundImg(Mat& binary, Mat& labelImg, std::vector<int>& area, std::vector<CContour>& exContours){ IplImage iplBinary = binary; labelImg.create(2, binary.size, CV_32S); int* pLabel = (int*)(labelImg.data); int num = LabelImagewithExContours(&iplBinary, pLabel, exContours); area.assign(num, 0); int w = binary.cols; int h = binary.rows; for (int i = 0; i < h; ++ i) { int* labelDat = (int*)(labelImg.ptr(i)); for (int j = 0; j < w; ++ j) { if (labelDat[j] > 0) { int n = labelDat[j] - 1; ++ area[n]; } } } return num;}void UniteComponets(std::vector<int>& area, std::vector<CContour>& exContours, RotatedRect& box, std::vector<int>& labels){ int n2 = exContours.size(); int nn = area.size(); int* ids = new int[nn]; int* area_ = new int[nn]; for (int i = 0; i < nn; ++ i){ ids[i] = i; area_[i] = area[i]; } for (int i = 0; i < nn - 1; ++ i) { for (int j = i + 1; j < nn; ++ j) { if (area_[i] < area_[j]) { int t = area_[i]; area_[i] = area_[j]; area_[j] = t; t = ids[i]; ids[i] = ids[j]; ids[j] = t; } } } delete[] area_; int id = ids[0]; CPointSet ptSet; ptSet.Resize(exContours[id].GetLenth()); exContours[id].ExportPointSet(ptSet); int orgPtNum = ptSet.GetPntsCount(); CvPoint* orgPts = new CvPoint[orgPtNum]; ptSet.ExportData(orgPts); Mat orgPtMat(1, orgPtNum, CV_32SC2, orgPts); RotatedRect orgBox = minAreaRect(orgPtMat); labels.clear(); labels.push_back(id); for (int i = 1; i < nn; ++ i) { int id = ids[i]; ptSet.Resize(exContours[id].GetLenth()); exContours[id].ExportPointSet(ptSet); int ptNum = ptSet.GetPntsCount(); CvPoint* pts = new CvPoint[orgPtNum + ptNum]; ptSet.ExportData(pts + orgPtNum); Mat ptMat(1, ptNum, CV_32SC2, pts + orgPtNum); RotatedRect box = minAreaRect(ptMat); memcpy(pts, orgPts, sizeof(CvPoint) * orgPtNum); Mat ptMatTotal(1, orgPtNum+ ptNum, CV_32SC2, pts); RotatedRect boxTotal = minAreaRect(ptMatTotal); if (boxTotal.boundingRect().area() < orgBox.boundingRect().area() + box.boundingRect().area() * 1.5) { labels.push_back(id); orgBox = boxTotal; orgPtNum += ptNum; CvPoint* temp = orgPts; orgPts = pts; pts = temp; } delete[] pts; } delete[] orgPts; delete[] ids; box = orgBox;}
3.重新得到车牌区域的二值图像。
void SetBinaryImage(Mat& binary, Mat& lable, int labelsTotalNum, std::vector<int> plateLabels){ unsigned char* labelMap = new unsigned char[labelsTotalNum + 1]; memset(labelMap, 0xff, labelsTotalNum + 1); for (unsigned int i = 0; i < plateLabels.size(); ++ i) { int id = plateLabels[i] + 1; //因为标记是从1开始的,而数组下标从0开始 labelMap[id] = 0x00; } unsigned char* bDat = binary.data; memset(bDat, 0xff, binary.step * binary.rows); int id; for (int i = 0; i < binary.rows; ++ i) { int* lDat = (int*)(lable.ptr(i)); bDat = binary.ptr(i); for (int j = 0; j < binary.cols; ++ j) { id = lDat[j]; if (id > 0) bDat[j] = labelMap[id]; } } delete[] labelMap;}
4.对图像进行归一化处理,用以解决图像大小不一的问题。得到大小相同的车牌区域的图像。
void NormalizeImage(Rect& rect, RotatedRect& box, Mat& binaryImg, Mat& plateImg, RotatedRect& newBox, Mat& newBinaryImg, Mat& newPlateImg){ Rect areRect = box.boundingRect(); double ratio = 300.0 / areRect.width; if (75.0 / areRect.height > ratio) ratio = 75.0 / areRect.height; int imgSize[2]; imgSize[0] = int(rect.height* ratio + 100.5f); imgSize[1] = int(rect.width * ratio + 100.5f); newBox.center.x = float(imgSize[1] / 2.0); newBox.center.y = float(imgSize[0] / 2.0); newBox.angle = box.angle; newBox.size.width = float(box.size.width * ratio); newBox.size.height= float(box.size.height * ratio); Point2f srcPnts[3]; srcPnts[0].x = float(rect.x); srcPnts[0].y = float(rect.y); srcPnts[1] = srcPnts[0]; srcPnts[1].x += rect.width; srcPnts[2] = srcPnts[0]; srcPnts[2].y += rect.height; Point2f dstPnts[3]; dstPnts[0].x = 50; dstPnts[0].y = 50; dstPnts[1] = dstPnts[0]; dstPnts[1].x += float(rect.width * ratio); dstPnts[2] = dstPnts[0]; dstPnts[2].y += float(rect.height* ratio); Mat transMat = getAffineTransform(srcPnts, dstPnts); //有没有简单的方法求这个变换? Size dstSize(imgSize[1], imgSize[0]); newBinaryImg.create(2, imgSize, binaryImg.type()); newBinaryImg = Scalar(255, 0, 0); warpAffine(binaryImg, newBinaryImg, transMat, dstSize, INTER_NEAREST, BORDER_TRANSPARENT);//是否可用resize函数替代 newPlateImg.create(2, imgSize, plateImg.type()); newPlateImg = Scalar(255, 255, 255); warpAffine(plateImg, newPlateImg, transMat, dstSize, INTER_LINEAR, BORDER_TRANSPARENT); //是否可用resize函数替代}// Step2.2: normalize binary image blocks//////////////////////////////////////////////////////////////////////////void Dilate_ErodeBinaryImg(Mat& binaryImg){ int height= binaryImg.rows; int width = binaryImg.cols; Mat dist; distanceTransform(binaryImg, dist, CV_DIST_L2, CV_DIST_MASK_PRECISE); int i, j; for (i = 0; i < height; ++ i) { float* distDat = (float*)(dist.ptr(i)); unsigned char* binDat = (unsigned char*)(binaryImg.ptr(i)); for (j = 0; j < width; ++ j) { if (distDat[j] < 7.5) binDat[j] = 0; } } dilate(binaryImg, binaryImg, Mat(), Point(-1, -1), 3);}
5.通过图像匹配技术,得到车牌区域的透视投影下的校正图像。
bool RectifyPlateImage(Mat& Img, Mat& binary, RotatedRect& box, Mat& rectifiedImg){ Mat edge = binary.clone(); Canny(binary, edge, 50, 120, 5); //可用简单的方法来实现 Mat iedge; edge.convertTo(iedge, -1, -1.0, 255); //for debug imwrite(_T("F:\\我的VC10应用程序\\plate num rec\\test\\test_contour_Img.bmp"), iedge); Mat dist; distanceTransform(iedge, dist, CV_DIST_L1, CV_DIST_MASK_PRECISE); for (int i = 0; i < dist.rows; ++ i) { float* dat = dist.ptr<float>(i); for (int j = 0; j < dist.cols; ++ j) dat[j] = sqrt(dat[j]); } int w = Img.cols; int h = Img.rows; Point2f verts[4]; box.points(verts); Point2f normalVerts[4]; GetMatchVertexs(verts, normalVerts); normalVerts[0].x -= 5; normalVerts[0].y -= 5; normalVerts[1].x += 5; normalVerts[1].y -= 5; normalVerts[2].x += 5; normalVerts[2].y += 5; normalVerts[3].x -= 5; normalVerts[3].y += 5; FitRectangle(dist, normalVerts); Point2f orgVerts[4]; float dd = 3; orgVerts[0].x = 32 - dd; orgVerts[0].y = 32 - dd; orgVerts[1].x =288 + dd; orgVerts[1].y = 32 - dd; orgVerts[2].x =288 + dd; orgVerts[2].y = 96 + dd; orgVerts[3].x = 32 - dd; orgVerts[3].y = 96 + dd; Mat transMat = getPerspectiveTransform(normalVerts, orgVerts); Size sizeDst(320, 128); warpPerspective(Img, rectifiedImg, transMat, sizeDst); return true;}void GetMatchVertexs(Point2f* verts, Point2f* normalVerts){ int indexs[4]; double maxMV = verts[0].x * verts[0].y; double minMV = verts[0].x * verts[0].y; double maxDV = verts[0].x / verts[0].y; double minDV = verts[0].x / verts[0].y; memset(indexs, 0x00, sizeof(int) * 4); for (int i = 1; i < 4; ++ i) { double tempM = verts[i].x * verts[i].y; double tempD = verts[i].x / verts[i].y; if (tempM > maxMV) { maxMV = tempM; indexs[2] = i; } else if (tempM < minMV) { minMV = tempM; indexs[0] = i; } if (tempD > maxDV) { maxDV = tempD; indexs[1] = i; } else if (tempD < minDV) { minDV = tempD; indexs[3] = i; } } for (int i = 0; i < 4; ++ i) normalVerts[i] = verts[indexs[i]];}
6.通过图像匹配技术,进一步分割得到各字符图像区域,
int GetBlueSpacePoints(Mat& templateImg, Point2d* templatePnts){ int h = templateImg.rows; int w = templateImg.cols; int cc = 0; for (int i = 0; i < h; ++ i) { unsigned char* pDat = templateImg.ptr(i); for (int j = 0; j < w; ++ j) { if (pDat[j] == 0) { templatePnts[cc].x = j; templatePnts[cc].y = i; ++ cc; } } } return cc;}bool BlueSpaceMatch(Mat& image, Point2d* templatePnts, int nCount, double val, Point2d center, double& scaleRatio, double& centerShiftX, double& centerShiftY){ double a_b[3]; double deltaX[3]; double AL[3]; double MM[9]; Point2d* pPntSet = new Point2d[nCount]; memcpy(pPntSet, templatePnts, sizeof(Point2d) * nCount); double* pL = new double[nCount]; double* pA = new double[nCount * 3]; for (int j = 0; j < nCount; ++ j){ pPntSet[j].x -= center.x; pPntSet[j].y -= center.y; } double va0, va1, va2, va3, va4; double sx, sy; double perror; perror = 0.0f; a_b[0] = scaleRatio; a_b[1] = centerShiftX; a_b[2] = centerShiftY; for (int i = 0; i < 100; ++ i) { for (int j = 0; j < nCount; ++ j) { sx = a_b[0] * pPntSet[j].x + a_b[1]; sy = a_b[0] * pPntSet[j].y + a_b[2]; va0 = GetDataValue(image, sx, sy); va1 = GetDataValue(image, sx + 0.5, sy); va2 = GetDataValue(image, sx - 0.5, sy); va3 = GetDataValue(image, sx, sy + 0.5); va4 = GetDataValue(image, sx, sy - 0.5); double dX = va1 - va2; double dY = va3 - va4; pA[j] = dX * pPntSet[j].x + dY * pPntSet[j].y; pA[j + 1 * nCount] = dX; pA[j + 2 * nCount] = dY; // A pL[j] = val - va0; // L = G - G(‘) } double error = 0.0; for(int j = 0; j < nCount; ++ j) error += pL[j] * pL[j]; if(i == 0) perror = error; if (perror < error) { for (int i = 0; i < 3; ++ i) { deltaX[i] *= 0.5; a_b[i] -= deltaX[i]; } int j; for(j = 0; j < 3; ++ j){ if(fabs(deltaX[j]) > 0.0000001) break; } if(j == 3) break; continue; } perror = error; int nOff_M = 0; for(int j = 0; j < 3; ++ j){ for(int jj = 0; jj < 3; ++ jj){ MM[nOff_M + jj] = 0.0; for(int kk = 0; kk < nCount; ++ kk) MM[nOff_M + jj] += pA[j * nCount + kk] * pA[jj * nCount + kk]; } // M = A * A(T) nOff_M += 3; } gslExInverseMatrix(MM, 3); // M = M(-) int j; for(j = 0; j < 3; ++ j){ AL[j] = 0.0; // AL = A * L for(int kk = 0; kk < nCount; ++ kk) AL[j] += pA[j * nCount + kk] * pL[kk]; } for(j = 0; j < 3; ++ j){ deltaX[j] = 0.0; for(int jj = 0; jj < 3; ++ jj) deltaX[j] += MM[j * 3 + jj] * AL[jj]; } // deltaX = (A * A(T))(-) * A(T) * L for(j = 0; j < 3; j++){ if(fabs(deltaX[j]) > 0.0000001) break; } if(j == 3) break; for(j = 0; j < 3; j++) a_b[j] += deltaX[j]; // a = a + delta / b = b + delta } delete[] pL; delete[] pA; scaleRatio = a_b[0]; centerShiftX = a_b[1]; centerShiftY = a_b[2]; delete [] pPntSet; return true;}int GetCharBlockRect(double& scaleRatio, double& centerShiftX, double& centerShiftY, Rect* rects){ Size templateImgSize; templateImgSize.width = 256; templateImgSize.height= 64;// Point2d center; center.x = templateImgSize.width / 2; center.y = templateImgSize.height/ 2; Rect templateBlockRect[7]; int xx[7] = {1, 36, 85, 120, 155, 190, 225}; for (int i = 0; i < 7; ++ i) { templateBlockRect[i].x = xx[i]; templateBlockRect[i].y = 4; templateBlockRect[i].width = 29; templateBlockRect[i].height= 57; }//// for (int i = 0; i < 7; ++ i) { templateBlockRect[i].x -= int(center.x + 0.5f); templateBlockRect[i].y -= int(center.y + 0.5f); rects[i].x = int(templateBlockRect[i].x * scaleRatio + centerShiftX + 0.5f); rects[i].y = int(templateBlockRect[i].y * scaleRatio + centerShiftY + 0.5f); rects[i].width = int(templateBlockRect[i].width * scaleRatio + 0.5f); rects[i].height= int(templateBlockRect[i].height* scaleRatio + 0.5f); } return 7;}int ExtractCharBlockImg(Mat& plateImg, Rect* rects, Mat* blockImgs){ Size dstSize; dstSize.width = 30; dstSize.height= 60; for (int i = 0; i < 7; ++ i) { int size[2]; size[0] = dstSize.width; size[1] = dstSize.height; blockImgs[i].create(2, size, plateImg.type()); Mat temp(plateImg, rects[i]); resize(temp, blockImgs[i], dstSize); } return 7;}
7.通过图像匹配技术,识别各个字符。
int RecognizeWord(InputArray blockImg);int RecognizeChar(InputArray blockImg);int RecognizeNumChar(InputArray blockImg);int RecognizeBlockImg(Mat& blockImg, int i){ if (i < 0 || i > 6) return -1; Mat grayImg; cvtColor(blockImg, grayImg, CV_RGB2GRAY); Mat grayImgInv; grayImg.convertTo(grayImgInv, -1, -1.0, 255); Mat binaryImg; adaptiveThreshold(grayImgInv, binaryImg, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, 51, 9); Mat binaryImgSmooth; GaussianBlur(binaryImg, binaryImgSmooth, Size(3, 3), 1.25f, 1.25f); imwrite(_T("F:\\我的VC10应用程序\\plate num rec\\test\\test_block_binary_char.bmp"), binaryImgSmooth); int nRes = -1; if (i == 0) nRes = RecognizeWord(binaryImgSmooth); else if (i == 1) nRes = RecognizeChar(binaryImgSmooth); else nRes = RecognizeNumChar(binaryImgSmooth); return nRes;}void MatchTemplatesWord(InputArray templatsImg_, InputArray charImg_, std::vector<double>& results);void MatchTemplatesChar(InputArray templatsImg_, InputArray charImg_, std::vector<double>& results);void MatchTemplatesNumChar(InputArray templatsImg_, InputArray numImg_, std::vector<double>& results);int RecognizeWord(InputArray blockImg){ Mat tempImg = imread(_T("..\\template\\word_grayScale_AutoSize_Smooth.bmp"), CV_LOAD_IMAGE_GRAYSCALE); std::vector<double> results; MatchTemplatesWord(tempImg, blockImg, results); double minVal = results[0]; unsigned int nl = 0; for (unsigned int i = 1; i < results.size(); ++ i) { if (results[i] < minVal) { minVal = results[i]; nl = i; } } return nl;}int RecognizeChar(InputArray blockImg){ Mat tempImg = imread(_T("..\\template\\char_grayScale_AutoSize_Smooth.bmp"), CV_LOAD_IMAGE_GRAYSCALE); std::vector<double> results; MatchTemplatesChar(tempImg, blockImg, results); double minVal = results[0]; unsigned int nl = 0; for (unsigned int i = 1; i < results.size(); ++ i) { if (results[i] < minVal) { minVal = results[i]; nl = i; } } return nl;}int RecognizeNumChar(InputArray blockImg){ Mat tempImg = imread(_T("..\\template\\num_char_grayScale_AutoSize_Smooth.bmp"), CV_LOAD_IMAGE_GRAYSCALE); std::vector<double> results; MatchTemplatesNumChar(tempImg, blockImg, results); double minVal = results[0]; unsigned int nl = 0; for (unsigned int i = 1; i < results.size(); ++ i) { if (results[i] < minVal) { minVal = results[i]; nl = i; } } return nl;}
代码比较多,有部分不影响主线的代码就没有贴出来了。如果大家有兴趣讨论可以私信我哈。秋秋31667460
基于‘匹配’技术的车牌自动识别系统