Original address: opencv for iOS Study Notes (5)-mark Detection 2
Relevance search
Void markerdetector: const extends svector & contours, STD: vector <marker> & detectedmarkers) {pointsvector approxcurve; // STD: vector <marker> possiblemarkers; /// possible tags // analyze each tag. If it is a label-like parallel cube... for (size_t I = 0; I <contours. size (); I ++) {// returns an approximate polygon double EPS = contours [I]. size () * 0.05; // smooths the polygon edge to obtain an approximate polygon CV: approxpolydp (contours [I], approxcurve, EPS, true). // only the quadrilateral is considered here. If (approxcurve. Size ()! = 4) continue; // it must be a convex if (! CV: isw.convex (approxcurve) continue; // ensure that the distance between two adjacent points is "large enough"-the distance is one side rather than the short segment. Float mindist = STD :: numeric_limits <float>: max (); For (INT I = 0; I <4; I ++) {CV :: point Side = approxcurve [I]-approxcurve [(I + 1) % 4]; float squaredsidelength = side. DOT (side); // obtain the minimum mindist = STD: min (mindist, squaredsidelength);} // ensure that the distance is not too short if (mindist <m_mininclulengthallowed) continue; // save similar marker m; For (INT I = 0; I <4; I ++) {M. points. push_back (CV: point2f (approxcurve [I]. x, approxcurve [I]. y);} // arrange the points counter-clockwise. // connect a line between the first point and the second point. // if the third point is on the right, the points are counter-clockwise -??? CV: Point V1 = m. points [1]-M. points [0]; CV: point V2 = m. points [2]-M. points [0]; double O = (v1.x * v2.y)-(v1.y * v2.x); If (O <0.0) // if the third point is on the left, then you need to arrange the point into a counter-clockwise {// http://blog.csdn.net/dizuo/article/details/6435847 // exchange the location of two points STD: swap (M. points [1], M. points [3]);} possiblemarkers. push_back (m);} // remove elements that are too close to the corner. // first detection similarity STD: vector <STD: pair <int, int> toonearcandidates; for (size_t I = 0; I <Po Ssiblemarkers. size (); I ++) {const marker & M1 = possiblemarkers [I]; // calculate the average distance from each corner to the nearest corner that may be marked. For (size_t J = I + 1; j <possiblemarkers. size (); j ++) {const marker & m2 = possiblemarkers [J]; float distsquared = 0.0; For (int c = 0; C <4; C ++) {CV: Point V = m1.points [c]-m2.points [c]; // distance between two points in the dot multiplication-"distsquared + = v. DOT (V);} distsquared/= 4; If (distsquared <100) {toonearcandidates. push _ Back (STD: pair <int, int> (I, j) ;}} STD: vector <bool> removalmask (possiblemarkers. size (), false); For (size_t I = 0; I <toonearcandidates. size (); I ++) {// perimeter float p1 = perimeter (possiblemarkers [toonearcandidates [I]. first]. points); float P2 = perimeter (possiblemarkers [toonearcandidates [I]. second]. points); size_t removalindex; If (P1> P2) {removalindex = toonearcandidates [I]. second;} else {Rem Ovalindex = toonearcandidates [I]. first;} removalmask [removalindex] = true;} // return the possible object detectedmarkers. clear (); For (size_t I = 0; I <possiblemarkers. size (); I ++) {If (! Removalmask [I]) {detectedmarkers. push_back (possiblemarkers [I]) ;}}
We have obtained a series of suspicious tags in the above method. To further confirm whether they are the tags we want, we also need the following three steps:
1. Remove the Perspective Projection to get the rectangle on the plane/front.
2. Use the Otsu algorithm to calculate the threshold value of an image.
3. Finally, the mark recognition code.
To obtain the labeled images of these rectangles, we have to use perspective transformations to restore (unwarp) the input images. This matrix should use the CV: getperspectivetransform function. It first finds the perspective transformation based on four corresponding points. The first parameter is the mark coordinate, and the second is the coordinate of the square mark image. The estimated transformation will convert the mark into a square to facilitate our analysis.
// Analyze each captured tag marker & marker = detectedmarkers [I]; // locate the Perspective Projection, convert the marker to a rectangle. // enter the coordinate of the Quadrilateral vertex of the image. // the coordinate of the corresponding quadrilateral vertex of the output image. CV: mat markertransform = CV: getperspectivetransform (marker. points, m_markercorners2d); // Transform Image to get a canonical marker image // input image // output image // 3x3 transformation matrix CV: warpperspective (grayscale, canonicalmarkerimage, markertransform, markersize );
Convert the image into a forward view:
Now test whether our tag is valid.
Use the Otsu algorithm to remove gray pixels, leaving only black and white pixels.
// This is a fixed threshold method // The input image must be a 2-value single-channel image // an array of detected outlines, each profile is represented by a point-type vector // threshold value // max_value uses the maximum value of cv_thresh_binary and cv_thresh_binary_inv // type CV: threshold (Gray, gray, 125,255, CV :: thresh_binary | CV: thresh_otsu );
Mark Encoding
The tags used by US (authors) all have an internal 5x5 code, which uses a simplified Chinese code. Simply put, only 2 bits in 5 bits are used, and the other three are incorrect identifiers, that is, we have up to 1024 different identifiers.
The biggest difference between our Chinese codes is that the first digit of the Chinese codes (3 and 5 of the parity bit) is reverse. All ID 0 (in the Hamming code is 00000), here is 10000, the purpose is to reduce the impact of the environment (?).
Then we calculate the number of black and white pixels in each 5x5 area based on the tag.
Recognition mark Encoding
// Store the matrix CV: mat bitmatrix = CV: mat: zeros (5, 5, cv_8uc1) of the judgment structure; // you can determine each 5x5 region, whether it is a white pixel or a black pixel for (INT y = 0; y <5; y ++) {for (INT x = 0; x <5; X ++) {int cellx = (x + 1) * cellsize; int celly = (Y + 1) * cellsize; // create an image of different gray sizes/* is equivalent to the following CV :: rect (cellx, celly, cellsize, cellsize); CV: mat cell = gray (rect); */CV: mat cell = gray (CV: rect (cellx, celly, cellsize, cellsize); // calculate the number of non-0 pixels int NZ = CV: countnonzero (cell); If (NZ> (cellsize * cellsize)/2) bitmatrix. at <uchar> (Y, x) = 1 ;}}
Depending on the camera angle, there are four possible shapes marked:
We already have four possible image tags, and we have to find the correct form of tags. We have known Three parity bits for each 2bits (only 2bits is valid for each 5bits, and the remaining three are for verification ). Correct labeling should have 0 Hamming distance error!
// Check all possible locations CV: mat rotations [4]; int distances [4]; rotations [0] = bitmatrix; // The Hamming distance from distances [0] = hammdistmarker (rotations [0]); STD: pair <int, int> mindist (distances [0], 0 ); for (INT I = 1; I <4; I ++) {// find the smallest Hamming distance from rotations [I] = rotate (rotations [I-1]); distances [I] = hammdistmarker (rotations [I]); If (distances [I] <mindist. first) {mindist. first = distances [I]; mindist. second = I ;}}
The minimum Hamming distance found in the code above should be 0. If not, it is an incorrect tag mode-in case of a corrupted tag or false positive tag detection (false-positive marker detection ).
-Hamming distance:
In information theory, the Chinese distance between two equal-length strings is the number of characters at the corresponding position of the two strings. In other words, it is the number of characters to replace a string into another string.
Change location:
cv::Mat Marker::rotate(cv::Mat in){ cv::Mat out; in.copyTo(out); for (int i=0;i<in.rows;i++) { for (int j=0;j<in.cols;j++) { out.at<uchar>(i,j)=in.at<uchar>(in.cols-j-1,i); } } return out;}