EasyPR源碼剖析(5):車牌定位之偏斜扭轉


一、簡介

通過顏色定位和Sobel算子定位可以計算出一個個的矩形區域,這些區域都是潛在車牌區域,但是在進行SVM判別是否是車牌之前,還需要進行一定的處理。主要是考慮到以下幾個問題:

1、定位區域存在一定程度的傾斜,需要旋轉到正常視角;

2、定位區域存在偏斜,除了進行旋轉之后,還需要進行仿射變換;

3、定位出區域的大小不一致,需要對車牌的尺寸進行統一。

 仿射變換(Affine Transformation 或 Affine Map),又稱仿射映射,是指在幾何中,一個向量空間進行一次線性變換並接上一個平移,變換為另一個向量空間的過程。它保持了二維圖形的“平直性”(即:直線經過變換之后依然是直線)和“平行性”(即:二維圖形之間的相對位置關系保持不變,平行線依然是平行線,且直線上點的位置順序不變)。

一個任意的仿射變換都能表示為乘以一個矩陣(線性變換)接着再加上一個向量(平移)的形式。

那么, 我們能夠用仿射變換來表示如下三種常見的變換形式:

  • 旋轉,rotation (線性變換)
  • 平移,translation(向量加)
  • 縮放,scale(線性變換)

如果進行更深層次的理解,仿射變換代表的是兩幅圖之間的一種映射關系。這類變換可以用一個3*3的矩陣M來表示,其最后一行為(0,0,1)。該變換矩陣將原坐標為(x,y)變換為新坐標(x',y')。

對應的opencv函數如下:

1、getRotationMatrix2D

  Mat getRotationMatrix2D(Point2f center,  angle,  scale)
       已知旋轉中心坐標(坐標原點為圖像左上端點)、旋轉角度(單位為度°,順時針為負,逆時針為正)、放縮比例,返回旋轉/放縮矩陣。

2、warpAffine

    void warpAffine(InputArray src, 

        OutputArray dst,

           InputArray M, 

        Size dsize,

           int flags=INTER_LINEAR,

           int borderMode=BORDER_CONSTANT, 

        const Scalar& borderValue=Scalar())

   根據getRotationMatrix2D得到的變換矩陣,計算變換后的圖像。warpAffine 方法要求輸入的參數是原始圖像的左上點,右上點,左下點,以及輸出圖像的左上點,右上點,左下點。注意,必須保證這些點的對應順序,否則仿射的效果跟你預想的不一樣。因此 opencv 需要的是三個點對(共六個點)的坐標,然后建立一個映射關系,通過這個映射關系將原始圖像的所有點映射到目標圖像上。 如下圖所示:

二、偏斜扭轉

具體偏斜扭轉的代碼如下所示:

  1 int CPlateLocate::deskew(const Mat &src, const Mat &src_b,
  2                          vector<RotatedRect> &inRects,
  3                          vector<CPlate> &outPlates, bool useDeteleArea, Color color) {
  4   Mat mat_debug;
  5   src.copyTo(mat_debug);
  6 
  7   for (size_t i = 0; i < inRects.size(); i++) {
  8     RotatedRect roi_rect = inRects[i];
  9 
 10     float r = (float) roi_rect.size.width / (float) roi_rect.size.height;
 11     float roi_angle = roi_rect.angle;
 12 
 13     Size roi_rect_size = roi_rect.size;
 14     if (r < 1) {
 15       roi_angle = 90 + roi_angle;
 16       swap(roi_rect_size.width, roi_rect_size.height);
 17     }
 18 
 19     if (m_debug) {
 20       Point2f rect_points[4];
 21       roi_rect.points(rect_points);
 22       for (int j = 0; j < 4; j++)
 23         line(mat_debug, rect_points[j], rect_points[(j + 1) % 4],
 24              Scalar(0, 255, 255), 1, 8);
 25     }
 26 
 27     // changed
 28     // rotation = 90 - abs(roi_angle);
 29     // rotation < m_angel;
 30     // m_angle=60
 31     if (roi_angle - m_angle < 0 && roi_angle + m_angle > 0) {
 32       Rect_<float> safeBoundRect;
 33       bool isFormRect = calcSafeRect(roi_rect, src, safeBoundRect);
 34       if (!isFormRect) continue;
 35 
 36       Mat bound_mat = src(safeBoundRect);
 37       Mat bound_mat_b = src_b(safeBoundRect);
 38 
 39       if (0) {
 40         imshow("bound_mat_b", bound_mat_b);
 41         waitKey(0);
 42         destroyWindow("bound_mat_b");
 43       }
 44 
 45       Point2f roi_ref_center = roi_rect.center - safeBoundRect.tl();
 46 
 47       Mat deskew_mat;
 48       if ((roi_angle - 5 < 0 && roi_angle + 5 > 0) || 90.0 == roi_angle ||
 49           -90.0 == roi_angle) {
 50         deskew_mat = bound_mat;
 51       } else {
 52 
 53 
 54         Mat rotated_mat;
 55         Mat rotated_mat_b;
 56 
 57         if (!rotation(bound_mat, rotated_mat, roi_rect_size, roi_ref_center,
 58                       roi_angle))
 59           continue;
 60 
 61         if (!rotation(bound_mat_b, rotated_mat_b, roi_rect_size, roi_ref_center,
 62                       roi_angle))
 63           continue;
 64 
 65         // we need affine for rotatioed image
 66         double roi_slope = 0;
 67 
 68         if (isdeflection(rotated_mat_b, roi_angle, roi_slope)) {
 69           affine(rotated_mat, deskew_mat, roi_slope);
 70         } else
 71           deskew_mat = rotated_mat;
 72       }
 73 
 74 
 75       Mat plate_mat;
 76       plate_mat.create(HEIGHT, WIDTH, TYPE);
 77 
 78       // haitungaga add,affect 25% to full recognition.
 79       if (useDeteleArea)
 80         deleteNotArea(deskew_mat, color);
 81 
 82 
 83       if (deskew_mat.cols * 1.0 / deskew_mat.rows > 2.3 &&
 84           deskew_mat.cols * 1.0 / deskew_mat.rows < 6) {
 85 
 86         if (deskew_mat.cols >= WIDTH || deskew_mat.rows >= HEIGHT)
 87           resize(deskew_mat, plate_mat, plate_mat.size(), 0, 0, INTER_AREA);
 88         else
 89           resize(deskew_mat, plate_mat, plate_mat.size(), 0, 0, INTER_CUBIC);
 90 
 91         CPlate plate;
 92         plate.setPlatePos(roi_rect);
 93         plate.setPlateMat(plate_mat);
 94         if (color != UNKNOWN) plate.setPlateColor(color);
 95 
 96         outPlates.push_back(plate);
 97       }
 98     }
 99   }
100 
101   return 0;
102 }
View Code

下面我們對代碼的主要邏輯過程做一個簡單的梳理,對於定位后的車牌,首先進行旋轉角度的判定,在-5°~5°范圍內車牌直接輸出,在-60°~ -5°和 5°~60°范圍內車牌,首先進行偏斜程度的判定,如果偏斜程度不嚴重,旋轉后輸出,否則旋轉角度后還需要仿射變換。

rotation()函數主要用於對傾斜的圖片進行旋轉, 具體的代碼如下:

 1 bool CPlateLocate::rotation(Mat &in, Mat &out, const Size rect_size,
 2                             const Point2f center, const double angle) {
 3   Mat in_large;
 4   in_large.create(int(in.rows * 1.5), int(in.cols * 1.5), in.type());
 5 
 6   float x = in_large.cols / 2 - center.x > 0 ? in_large.cols / 2 - center.x : 0;
 7   float y = in_large.rows / 2 - center.y > 0 ? in_large.rows / 2 - center.y : 0;
 8 
 9   float width = x + in.cols < in_large.cols ? in.cols : in_large.cols - x;
10   float height = y + in.rows < in_large.rows ? in.rows : in_large.rows - y;
11 
12   /*assert(width == in.cols);
13   assert(height == in.rows);*/
14 
15   if (width != in.cols || height != in.rows) return false;
16 
17   Mat imageRoi = in_large(Rect_<float>(x, y, width, height));
18   addWeighted(imageRoi, 0, in, 1, 0, imageRoi);
19 
20   Point2f center_diff(in.cols / 2.f, in.rows / 2.f);
21   Point2f new_center(in_large.cols / 2.f, in_large.rows / 2.f);
22 
23   Mat rot_mat = getRotationMatrix2D(new_center, angle, 1);
24 
25   /*imshow("in_copy", in_large);
26   waitKey(0);*/
27 
28   Mat mat_rotated;
29   warpAffine(in_large, mat_rotated, rot_mat, Size(in_large.cols, in_large.rows),
30              CV_INTER_CUBIC);
31 
32   /*imshow("mat_rotated", mat_rotated);
33   waitKey(0);*/
34 
35   Mat img_crop;
36   getRectSubPix(mat_rotated, Size(rect_size.width, rect_size.height),
37                 new_center, img_crop);
38 
39   out = img_crop;
40 
41   if (0) {
42     imshow("out", out);
43     waitKey(0);
44     destroyWindow("out");
45   }
46 
47   /*imshow("img_crop", img_crop);
48   waitKey(0);*/
49 
50   return true;
51 }
View Code

在旋轉的過程當中,遇到一個問題,就是旋轉后的圖像被截斷了,如下圖所示:

仔細分析下代碼可以發現,getRotationMatrix2D()  函數主要根據旋轉中心和角度進行旋轉,當旋轉角度還小時,一切都還好,但當角度變大時,明顯我們看到的外接矩形的大小也在擴增。在這里,外接矩形被稱為視框,也就是我需要旋轉的正方形所需要的最小區域。隨着旋轉角度的變大,視框明顯增大。 如下圖所示:

EasyPR使用了一個極為簡單的策略,它將原始圖像與目標圖像都進行了擴大化。首先新建一個尺寸為原始圖像 1.5 倍的新圖像,接着把原始圖像映射到新圖像上,於是我們得到了一個顯示區域(視框)擴大化后的原始圖像。顯示區域擴大以后,那些在原圖像中沒有值的像素被置了一個初值。接着調用 warpAffine 函數,使用新圖像的大小作為目標圖像的大小。warpAffine 函數會將新圖像旋轉,並用目標圖像尺寸的視框去顯示它。於是我們得到了一個所有感興趣區域都被完整顯示的旋轉后圖像,這樣,我們再使用 getRectSubPix()函數就可以獲得想要的車牌區域了。

接下來就是分析截取后的車牌區域。車牌區域里的車牌分為正角度和偏斜角度兩種。對於正的角度而言,可以看出車牌區域就是車牌,因此直接輸出即可。而對於偏斜角度而言,車牌是平行四邊形,與矩形的車牌區域不重合。如何判斷一個圖像中的圖形是否是平行四邊形?

一種簡單的思路就是對圖像二值化,然后根據二值化圖像進行判斷。為了判斷二值化圖像中白色的部分是平行四邊形。一種簡單的做法就是從圖像中選擇一些特定的行。計算在這個行中,第一個全為0的串的長度。從幾何意義上來看, 這就是平行四邊形斜邊上某個點距離外接矩形的長度。假設我們選擇的這些行位於二值化圖像高度的 1/4,2/4,3/4 處的話,如果是白色圖形是矩形的話, 這些串的大小應該是相等或者相差很小的,相反如果是平行四邊形的話,那么這些串的大小應該不等,並 且呈現一個遞增或遞減的關系。通過這種不同,我們就可以判斷車牌區域里的圖形,究竟是矩形還是平行 四邊形。

偏斜判斷的另一個重要作用就是,計算平行四邊形傾斜的斜率,這個斜率值用來在下面的仿射變換中 發揮作用。我們使用一個簡單的公式去計算這個斜率,那就是利用上面判斷過程中使用的串大小,假設二值化圖像高度的 1/4,2/4,3/4 處對應的串的大小分別為 len1,len2,len3,車牌區域的高度為 Height。 一個計算斜率 slope 的計算公式就是:(len3-len1)/Height*2。     

函數 isdeflection()  的主要功能是判斷車牌偏斜的程度,並且計算偏斜的值。具體代碼如下:

 1 bool CPlateLocate::isdeflection(const Mat &in, const double angle,
 2                                 double &slope) { 
 3   int nRows = in.rows;
 4   int nCols = in.cols;
 5 
 6   assert(in.channels() == 1);
 7 
 8   int comp_index[3];
 9   int len[3];
10 
11   comp_index[0] = nRows / 4;
12   comp_index[1] = nRows / 4 * 2;
13   comp_index[2] = nRows / 4 * 3;
14 
15   const uchar* p;
16 
17   for (int i = 0; i < 3; i++) {
18     int index = comp_index[i];
19     p = in.ptr<uchar>(index);
20 
21     int j = 0;
22     int value = 0;
23     while (0 == value && j < nCols) value = int(p[j++]);
24 
25     len[i] = j;
26   }
27 
28   // len[0]/len[1]/len[2] are used to calc the slope
29 
30   double maxlen = max(len[2], len[0]);
31   double minlen = min(len[2], len[0]);
32   double difflen = abs(len[2] - len[0]);
33 
34   double PI = 3.14159265;
35 
36   double g = tan(angle * PI / 180.0);
37 
38   if (maxlen - len[1] > nCols / 32 || len[1] - minlen > nCols / 32) {
39 
40     double slope_can_1 =
41         double(len[2] - len[0]) / double(comp_index[1]);
42     double slope_can_2 = double(len[1] - len[0]) / double(comp_index[0]);
43     double slope_can_3 = double(len[2] - len[1]) / double(comp_index[0]);
44     slope = abs(slope_can_1 - g) <= abs(slope_can_2 - g) ? slope_can_1
45                                                          : slope_can_2;
46     return true;
47   } else {
48     slope = 0;
49   }
50 
51   return false;
52 }
View Code

我們已經實現了旋轉功能,並且在旋轉后的區域中截取了車牌區域,然后判斷車牌區域中的圖形是一 個平行四邊形。下面要做的工作就是把平行四邊形扭正成一個矩形。

 函數 affine() 的主要功能是對圖像進行根據偏斜角度,進行仿射變換。具體代碼如下: 

 1 void CPlateLocate::affine(const Mat &in, Mat &out, const double slope) {
 2 
 3   Point2f dstTri[3];
 4   Point2f plTri[3];
 5 
 6   float height = (float) in.rows;
 7   float width = (float) in.cols;
 8   float xiff = (float) abs(slope) * height;
 9 
10   if (slope > 0) {
11 
12     // right, new position is xiff/2
13 
14     plTri[0] = Point2f(0, 0);
15     plTri[1] = Point2f(width - xiff - 1, 0);
16     plTri[2] = Point2f(0 + xiff, height - 1);
17 
18     dstTri[0] = Point2f(xiff / 2, 0);
19     dstTri[1] = Point2f(width - 1 - xiff / 2, 0);
20     dstTri[2] = Point2f(xiff / 2, height - 1);
21   } else {
22 
23     // left, new position is -xiff/2
24 
25     plTri[0] = Point2f(0 + xiff, 0);
26     plTri[1] = Point2f(width - 1, 0);
27     plTri[2] = Point2f(0, height - 1);
28 
29     dstTri[0] = Point2f(xiff / 2, 0);
30     dstTri[1] = Point2f(width - 1 - xiff + xiff / 2, 0);
31     dstTri[2] = Point2f(xiff / 2, height - 1);
32   }
33 
34   Mat warp_mat = getAffineTransform(plTri, dstTri);
35 
36   Mat affine_mat;
37   affine_mat.create((int) height, (int) width, TYPE);
38 
39   if (in.rows > HEIGHT || in.cols > WIDTH)
40 
41     warpAffine(in, affine_mat, warp_mat, affine_mat.size(),
42                CV_INTER_AREA);
43   else
44     warpAffine(in, affine_mat, warp_mat, affine_mat.size(), CV_INTER_CUBIC);
45 
46   out = affine_mat;
47 }
View Code

最后使用 resize 函數將車牌區域統一化為 EasyPR 的車牌大小,大小為136*36。

 

                 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM