|
| 1 | +#include "./include/DocumentDetector.h" |
| 2 | + |
| 3 | +using namespace detector; |
| 4 | +using namespace cv; |
| 5 | +using namespace std; |
| 6 | + |
| 7 | +typedef std::pair<std::vector<cv::Point>, double> PointAndArea; |
| 8 | +bool sortByArea(PointAndArea contour1, PointAndArea contour2) |
| 9 | +{ |
| 10 | + return (contour1.second > contour2.second); |
| 11 | +} |
| 12 | + |
| 13 | +DocumentDetector::DocumentDetector(cv::Mat &bitmap, int resizeThreshold, int imageRotation) { |
| 14 | + image = bitmap; |
| 15 | + DocumentDetector::resizeThreshold = resizeThreshold; |
| 16 | + DocumentDetector::imageRotation = imageRotation; |
| 17 | +} |
| 18 | + |
| 19 | +DocumentDetector::DocumentDetector(int resizeThreshold, int imageRotation) { |
| 20 | + DocumentDetector::resizeThreshold = resizeThreshold; |
| 21 | + DocumentDetector::imageRotation = imageRotation; |
| 22 | +} |
| 23 | + |
| 24 | +DocumentDetector::~DocumentDetector() { |
| 25 | +} |
| 26 | + |
| 27 | + |
| 28 | +double angle(cv::Point pt1, cv::Point pt2, cv::Point pt0) { |
| 29 | + double dx1 = pt1.x - pt0.x; |
| 30 | + double dy1 = pt1.y - pt0.y; |
| 31 | + double dx2 = pt2.x - pt0.x; |
| 32 | + double dy2 = pt2.y - pt0.y; |
| 33 | + return (dx1 * dx2 + dy1 * dy2) / |
| 34 | + sqrt((dx1 * dx1 + dy1 * dy1) * (dx2 * dx2 + dy2 * dy2) + 1e-10); |
| 35 | +} |
| 36 | + |
| 37 | +void DocumentDetector::findSquares(cv::Mat srcGray, double scaledWidth, double scaledHeight, |
| 38 | + std::vector<std::pair<std::vector<cv::Point>, double>> &squares) { |
| 39 | + // Contours search |
| 40 | + std::vector<std::vector<cv::Point>> contours; |
| 41 | + vector<Vec4i> hierarchy; |
| 42 | + cv::findContours(srcGray, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE); |
| 43 | + |
| 44 | + std::vector<Point> approx; |
| 45 | + for (size_t i = 0; i < contours.size(); i++) { |
| 46 | + std::vector<Point> contour = contours[i]; |
| 47 | + |
| 48 | + // Detection of geometric shapes |
| 49 | + double epsilon = cv::arcLength(contour, true) * contoursApproxEpsilonFactor; |
| 50 | + cv::approxPolyDP(contour, approx, epsilon, true); |
| 51 | + |
| 52 | + // Detection of quadrilaterals among geometric shapes |
| 53 | + if (approx.size() >= 4 && cv::isContourConvex(approx)) { |
| 54 | + const double area = std::abs(contourArea(approx)); |
| 55 | + if (area > scaledWidth / areaScaleMinFactor * (scaledHeight / areaScaleMinFactor)) { |
| 56 | + double maxCosine = 0.0; |
| 57 | + for (int j = 2; j < 5; j++) { |
| 58 | + double cosine = std::abs(angle(approx[j % 4], approx[j - 2], approx[j - 1])); |
| 59 | + maxCosine = std::max(maxCosine, cosine); |
| 60 | + } |
| 61 | + // Selection of quadrilaterals with large enough angles |
| 62 | + if (maxCosine < 0.5) { |
| 63 | + squares.push_back(std::pair<std::vector<cv::Point>, double>(approx, area)); |
| 64 | + } |
| 65 | + } |
| 66 | + } |
| 67 | + } |
| 68 | +} |
| 69 | + |
| 70 | +vector<vector<cv::Point>> DocumentDetector::scanPoint() { |
| 71 | + Mat edged; |
| 72 | + vector<vector<cv::Point>> result = scanPoint(edged); |
| 73 | + edged.release(); |
| 74 | + return result; |
| 75 | +} |
| 76 | + |
| 77 | +long long DocumentDetector::pointSideLine(Point &lineP1, Point &lineP2, Point &point) { |
| 78 | + long x1 = lineP1.x; |
| 79 | + long y1 = lineP1.y; |
| 80 | + long x2 = lineP2.x; |
| 81 | + long y2 = lineP2.y; |
| 82 | + long x = point.x; |
| 83 | + long y = point.y; |
| 84 | + return (x - x1)*(y2 - y1) - (y - y1)*(x2 - x1); |
| 85 | +} |
| 86 | +vector<cv::Point> DocumentDetector::sortPointClockwise(vector<cv::Point> points) { |
| 87 | + if (points.size() != 4) { |
| 88 | + return points; |
| 89 | + } |
| 90 | + |
| 91 | + Point unFoundPoint; |
| 92 | + vector<Point> result = {unFoundPoint, unFoundPoint, unFoundPoint, unFoundPoint}; |
| 93 | + |
| 94 | + long minDistance = -1; |
| 95 | + for(Point &point : points) { |
| 96 | + long distance = point.x * point.x + point.y * point.y; |
| 97 | + if(minDistance == -1 || distance < minDistance) { |
| 98 | + result[0] = point; |
| 99 | + minDistance = distance; |
| 100 | + } |
| 101 | + } |
| 102 | + if (result[0] != unFoundPoint) { |
| 103 | + Point &leftTop = result[0]; |
| 104 | + points.erase(std::remove(points.begin(), points.end(), leftTop)); |
| 105 | + if ((pointSideLine(leftTop, points[0], points[1]) * pointSideLine(leftTop, points[0], points[2])) < 0) { |
| 106 | + result[2] = points[0]; |
| 107 | + } else if ((pointSideLine(leftTop, points[1], points[0]) * pointSideLine(leftTop, points[1], points[2])) < 0) { |
| 108 | + result[2] = points[1]; |
| 109 | + } else if ((pointSideLine(leftTop, points[2], points[0]) * pointSideLine(leftTop, points[2], points[1])) < 0) { |
| 110 | + result[2] = points[2]; |
| 111 | + } |
| 112 | + } |
| 113 | + if (result[0] != unFoundPoint && result[2] != unFoundPoint) { |
| 114 | + Point &leftTop = result[0]; |
| 115 | + Point &rightBottom = result[2]; |
| 116 | + points.erase(std::remove(points.begin(), points.end(), rightBottom)); |
| 117 | + if (pointSideLine(leftTop, rightBottom, points[0]) > 0) { |
| 118 | + result[1] = points[0]; |
| 119 | + result[3] = points[1]; |
| 120 | + } else { |
| 121 | + result[1] = points[1]; |
| 122 | + result[3] = points[0]; |
| 123 | + } |
| 124 | + } |
| 125 | + |
| 126 | + if (result[0] != unFoundPoint && result[1] != unFoundPoint && result[2] != unFoundPoint && result[3] != unFoundPoint) { |
| 127 | + return result; |
| 128 | + } |
| 129 | + |
| 130 | + return points; |
| 131 | +} |
| 132 | +vector<vector<cv::Point>> DocumentDetector::scanPoint(Mat &edged) { |
| 133 | + double width; |
| 134 | + double height; |
| 135 | + Mat image = resizeImage(); |
| 136 | + cvtColor(image, image, COLOR_BGR2GRAY); |
| 137 | + // convert photo to LUV colorspace to avoid glares caused by lights |
| 138 | + if (imageRotation != 0) { |
| 139 | + switch (imageRotation) { |
| 140 | + case 90: |
| 141 | + rotate(image, image, ROTATE_90_CLOCKWISE); |
| 142 | + break; |
| 143 | + case 180: |
| 144 | + rotate(image, image, ROTATE_180); |
| 145 | + break; |
| 146 | + default: |
| 147 | + rotate(image, image, ROTATE_90_COUNTERCLOCKWISE); |
| 148 | + break; |
| 149 | + |
| 150 | + } |
| 151 | + } |
| 152 | +// cvtColor(image, image, COLOR_BGR2Luv); |
| 153 | + Size size = image.size(); |
| 154 | + width = size.width; |
| 155 | + height = size.height; |
| 156 | + cv::Mat blurred; |
| 157 | + |
| 158 | + cv::GaussianBlur(image, image, cv::Size(9,9), 0); |
| 159 | + // dilate helps to remove potential holes between edge segments |
| 160 | + Mat kernel = cv::getStructuringElement(MORPH_RECT,cv::Size(9,9)); |
| 161 | + cv::morphologyEx(image, image, MORPH_CLOSE, kernel); |
| 162 | + cv::Canny(image, image, 0, 84); |
| 163 | +// cv::Mat threshOutput = cv::Mat(blurred.size(), CV_8U); |
| 164 | + std::vector<PointAndArea> squares; |
| 165 | + std::vector<PointAndArea> foundSquares; |
| 166 | + std::vector<int> indices; |
| 167 | +// for (int c = 2; c >= 0; c--) { |
| 168 | +// Mat lBlurred[] = {blurred}; |
| 169 | +// Mat lOutput[] = {threshOutput}; |
| 170 | +// int ch[] = {c, 0}; |
| 171 | +// cv::mixChannels(lBlurred, 1, lOutput, 1, ch, 1); |
| 172 | + |
| 173 | + int thresholdLevel = 3; |
| 174 | + |
| 175 | + int t = 60; |
| 176 | + int l =2; |
| 177 | +// for (int l = thresholdLevel-1 ; l >= 0; l--) { |
| 178 | +// for (int l = 0; l < thresholdLevel; l++) { |
| 179 | +// if (l == 0) { |
| 180 | + // t = 60; |
| 181 | + // while (t >= 10) { |
| 182 | + // cv::Canny(threshOutput, edged, t, t * 2); |
| 183 | + // cv::dilate(edged, edged, cv::Mat(), cv::Point(-1, -1), 2); |
| 184 | + // findSquares( |
| 185 | + // edged, |
| 186 | + // width, |
| 187 | + // height, |
| 188 | + // foundSquares); |
| 189 | + // if (foundSquares.size() > 0) { |
| 190 | + // break; |
| 191 | + // } |
| 192 | + // Call findCannySquares here with appropriate parameters |
| 193 | + // t -= 10; |
| 194 | + // } |
| 195 | +// } else { |
| 196 | +// cv::threshold(blurred, edged, (200 - 175 / (l + 2.0)), 256.0, |
| 197 | +// cv::THRESH_BINARY); |
| 198 | + findSquares(image, width, height, foundSquares); |
| 199 | + // Call findThreshSquares here with appropriate parameters |
| 200 | +// } |
| 201 | + |
| 202 | +// if (foundSquares.size() > 0) { |
| 203 | + // stop as soon as find some |
| 204 | +// break; |
| 205 | +// } |
| 206 | +// } |
| 207 | +// } |
| 208 | + |
| 209 | + int marge = static_cast<int>(width * 0.01); |
| 210 | + std::vector<PointAndArea> squaresProba; |
| 211 | + bool probable; |
| 212 | + |
| 213 | + for (size_t i = 0; i < foundSquares.size(); i++) { |
| 214 | + probable = true; |
| 215 | + std::vector<cv::Point> pointsProba = foundSquares[i].first; |
| 216 | + for (const cv::Point &p: pointsProba) { |
| 217 | + if (p.x < marge || p.x >= width - marge || p.y < marge || p.y >= height - marge) { |
| 218 | + probable = false; |
| 219 | + break; |
| 220 | + } |
| 221 | + } |
| 222 | + if (probable) { |
| 223 | + squaresProba.push_back(foundSquares[i]); |
| 224 | + } |
| 225 | + } |
| 226 | + |
| 227 | + int largestContourIndex = 0; |
| 228 | + |
| 229 | + if (!squaresProba.empty()) { |
| 230 | + double largestArea = -1.0; |
| 231 | + for (size_t i = 0; i < squaresProba.size(); i++) { |
| 232 | + double a = squaresProba[i].second; |
| 233 | + if (a > largestArea && a < width * height) { |
| 234 | + largestArea = a; |
| 235 | + largestContourIndex = static_cast<int>(i); |
| 236 | + } |
| 237 | + } |
| 238 | + squares.push_back(squaresProba[largestContourIndex]); |
| 239 | + } |
| 240 | + |
| 241 | + for (size_t id = 0; id < squaresProba.size(); id++) { |
| 242 | + if (static_cast<int>(id) != largestContourIndex) { |
| 243 | + squares.push_back(squaresProba[id]); |
| 244 | + } |
| 245 | + } |
| 246 | + blurred.release(); |
| 247 | + image.release(); |
| 248 | + if (squares.size() > 0) { |
| 249 | + sort(squares.begin(), squares.end(), sortByArea); |
| 250 | + std::vector< std::vector<Point>> result; |
| 251 | + for (int i = 0; i < squares.size(); i++) { |
| 252 | + std::vector<Point> points = squares[i].first; |
| 253 | + for (int j = 0; j < points.size(); j++) { |
| 254 | + points[j] *= resizeScale; |
| 255 | + } |
| 256 | + result.push_back(sortPointClockwise(points)); |
| 257 | + } |
| 258 | + return result; |
| 259 | + |
| 260 | + } |
| 261 | + return vector<vector<Point>>(); |
| 262 | +} |
| 263 | + |
| 264 | +Mat DocumentDetector::resizeImage() { |
| 265 | + int width = image.cols; |
| 266 | + int height = image.rows; |
| 267 | + int minSize = min(width, height); |
| 268 | + if (minSize > resizeThreshold) { |
| 269 | + resizeScale = 1.0f * minSize / resizeThreshold; |
| 270 | + width = static_cast<int>(width / resizeScale); |
| 271 | + height = static_cast<int>(height / resizeScale); |
| 272 | + Size size(width, height); |
| 273 | + Mat resizedBitmap(size, CV_8UC3); |
| 274 | + resize(image, resizedBitmap, size); |
| 275 | + return resizedBitmap; |
| 276 | + } |
| 277 | + // we clone to ensure we can clean things correctly |
| 278 | + return image.clone(); |
| 279 | +} |
0 commit comments