From e38ea3a888fec6baff9fee3f401ef60e293f6913 Mon Sep 17 00:00:00 2001 From: Alexander Nesterov Date: Tue, 10 Jul 2018 14:24:09 -0300 Subject: [PATCH] Update detect QRCode algorithm --- modules/objdetect/include/opencv2/objdetect.hpp | 15 + modules/objdetect/src/qrcode.cpp | 811 ++++++++++++------------ modules/objdetect/test/test_qrcode.cpp | 173 +++-- 3 files changed, 543 insertions(+), 456 deletions(-) diff --git a/modules/objdetect/include/opencv2/objdetect.hpp b/modules/objdetect/include/opencv2/objdetect.hpp index 7c36bac..bd8bbf0 100644 --- a/modules/objdetect/include/opencv2/objdetect.hpp +++ b/modules/objdetect/include/opencv2/objdetect.hpp @@ -670,6 +670,21 @@ public: void groupRectangles(std::vector& rectList, std::vector& weights, int groupThreshold, double eps) const; }; +class CV_EXPORTS QRCodeDetector +{ +public: + QRCodeDetector(); + ~QRCodeDetector(); + + void setEpsX(double epsX); + void setEpsY(double epsY); + + bool detect(InputArray in, OutputArray points) const; +protected: + struct Impl; + Ptr p; +}; + /** @brief Detect QR code in image and return minimum area of quadrangle that describes QR code. @param in Matrix of the type CV_8UC1 containing an image where QR code are detected. @param points Output vector of vertices of a quadrangle of minimal area that describes QR code. diff --git a/modules/objdetect/src/qrcode.cpp b/modules/objdetect/src/qrcode.cpp index e3c8d55..731e74f 100644 --- a/modules/objdetect/src/qrcode.cpp +++ b/modules/objdetect/src/qrcode.cpp @@ -15,45 +15,52 @@ namespace cv { +using std::vector; + class QRDecode { - public: - void init(Mat src, double eps_vertical_ = 0.19, double eps_horizontal_ = 0.09); +public: + void init(Mat src, double eps_vertical_ = 0.2, double eps_horizontal_ = 0.1); void binarization(); bool localization(); bool transformation(); Mat getBinBarcode() { return bin_barcode; } - Mat getLocalizationBarcode() { return local_barcode; } - Mat getTransformationBarcode() { return transform_barcode; } - std::vector getTransformationPoints() { return transformation_points; } Mat getStraightBarcode() { return straight_barcode; } - protected: - std::vector searchVerticalLines(); - std::vector separateHorizontalLines(std::vector list_lines); - std::vector pointClustering(std::vector list_lines); - void fixationPoints(std::vector &local_point, std::vector &local_len); - Point getTransformationPoint(Point left, Point center, double cos_angle_rotation, - bool right_rotate = true); - Point intersectionLines(Point a1, Point a2, Point b1, Point b2); - std::vector getQuadrilateral(std::vector angle_list); - double getQuadrilateralArea(Point a, Point b, Point c, Point d); - double getCosVectors(Point a, Point b, Point c); - - Mat barcode, bin_barcode, local_barcode, transform_barcode, straight_barcode; - std::vector localization_points, transformation_points; - std::vector localization_length; - double experimental_area; - - double eps_vertical, eps_horizontal; - std::vector result; - std::vector test_lines; - uint8_t next_pixel, future_pixel; - double length, weight; + vector getTransformationPoints() { return transformation_points; } +protected: + bool computeTransformationPoints(); + vector searchVerticalLines(); + vector separateHorizontalLines(vector list_lines); + void fixationPoints(vector &local_point); + Point2f intersectionLines(Point2f a1, Point2f a2, Point2f b1, Point2f b2); + vector getQuadrilateral(vector angle_list); + bool testBypassRoute(vector hull, int start, int finish); + double getQuadrilateralArea(Point2f a, Point2f b, Point2f c, Point2f d); + double getTriangleArea(Point2f a, Point2f b, Point2f c); + double getCosVectors(Point2f a, Point2f b, Point2f c); + + Mat barcode, bin_barcode, straight_barcode; + vector localization_points, transformation_points; + double experimental_area, eps_vertical, eps_horizontal, coeff_expansion; }; + void QRDecode::init(Mat src, double eps_vertical_, double eps_horizontal_) { - barcode = src; + double min_side = std::min(src.size().width, src.size().height); + if (min_side < 512.0) + { + coeff_expansion = 512.0 / min_side; + int width = static_cast(src.size().width * coeff_expansion); + int height = static_cast(src.size().height * coeff_expansion); + Size new_size(width, height); + resize(src, barcode, new_size); + } + else + { + coeff_expansion = 1.0; + barcode = src; + } eps_vertical = eps_vertical_; eps_horizontal = eps_horizontal_; } @@ -65,37 +72,12 @@ void QRDecode::binarization() threshold(filter_barcode, bin_barcode, 0, 255, THRESH_BINARY + THRESH_OTSU); } -bool QRDecode::localization() -{ - cvtColor(bin_barcode, local_barcode, COLOR_GRAY2RGB); - Point begin, end; - - std::vector list_lines_x = searchVerticalLines(); - if (list_lines_x.empty()) return false; - std::vector list_lines_y = separateHorizontalLines(list_lines_x); - if (list_lines_y.empty()) return false; - std::vector result_point = pointClustering(list_lines_y); - if (result_point.empty()) return false; - for (int i = 0; i < 3; i++) - { - localization_points.push_back( - Point(static_cast(result_point[i][0]), - static_cast(result_point[i][1] + result_point[i][2]))); - localization_length.push_back(result_point[i][2]); - } - - fixationPoints(localization_points, localization_length); - - - if (localization_points.size() != 3) { return false; } - return true; - -} - -std::vector QRDecode::searchVerticalLines() +vector QRDecode::searchVerticalLines() { - result.clear(); + vector result; int temp_length = 0; + uint8_t next_pixel, future_pixel; + vector test_lines; for (int x = 0; x < bin_barcode.rows; x++) { @@ -125,7 +107,7 @@ std::vector QRDecode::searchVerticalLines() if (test_lines.size() == 5) { - length = 0.0; weight = 0.0; + double length = 0.0, weight = 0.0; for (size_t i = 0; i < test_lines.size(); i++) { length += test_lines[i]; } @@ -147,16 +129,17 @@ std::vector QRDecode::searchVerticalLines() return result; } -std::vector QRDecode::separateHorizontalLines(std::vector list_lines) +vector QRDecode::separateHorizontalLines(vector list_lines) { - result.clear(); + vector result; int temp_length = 0; - int x, y; + uint8_t next_pixel, future_pixel; + vector test_lines; for (size_t pnt = 0; pnt < list_lines.size(); pnt++) { - x = static_cast(list_lines[pnt][0] + list_lines[pnt][2] / 2); - y = static_cast(list_lines[pnt][1]); + int x = static_cast(list_lines[pnt][0] + list_lines[pnt][2] / 2); + int y = static_cast(list_lines[pnt][1]); // --------------- Search horizontal up-lines --------------- // test_lines.clear(); @@ -195,7 +178,7 @@ std::vector QRDecode::separateHorizontalLines(std::vector list_lin if (test_lines.size() == 6) { - length = 0.0; weight = 0.0; + double length = 0.0, weight = 0.0; for (size_t i = 0; i < test_lines.size(); i++) { length += test_lines[i]; } @@ -204,83 +187,26 @@ std::vector QRDecode::separateHorizontalLines(std::vector list_lin if (i % 3 == 0) { weight += abs((test_lines[i] / length) - 3.0/14.0); } else { weight += abs((test_lines[i] / length) - 1.0/ 7.0); } } - } - - if(weight < eps_horizontal) - { - result.push_back(list_lines[pnt]); - } - } - return result; -} - -std::vector QRDecode::pointClustering(std::vector list_lines) -{ - std::vector centers; - std::vector clusters[3]; - double weight_clusters[3] = {0.0, 0.0, 0.0}; - Point basis[3], temp_pnt; - double temp_norm = 0.0, temp_compute_norm, distance[3]; - - basis[0] = Point(static_cast(list_lines[0][1]), static_cast(list_lines[0][0])); - for (size_t i = 1; i < list_lines.size(); i++) - { - temp_pnt = Point(static_cast(list_lines[i][1]), static_cast(list_lines[i][0])); - temp_compute_norm = norm(basis[0] - temp_pnt); - if (temp_norm < temp_compute_norm) - { - basis[1] = temp_pnt; - temp_norm = temp_compute_norm; - } - } - for (size_t i = 1; i < list_lines.size(); i++) - { - temp_pnt = Point(static_cast(list_lines[i][1]), static_cast(list_lines[i][0])); - temp_compute_norm = norm(basis[0] - temp_pnt) + norm(basis[1] - temp_pnt); - if (temp_norm < temp_compute_norm) - { - basis[2] = temp_pnt; - temp_norm = temp_compute_norm; - } - } + if(weight < eps_horizontal) + { + result.push_back(list_lines[pnt]); + } - for (size_t i = 0; i < list_lines.size(); i++) - { - temp_pnt = Point(static_cast(list_lines[i][1]), static_cast(list_lines[i][0])); - distance[0] = norm(basis[0] - temp_pnt); - distance[1] = norm(basis[1] - temp_pnt); - distance[2] = norm(basis[2] - temp_pnt); - if (distance[0] < distance[1] && distance[0] < distance[2]) - { - clusters[0].push_back(temp_pnt); - weight_clusters[0] += list_lines[i][2]; - } - else if (distance[1] < distance[0] && distance[1] < distance[2]) - { - clusters[1].push_back(temp_pnt); - weight_clusters[1] += list_lines[i][2]; - } - else - { - clusters[2].push_back(temp_pnt); - weight_clusters[2] += list_lines[i][2]; } } - for (int i = 0; i < 3; i++) + vector point2f_result; + for (size_t i = 0; i < result.size(); i++) { - basis[i] = Point(0, 0); - for (size_t j = 0; j < clusters[i].size(); j++) { basis[i] += clusters[i][j]; } - basis[i] = basis[i] / static_cast(clusters[i].size()); - weight = weight_clusters[i] / (2 * clusters[i].size()); - centers.push_back(Vec3d(basis[i].x, basis[i].y, weight)); + point2f_result.push_back( + Point2f(static_cast(result[i][1]), + static_cast(result[i][0] + result[i][2] / 2))); } - - return centers; + return point2f_result; } -void QRDecode::fixationPoints(std::vector &local_point, std::vector &local_len) +void QRDecode::fixationPoints(vector &local_point) { double cos_angles[3], norm_triangl[3]; @@ -289,182 +215,242 @@ void QRDecode::fixationPoints(std::vector &local_point, std::vector(0, 0) = 1; - vector_mult.at(1, 0) = 1; - vector_mult.at(2, 0) = 1; - vector_mult.at(0, 1) = static_cast((local_point[1] - local_point[0]).x); - vector_mult.at(1, 1) = static_cast((local_point[1] - local_point[0]).y); - vector_mult.at(0, 2) = static_cast((local_point[2] - local_point[0]).x); - vector_mult.at(1, 2) = static_cast((local_point[2] - local_point[0]).y); - double res_vect_mult = determinant(vector_mult); - if (res_vect_mult < 0) + / (2 * norm_triangl[0] * norm_triangl[1]); + + size_t i_min_cos = + (cos_angles[0] < cos_angles[1] && cos_angles[0] < cos_angles[2]) ? 0 : + (cos_angles[1] < cos_angles[0] && cos_angles[1] < cos_angles[2]) ? 1 : 2; + + std::swap(local_point[0], local_point[i_min_cos]); + + Point2f rpt = local_point[0], bpt = local_point[1], gpt = local_point[2]; + Matx22f m(rpt.x - bpt.x, rpt.y - bpt.y, gpt.x - rpt.x, gpt.y - rpt.y); + if( determinant(m) > 0 ) { - temp_pnt = local_point[1]; - tmp_len = local_len[1]; - local_point[1] = local_point[2]; - local_len[1] = local_len[2]; - local_point[2] = temp_pnt; - local_len[2] = tmp_len; + std::swap(local_point[1], local_point[2]); } } -bool QRDecode::transformation() +bool QRDecode::localization() { - cvtColor(bin_barcode, transform_barcode, COLOR_GRAY2RGB); + Point2f begin, end; + vector list_lines_x = searchVerticalLines(); + if( list_lines_x.empty() ) { return false; } + vector list_lines_y = separateHorizontalLines(list_lines_x); + if( list_lines_y.empty() ) { return false; } + + vector centers; + Mat labels; + kmeans(list_lines_y, 3, labels, + TermCriteria( TermCriteria::EPS+TermCriteria::COUNT, 10, 1.0), + 3, KMEANS_PP_CENTERS, localization_points); + + fixationPoints(localization_points); if (localization_points.size() != 3) { return false; } - Point red = localization_points[0]; - Point green = localization_points[1]; - Point blue = localization_points[2]; - Point adj_b_r_pnt, adj_r_b_pnt, adj_g_r_pnt, adj_r_g_pnt; - Point line_r_b_pnt, line_r_g_pnt, norm_r_b_pnt, norm_r_g_pnt; - adj_b_r_pnt = getTransformationPoint(blue, red, -1); - adj_r_b_pnt = getTransformationPoint(red, blue, -1); - adj_g_r_pnt = getTransformationPoint(green, red, -1); - adj_r_g_pnt = getTransformationPoint(red, green, -1); - line_r_b_pnt = getTransformationPoint(red, blue, -0.91); - line_r_g_pnt = getTransformationPoint(red, green, -0.91); - norm_r_b_pnt = getTransformationPoint(red, blue, 0.0, true); - norm_r_g_pnt = getTransformationPoint(red, green, 0.0, false); - - transformation_points.push_back(intersectionLines( - adj_r_g_pnt, line_r_g_pnt, adj_r_b_pnt, line_r_b_pnt)); - transformation_points.push_back(intersectionLines( - adj_b_r_pnt, norm_r_g_pnt, adj_r_g_pnt, line_r_g_pnt)); - transformation_points.push_back(intersectionLines( - norm_r_b_pnt, adj_g_r_pnt, adj_b_r_pnt, norm_r_g_pnt)); - transformation_points.push_back(intersectionLines( - norm_r_b_pnt, adj_g_r_pnt, adj_r_b_pnt, line_r_b_pnt)); - - experimental_area = getQuadrilateralArea(transformation_points[0], - transformation_points[1], - transformation_points[2], - transformation_points[3]); - std::vector quadrilateral = getQuadrilateral(transformation_points); - transformation_points = quadrilateral; - - int max_length_norm = -1; - size_t transform_size = transformation_points.size(); - for (size_t i = 0; i < transform_size; i++) + if (coeff_expansion > 1.0) { - int len_norm = static_cast(norm(transformation_points[i % transform_size] - - transformation_points[(i + 1) % transform_size])); - if (max_length_norm < len_norm) { max_length_norm = len_norm; } + int width = static_cast(bin_barcode.size().width / coeff_expansion); + int height = static_cast(bin_barcode.size().height / coeff_expansion); + Size new_size(width, height); + Mat intermediate; + resize(bin_barcode, intermediate, new_size); + bin_barcode = intermediate.clone(); + for (size_t i = 0; i < localization_points.size(); i++) + { + localization_points[i] /= coeff_expansion; + } } - std::vector perspective_points; - perspective_points.push_back(Point(0, 0)); - perspective_points.push_back(Point(0, max_length_norm)); - perspective_points.push_back(Point(max_length_norm, max_length_norm)); - perspective_points.push_back(Point(max_length_norm, 0)); - - // warpPerspective(bin_barcode, straight_barcode, - // findHomography(transformation_points, perspective_points), - // Size(max_length_norm, max_length_norm)); + for (size_t i = 0; i < localization_points.size(); i++) + { + for (size_t j = i + 1; j < localization_points.size(); j++) + { + if (norm(localization_points[i] - localization_points[j]) < 10) + { + return false; + } + } + } return true; + } -Point QRDecode::getTransformationPoint(Point left, Point center, double cos_angle_rotation, - bool right_rotate) +bool QRDecode::computeTransformationPoints() { - Point temp_pnt, prev_pnt(0, 0), next_pnt, start_pnt(center); - double temp_delta, min_delta; - int steps = 0; + if (localization_points.size() != 3) { return false; } - future_pixel = 255; - while(true) + vector locations, non_zero_elem[3], newHull; + vector new_non_zero_elem[3]; + for (size_t i = 0; i < 3; i++) { - min_delta = std::numeric_limits::max(); - for (int i = -1; i < 2; i++) + Mat mask = Mat::zeros(bin_barcode.rows + 2, bin_barcode.cols + 2, CV_8UC1); + uint8_t next_pixel, future_pixel = 255; + int count_test_lines = 0, index = static_cast(localization_points[i].x); + for (; index < bin_barcode.cols - 1; index++) { - for (int j = -1; j < 2; j++) + next_pixel = bin_barcode.at( + static_cast(localization_points[i].y), index + 1); + if (next_pixel == future_pixel) { - if (i == 0 && j == 0) { continue; } - temp_pnt = Point(start_pnt.x + i, start_pnt.y + j); - temp_delta = abs(getCosVectors(left, center, temp_pnt) - cos_angle_rotation); - if (temp_delta < min_delta && prev_pnt != temp_pnt) + future_pixel = 255 - future_pixel; + count_test_lines++; + if (count_test_lines == 2) { - next_pnt = temp_pnt; - min_delta = temp_delta; + floodFill(bin_barcode, mask, + Point(index + 1, static_cast(localization_points[i].y)), 255, + 0, Scalar(), Scalar(), FLOODFILL_MASK_ONLY); + break; } } } - prev_pnt = start_pnt; - start_pnt = next_pnt; - next_pixel = bin_barcode.at(start_pnt.y, start_pnt.x); - if (next_pixel == future_pixel) + Mat mask_roi = mask(Range(1, bin_barcode.rows - 1), Range(1, bin_barcode.cols - 1)); + findNonZero(mask_roi, non_zero_elem[i]); + newHull.insert(newHull.end(), non_zero_elem[i].begin(), non_zero_elem[i].end()); + } + convexHull(Mat(newHull), locations); + for (size_t i = 0; i < locations.size(); i++) + { + for (size_t j = 0; j < 3; j++) { - future_pixel = 255 - future_pixel; - steps++; - if (steps == 3) { break; } + for (size_t k = 0; k < non_zero_elem[j].size(); k++) + { + if (locations[i] == non_zero_elem[j][k]) + { + new_non_zero_elem[j].push_back(locations[i]); + } + } } } - if (cos_angle_rotation == 0.0) + double pentagon_diag_norm = -1; + Point2f down_left_edge_point, up_right_edge_point, up_left_edge_point; + for (size_t i = 0; i < new_non_zero_elem[1].size(); i++) { - Mat vector_mult(Size(3, 3), CV_32FC1); - vector_mult.at(0, 0) = 1; - vector_mult.at(1, 0) = 1; - vector_mult.at(2, 0) = 1; - vector_mult.at(0, 1) = static_cast((left - center).x); - vector_mult.at(1, 1) = static_cast((left - center).y); - vector_mult.at(0, 2) = static_cast((left - start_pnt).x); - vector_mult.at(1, 2) = static_cast((left - start_pnt).y); - double res_vect_mult = determinant(vector_mult); - if (( right_rotate && res_vect_mult < 0) || - (!right_rotate && res_vect_mult > 0)) + for (size_t j = 0; j < new_non_zero_elem[2].size(); j++) { - start_pnt = getTransformationPoint(start_pnt, center, -1); + double temp_norm = norm(new_non_zero_elem[1][i] - new_non_zero_elem[2][j]); + if (temp_norm > pentagon_diag_norm) + { + down_left_edge_point = new_non_zero_elem[1][i]; + up_right_edge_point = new_non_zero_elem[2][j]; + pentagon_diag_norm = temp_norm; + } } } + if (down_left_edge_point == Point2f(0, 0) || + up_right_edge_point == Point2f(0, 0)) { return false; } - return start_pnt; + double max_area = -1; + up_left_edge_point = new_non_zero_elem[0][0]; + for (size_t i = 0; i < new_non_zero_elem[0].size(); i++) + { + double temp_area = getTriangleArea(new_non_zero_elem[0][i], + down_left_edge_point, + up_right_edge_point); + if (max_area < temp_area) + { + up_left_edge_point = new_non_zero_elem[0][i]; + max_area = temp_area; + } + } + + Point2f down_max_delta_point, up_max_delta_point; + double norm_down_max_delta = -1, norm_up_max_delta = -1; + for (size_t i = 0; i < new_non_zero_elem[1].size(); i++) + { + double temp_norm_delta = norm(up_left_edge_point - new_non_zero_elem[1][i]) + + norm(down_left_edge_point - new_non_zero_elem[1][i]); + if (norm_down_max_delta < temp_norm_delta) + { + down_max_delta_point = new_non_zero_elem[1][i]; + norm_down_max_delta = temp_norm_delta; + } + } + + for (size_t i = 0; i < new_non_zero_elem[2].size(); i++) + { + double temp_norm_delta = norm(up_left_edge_point - new_non_zero_elem[2][i]) + + norm(up_right_edge_point - new_non_zero_elem[2][i]); + if (norm_up_max_delta < temp_norm_delta) + { + up_max_delta_point = new_non_zero_elem[2][i]; + norm_up_max_delta = temp_norm_delta; + } + } + + transformation_points.push_back(down_left_edge_point); + transformation_points.push_back(up_left_edge_point); + transformation_points.push_back(up_right_edge_point); + transformation_points.push_back( + intersectionLines(down_left_edge_point, down_max_delta_point, + up_right_edge_point, up_max_delta_point)); + + + + experimental_area = getQuadrilateralArea(transformation_points[0], + transformation_points[1], + transformation_points[2], + transformation_points[3]); + + vector quadrilateral = getQuadrilateral(transformation_points); + transformation_points = quadrilateral; + + return true; } -Point QRDecode::intersectionLines(Point a1, Point a2, Point b1, Point b2) +Point2f QRDecode::intersectionLines(Point2f a1, Point2f a2, Point2f b1, Point2f b2) { - Point result_square_angle( - static_cast( - static_cast - ((a1.x * a2.y - a1.y * a2.x) * (b1.x - b2.x) - - (b1.x * b2.y - b1.y * b2.x) * (a1.x - a2.x)) / - ((a1.x - a2.x) * (b1.y - b2.y) - - (a1.y - a2.y) * (b1.x - b2.x))), - static_cast( - static_cast - ((a1.x * a2.y - a1.y * a2.x) * (b1.y - b2.y) - - (b1.x * b2.y - b1.y * b2.x) * (a1.y - a2.y)) / - ((a1.x - a2.x) * (b1.y - b2.y) - - (a1.y - a2.y) * (b1.x - b2.x))) - ); + Point2f result_square_angle( + ((a1.x * a2.y - a1.y * a2.x) * (b1.x - b2.x) - + (b1.x * b2.y - b1.y * b2.x) * (a1.x - a2.x)) / + ((a1.x - a2.x) * (b1.y - b2.y) - + (a1.y - a2.y) * (b1.x - b2.x)), + ((a1.x * a2.y - a1.y * a2.x) * (b1.y - b2.y) - + (b1.x * b2.y - b1.y * b2.x) * (a1.y - a2.y)) / + ((a1.x - a2.x) * (b1.y - b2.y) - + (a1.y - a2.y) * (b1.x - b2.x)) + ); return result_square_angle; } -std::vector QRDecode::getQuadrilateral(std::vector angle_list) +// test function (if true then ------> else <------ ) +bool QRDecode::testBypassRoute(vector hull, int start, int finish) +{ + int index_hull = start, next_index_hull, hull_size = (int)hull.size(); + double test_length[2] = { 0.0, 0.0 }; + do + { + next_index_hull = index_hull + 1; + if (next_index_hull == hull_size) { next_index_hull = 0; } + test_length[0] += norm(hull[index_hull] - hull[next_index_hull]); + index_hull = next_index_hull; + } + while(index_hull != finish); + + index_hull = start; + do + { + next_index_hull = index_hull - 1; + if (next_index_hull == -1) { next_index_hull = hull_size - 1; } + test_length[1] += norm(hull[index_hull] - hull[next_index_hull]); + index_hull = next_index_hull; + } + while(index_hull != finish); + + if (test_length[0] < test_length[1]) { return true; } else { return false; } +} + +vector QRDecode::getQuadrilateral(vector angle_list) { size_t angle_size = angle_list.size(); uint8_t value, mask_value; - Mat mask(bin_barcode.rows + 2, bin_barcode.cols + 2, CV_8UC1); + Mat mask = Mat::zeros(bin_barcode.rows + 2, bin_barcode.cols + 2, CV_8UC1); + Mat fill_bin_barcode = bin_barcode.clone(); for (size_t i = 0; i < angle_size; i++) { LineIterator line_iter(bin_barcode, angle_list[ i % angle_size], @@ -475,119 +461,91 @@ std::vector QRDecode::getQuadrilateral(std::vector angle_list) mask_value = mask.at(line_iter.pos() + Point(1, 1)); if (value == 0 && mask_value == 0) { - floodFill(bin_barcode, mask, line_iter.pos(), 255); + floodFill(fill_bin_barcode, mask, line_iter.pos(), 255, + 0, Scalar(), Scalar(), FLOODFILL_MASK_ONLY); } } } - std::vector locations; - Mat mask_roi = mask(Range(1, bin_barcode.rows - 1), - Range(1, bin_barcode.cols - 1)); + vector locations; + Mat mask_roi = mask(Range(1, bin_barcode.rows - 1), Range(1, bin_barcode.cols - 1)); cv::findNonZero(mask_roi, locations); for (size_t i = 0; i < angle_list.size(); i++) { - locations.push_back(angle_list[i]); + int x = static_cast(angle_list[i].x); + int y = static_cast(angle_list[i].y); + locations.push_back(Point(x, y)); } - std::vector< std::vector > hull(1), approx_hull(1); - convexHull(Mat(locations), hull[0]); - int hull_size = static_cast(hull[0].size()); - - Point min_pnt; - - std::vector min_abc; - double min_abs_cos_abc, abs_cos_abc; - for (int count = 0; count < 4; count++) - { - min_abs_cos_abc = std::numeric_limits::max(); - for (int i = 0; i < hull_size; i++) - { - Point a = hull[0][ i % hull_size]; - Point b = hull[0][(i + 1) % hull_size]; - Point c = hull[0][(i + 2) % hull_size]; - abs_cos_abc = abs(getCosVectors(a, b, c)); - - bool flag_detect = true; - for (size_t j = 0; j < min_abc.size(); j++) - { - if (min_abc[j] == b) { flag_detect = false; break; } - } - - if (flag_detect && (abs_cos_abc < min_abs_cos_abc)) - { - min_pnt = b; - min_abs_cos_abc = abs_cos_abc; - } - } - min_abc.push_back(min_pnt); - } - - - int min_abc_size = static_cast(min_abc.size()); - std::vector index_min_abc(min_abc_size); - for (int i = 0; i < min_abc_size; i++) + vector integer_hull; + convexHull(Mat(locations), integer_hull); + int hull_size = (int)integer_hull.size(); + vector hull(hull_size); + for (int i = 0; i < hull_size; i++) { - for (int j = 0; j < hull_size; j++) - { - if (hull[0][j] == min_abc[i]) { index_min_abc[i] = j; break; } - } + float x = static_cast(integer_hull[i].x); + float y = static_cast(integer_hull[i].y); + hull[i] = Point2f(x, y); } - std::vector result_hull_point(angle_size); - double min_norm, temp_norm; + vector result_hull_point(angle_size); + double min_norm; for (size_t i = 0; i < angle_size; i++) { min_norm = std::numeric_limits::max(); Point closest_pnt; - for (int j = 0; j < min_abc_size; j++) + for (int j = 0; j < hull_size; j++) { - if (min_norm > norm(hull[0][index_min_abc[j]] - angle_list[i])) + double temp_norm = norm(hull[j] - angle_list[i]); + if (min_norm > temp_norm) { - min_norm = norm(hull[0][index_min_abc[j]] - angle_list[i]); - closest_pnt = hull[0][index_min_abc[j]]; + min_norm = temp_norm; + closest_pnt = hull[j]; } } result_hull_point[i] = closest_pnt; } - int start_line[2] = {0, 0}, finish_line[2] = {0, 0}, unstable_pnt = 0; + int start_line[2] = { 0, 0 }, finish_line[2] = { 0, 0 }, unstable_pnt = 0; for (int i = 0; i < hull_size; i++) { - if (result_hull_point[3] == hull[0][i]) { start_line[0] = i; } - if (result_hull_point[2] == hull[0][i]) { finish_line[0] = start_line[1] = i; } - if (result_hull_point[1] == hull[0][i]) { finish_line[1] = i; } - if (result_hull_point[0] == hull[0][i]) { unstable_pnt = i; } + if (result_hull_point[2] == hull[i]) { start_line[0] = i; } + if (result_hull_point[1] == hull[i]) { finish_line[0] = start_line[1] = i; } + if (result_hull_point[0] == hull[i]) { finish_line[1] = i; } + if (result_hull_point[3] == hull[i]) { unstable_pnt = i; } } - int index_hull, extra_index_hull, next_index_hull, extra_next_index_hull, count_points; + int index_hull, extra_index_hull, next_index_hull, extra_next_index_hull; Point result_side_begin[4], result_side_end[4]; + bool bypass_orientation = testBypassRoute(hull, start_line[0], finish_line[0]); + bool extra_bypass_orientation; + min_norm = std::numeric_limits::max(); index_hull = start_line[0]; - count_points = abs(start_line[0] - finish_line[0]); do { - if (count_points > hull_size / 2) { next_index_hull = index_hull + 1; } + if (bypass_orientation) { next_index_hull = index_hull + 1; } else { next_index_hull = index_hull - 1; } if (next_index_hull == hull_size) { next_index_hull = 0; } if (next_index_hull == -1) { next_index_hull = hull_size - 1; } - Point angle_closest_pnt = norm(hull[0][index_hull] - angle_list[2]) > - norm(hull[0][index_hull] - angle_list[3]) ? angle_list[3] : angle_list[2]; + Point angle_closest_pnt = norm(hull[index_hull] - angle_list[1]) > + norm(hull[index_hull] - angle_list[2]) ? angle_list[2] : angle_list[1]; Point intrsc_line_hull = - intersectionLines(hull[0][index_hull], hull[0][next_index_hull], - angle_list[2], angle_list[3]); - temp_norm = getCosVectors(hull[0][index_hull], intrsc_line_hull, angle_closest_pnt); + intersectionLines(hull[index_hull], hull[next_index_hull], + angle_list[1], angle_list[2]); + double temp_norm = getCosVectors(hull[index_hull], intrsc_line_hull, angle_closest_pnt); if (min_norm > temp_norm && - norm(hull[0][index_hull] - hull[0][next_index_hull]) > - norm(angle_list[2] - angle_list[3]) / 10) + norm(hull[index_hull] - hull[next_index_hull]) > + norm(angle_list[1] - angle_list[2]) / 10) { min_norm = temp_norm; - result_side_begin[0] = hull[0][index_hull]; - result_side_end[0] = hull[0][next_index_hull]; + result_side_begin[0] = hull[index_hull]; + result_side_end[0] = hull[next_index_hull]; } @@ -597,71 +555,56 @@ std::vector QRDecode::getQuadrilateral(std::vector angle_list) if (min_norm == std::numeric_limits::max()) { - result_side_begin[0] = angle_list[2]; - result_side_end[0] = angle_list[3]; + result_side_begin[0] = angle_list[1]; + result_side_end[0] = angle_list[2]; } min_norm = std::numeric_limits::max(); index_hull = start_line[1]; - count_points = abs(start_line[1] - finish_line[1]); + bypass_orientation = testBypassRoute(hull, start_line[1], finish_line[1]); do { - if (count_points > hull_size / 2) { next_index_hull = index_hull + 1; } + if (bypass_orientation) { next_index_hull = index_hull + 1; } else { next_index_hull = index_hull - 1; } if (next_index_hull == hull_size) { next_index_hull = 0; } if (next_index_hull == -1) { next_index_hull = hull_size - 1; } - Point angle_closest_pnt = norm(hull[0][index_hull] - angle_list[1]) > - norm(hull[0][index_hull] - angle_list[2]) ? angle_list[2] : angle_list[1]; + Point angle_closest_pnt = norm(hull[index_hull] - angle_list[0]) > + norm(hull[index_hull] - angle_list[1]) ? angle_list[1] : angle_list[0]; Point intrsc_line_hull = - intersectionLines(hull[0][index_hull], hull[0][next_index_hull], - angle_list[1], angle_list[2]); - temp_norm = getCosVectors(hull[0][index_hull], intrsc_line_hull, angle_closest_pnt); + intersectionLines(hull[index_hull], hull[next_index_hull], + angle_list[0], angle_list[1]); + double temp_norm = getCosVectors(hull[index_hull], intrsc_line_hull, angle_closest_pnt); if (min_norm > temp_norm && - norm(hull[0][index_hull] - hull[0][next_index_hull]) > - norm(angle_list[1] - angle_list[2]) / 20) + norm(hull[index_hull] - hull[next_index_hull]) > + norm(angle_list[0] - angle_list[1]) / 20) { min_norm = temp_norm; - result_side_begin[1] = hull[0][index_hull]; - result_side_end[1] = hull[0][next_index_hull]; + result_side_begin[1] = hull[index_hull]; + result_side_end[1] = hull[next_index_hull]; } - index_hull = next_index_hull; } while(index_hull != finish_line[1]); if (min_norm == std::numeric_limits::max()) { - result_side_begin[1] = angle_list[1]; - result_side_end[1] = angle_list[2]; + result_side_begin[1] = angle_list[0]; + result_side_end[1] = angle_list[1]; } - double test_norm[4] = { 0.0, 0.0, 0.0, 0.0 }; - int test_index[4]; - for (int i = 0; i < 4; i++) - { - test_index[i] = (i < 2) ? static_cast(start_line[0]) - : static_cast(finish_line[1]); - do - { - next_index_hull = ((i + 1) % 2 != 0) ? test_index[i] + 1 : test_index[i] - 1; - if (next_index_hull == hull_size) { next_index_hull = 0; } - if (next_index_hull == -1) { next_index_hull = hull_size - 1; } - test_norm[i] += norm(hull[0][next_index_hull] - hull[0][unstable_pnt]); - test_index[i] = next_index_hull; - } - while(test_index[i] != unstable_pnt); - } + bypass_orientation = testBypassRoute(hull, start_line[0], unstable_pnt); + extra_bypass_orientation = testBypassRoute(hull, finish_line[1], unstable_pnt); - std::vector result_angle_list(4), test_result_angle_list(4); + vector result_angle_list(4), test_result_angle_list(4); double min_area = std::numeric_limits::max(), test_area; index_hull = start_line[0]; do { - if (test_norm[0] < test_norm[1]) { next_index_hull = index_hull + 1; } + if (bypass_orientation) { next_index_hull = index_hull + 1; } else { next_index_hull = index_hull - 1; } if (next_index_hull == hull_size) { next_index_hull = 0; } @@ -670,24 +613,25 @@ std::vector QRDecode::getQuadrilateral(std::vector angle_list) extra_index_hull = finish_line[1]; do { - if (test_norm[2] < test_norm[3]) { extra_next_index_hull = extra_index_hull + 1; } + if (extra_bypass_orientation) { extra_next_index_hull = extra_index_hull + 1; } else { extra_next_index_hull = extra_index_hull - 1; } if (extra_next_index_hull == hull_size) { extra_next_index_hull = 0; } if (extra_next_index_hull == -1) { extra_next_index_hull = hull_size - 1; } test_result_angle_list[0] - = intersectionLines(result_side_begin[0], result_side_end[0], - result_side_begin[1], result_side_end[1]); + = intersectionLines(result_side_begin[0], result_side_end[0], + result_side_begin[1], result_side_end[1]); test_result_angle_list[1] - = intersectionLines(result_side_begin[1], result_side_end[1], - hull[0][extra_index_hull], hull[0][extra_next_index_hull]); + = intersectionLines(result_side_begin[1], result_side_end[1], + hull[extra_index_hull], hull[extra_next_index_hull]); test_result_angle_list[2] - = intersectionLines(hull[0][extra_index_hull], hull[0][extra_next_index_hull], - hull[0][index_hull], hull[0][next_index_hull]); + = intersectionLines(hull[extra_index_hull], hull[extra_next_index_hull], + hull[index_hull], hull[next_index_hull]); test_result_angle_list[3] - = intersectionLines(hull[0][index_hull], hull[0][next_index_hull], - result_side_begin[0], result_side_end[0]); + = intersectionLines(hull[index_hull], hull[next_index_hull], + result_side_begin[0], result_side_end[0]); + test_area = getQuadrilateralArea(test_result_angle_list[0], test_result_angle_list[1], test_result_angle_list[2], @@ -721,8 +665,6 @@ std::vector QRDecode::getQuadrilateral(std::vector angle_list) if (norm(result_angle_list[3] - angle_list[3]) > norm(angle_list[3] - angle_list[2]) / 3) { result_angle_list[3] = angle_list[3]; } - - return result_angle_list; } @@ -733,16 +675,16 @@ std::vector QRDecode::getQuadrilateral(std::vector angle_list) // / | // a --------------- d -double QRDecode::getQuadrilateralArea(Point a, Point b, Point c, Point d) +double QRDecode::getQuadrilateralArea(Point2f a, Point2f b, Point2f c, Point2f d) { double length_sides[4], perimeter = 0.0, result_area = 1.0; length_sides[0] = norm(a - b); length_sides[1] = norm(b - c); length_sides[2] = norm(c - d); length_sides[3] = norm(d - a); - for (int i = 0; i < 4; i++) { perimeter += length_sides[i]; } + for (size_t i = 0; i < 4; i++) { perimeter += length_sides[i]; } perimeter /= 2; - for (int i = 0; i < 4; i++) + for (size_t i = 0; i < 4; i++) { result_area *= (perimeter - length_sides[i]); } @@ -752,27 +694,116 @@ double QRDecode::getQuadrilateralArea(Point a, Point b, Point c, Point d) return result_area; } +// b +// / | +// / | +// / | +// / S | +// / | +// a ----- c + +double QRDecode::getTriangleArea(Point2f a, Point2f b, Point2f c) +{ + double length_sides[3], perimeter = 0.0, triangle_area = 1.0; + length_sides[0] = norm(a - b); + length_sides[1] = norm(b - c); + length_sides[2] = norm(c - a); + for (size_t i = 0; i < 3; i++) { perimeter += length_sides[i]; } + perimeter /= 2; + for (size_t i = 0; i < 3; i++) + { + triangle_area *= (perimeter - length_sides[i]); + } + triangle_area += sqrt(triangle_area); + + return triangle_area; +} + // / | b // / | // / | // a/ | c -double QRDecode::getCosVectors(Point a, Point b, Point c) +double QRDecode::getCosVectors(Point2f a, Point2f b, Point2f c) { - return ((a - b).x * (c - b).x + (a - b).y * (c - b).y) / (norm(a - b) * norm(c - b)); + return ((a - b).x * (c - b).x + (a - b).y * (c - b).y) + / (norm(a - b) * norm(c - b)); } -CV_EXPORTS bool detectQRCode(InputArray in, std::vector &points, double eps_x, double eps_y) +bool QRDecode::transformation() { - CV_Assert(in.isMat()); - CV_Assert(in.getMat().type() == CV_8UC1); + if(!computeTransformationPoints()) { return false; } + + double max_length_norm = -1; + size_t transform_size = transformation_points.size(); + for (size_t i = 0; i < transform_size; i++) + { + double len_norm = norm(transformation_points[i % transform_size] - + transformation_points[(i + 1) % transform_size]); + max_length_norm = std::max(max_length_norm, len_norm); + } + + Point2f transformation_points_[] = + { + transformation_points[0], + transformation_points[1], + transformation_points[2], + transformation_points[3] + }; + + Point2f perspective_points[] = + { + Point2f(0.f, 0.f), Point2f(0.f, (float)max_length_norm), + Point2f((float)max_length_norm, (float)max_length_norm), + Point2f((float)max_length_norm, 0.f) + }; + + Mat H = getPerspectiveTransform(transformation_points_, perspective_points); + + warpPerspective(bin_barcode, straight_barcode, H, + Size(static_cast(max_length_norm), + static_cast(max_length_norm))); + return true; +} + + +struct QRCodeDetector::Impl +{ +public: + Impl() { epsX = 0.2; epsY = 0.1; } + ~Impl() {} + + double epsX, epsY; +}; + +QRCodeDetector::QRCodeDetector() : p(new Impl) {} +QRCodeDetector::~QRCodeDetector() {} + +void QRCodeDetector::setEpsX(double epsX) { p->epsX = epsX; } +void QRCodeDetector::setEpsY(double epsY) { p->epsY = epsY; } + +bool QRCodeDetector::detect(InputArray in, OutputArray points) const +{ + Mat inarr = in.getMat(); + CV_Assert(!inarr.empty()); + CV_Assert(inarr.type() == CV_8UC1); QRDecode qrdec; - qrdec.init(in.getMat(), eps_x, eps_y); + qrdec.init(inarr, p->epsX, p->epsY); qrdec.binarization(); if (!qrdec.localization()) { return false; } if (!qrdec.transformation()) { return false; } - points = qrdec.getTransformationPoints(); + vector pnts2f = qrdec.getTransformationPoints(); + Mat(pnts2f).convertTo(points, points.fixedType() ? points.type() : CV_32FC2); return true; } +CV_EXPORTS bool detectQRCode(InputArray in, std::vector &points, double eps_x, double eps_y) +{ + QRCodeDetector qrdetector; + qrdetector.setEpsX(eps_x); + qrdetector.setEpsY(eps_y); + + return qrdetector.detect(in, points); +} + } diff --git a/modules/objdetect/test/test_qrcode.cpp b/modules/objdetect/test/test_qrcode.cpp index 87f5ce5..82e9990 100644 --- a/modules/objdetect/test/test_qrcode.cpp +++ b/modules/objdetect/test/test_qrcode.cpp @@ -1,74 +1,115 @@ -/*M/////////////////////////////////////////////////////////////////////////////////////// -// -// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. -// -// By downloading, copying, installing or using the software you agree to this license. -// If you do not agree to this license, do not download, install, -// copy or use the software. -// -// -// Intel License Agreement -// For Open Source Computer Vision Library -// -// Copyright (C) 2000, Intel Corporation, all rights reserved. -// Third party copyrights are property of their respective owners. -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// -// * Redistribution's of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * Redistribution's in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// * The name of Intel Corporation may not be used to endorse or promote products -// derived from this software without specific prior written permission. -// -// This software is provided by the copyright holders and contributors "as is" and -// any express or implied warranties, including, but not limited to, the implied -// warranties of merchantability and fitness for a particular purpose are disclaimed. -// In no event shall the Intel Corporation or contributors be liable for any direct, -// indirect, incidental, special, exemplary, or consequential damages -// (including, but not limited to, procurement of substitute goods or services; -// loss of use, data, or profits; or business interruption) however caused -// and on any theory of liability, whether in contract, strict liability, -// or tort (including negligence or otherwise) arising in any way out of -// the use of this software, even if advised of the possibility of such damage. -// -//M*/ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. #include "test_precomp.hpp" -namespace opencv_test { namespace { -TEST(Objdetect_QRCode, regression) +namespace opencv_test +{ + +String qrcode_images_name[] = { + "20110817_030.jpg", + "20110817_048.jpg", + "img_20120226_161648.jpg", + "img_2714.jpg", + "img_2716.jpg", + "img_3011.jpg", + "img_3029.jpg", + "img_3070.jpg", + "qr_test_030.jpg" +}; + +// #define UPDATE_QRCODE_TEST_DATA +#ifdef UPDATE_QRCODE_TEST_DATA + +TEST(Objdetect_QRCode, generate_test_data) { String root = cvtest::TS::ptr()->get_data_path() + "qrcode/"; - // String cascades[] = - // { - // root + "haarcascade_frontalface_alt.xml", - // root + "lbpcascade_frontalface.xml", - // String() - // }; - - // vector objects; - // RNG rng((uint64)-1); - - // for( int i = 0; !cascades[i].empty(); i++ ) - // { - // printf("%d. %s\n", i, cascades[i].c_str()); - // CascadeClassifier cascade(cascades[i]); - // for( int j = 0; j < 100; j++ ) - // { - // int width = rng.uniform(1, 100); - // int height = rng.uniform(1, 100); - // Mat img(height, width, CV_8U); - // randu(img, 0, 256); - // cascade.detectMultiScale(img, objects); - // } - // } + String dataset_config = cvtest::TS::ptr()->get_data_path() + "qrcode/dataset_config.json"; + FileStorage file_config(dataset_config, FileStorage::WRITE); + + file_config << "test_images" << "["; + size_t images_count = sizeof(qrcode_images_name) / sizeof(String); + for (size_t i = 0; i < images_count; i++) + { + file_config << "{:" << "image_name" << qrcode_images_name[i]; + String image_path = root + qrcode_images_name[i]; + std::vector transform; + Mat src = imread(image_path, IMREAD_GRAYSCALE); + EXPECT_TRUE(detectQRCode(src, transform)); + file_config << "x" << "[:"; + for (size_t j = 0; j < transform.size(); j++) { file_config << transform[j].x; } + file_config << "]"; + file_config << "y" << "[:"; + for (size_t j = 0; j < transform.size(); j++) { file_config << transform[j].y; } + file_config << "]" << "}"; + } + file_config << "]"; + file_config.release(); } -}} // namespace +#else + +typedef testing::TestWithParam< String > Objdetect_QRCode; +TEST_P(Objdetect_QRCode, regression) +{ + String root = cvtest::TS::ptr()->get_data_path() + "qrcode/"; + String dataset_config = cvtest::TS::ptr()->get_data_path() + "qrcode/dataset_config.json"; + FileStorage file_config(dataset_config, FileStorage::READ); + const int pixels_error = 3; + + std::vector corners; + String image_path = root + String(GetParam()); + Mat src = imread(image_path, IMREAD_GRAYSCALE); + EXPECT_TRUE(detectQRCode(src, corners)); + + if (file_config.isOpened()) + { + FileNode images_list = file_config["test_images"]; + int index = 0, images_count = static_cast(images_list.size()); + ASSERT_GT(images_count, 0); + + bool runTestsFlag = false; + String name_current_image = String(GetParam()); + for (; index < images_count; index++) + { + String name_test_image = images_list[index]["image_name"]; + if (name_test_image == name_current_image) + { + for (int i = 0; i < 4; i++) + { + int x = images_list[index]["x"][i]; + int y = images_list[index]["y"][i]; + EXPECT_NEAR(x, corners[i].x, pixels_error); + EXPECT_NEAR(y, corners[i].y, pixels_error); + } + runTestsFlag = true; + } + } + if (!runTestsFlag) + { + std::cout << "Not found results for " << name_current_image; + std::cout << " image in dataset_config.json file." << std::endl; + } + + file_config.release(); + } + else + { + std::cout << " Not found dataset_config.json file." << std::endl; + } +} + +INSTANTIATE_TEST_CASE_P(objdetect, Objdetect_QRCode, testing::ValuesIn(qrcode_images_name)); + +TEST(Objdetect_QRCode, not_found_qrcode) +{ + std::vector corners; + Mat zero_image = Mat::zeros(256, 256, CV_8UC1); + EXPECT_FALSE(detectQRCode(zero_image, corners)); +} + +#endif + +} // namespace -- 2.7.4