diff --git a/Image/BillDetection.h b/Image/BillDetection.h index 327a361..b3cf20b 100644 --- a/Image/BillDetection.h +++ b/Image/BillDetection.h @@ -20,166 +20,65 @@ namespace Image { class BillDetection { private: - static - double angle(cv::Point pt1, cv::Point pt2, cv::Point pt0) - { - double dx1 = pt1.x - pt0.x; - double dy1 = pt1.y - pt0.y; - double dx2 = pt2.x - pt0.x; - double dy2 = pt2.y - pt0.y; - - return (dx1 * dx2 + dy1 * dy2) / sqrt((dx1 * dx1 + dy1 * dy1) * (dx2 * dx2 + dy2 * dy2) + 1e-10); - } public: static cv::Mat highlightBill(cv::Mat in) { - const int MORPH = 9; - const int CANNY = 84; - const int HOUGH = 25; + cv::Mat gray; + cv::cvtColor(in, gray, cv::COLOR_BGR2GRAY); + cv::GaussianBlur(gray, gray, cv::Size(3, 3), 0); - cv::Mat grey; - cv::cvtColor(in, grey, cv::COLOR_BGR2GRAY); - - cv::Size ksize = cv::Size(3, 3); - cv::GaussianBlur(grey, grey, ksize, 0); - - ksize = cv::Size(MORPH, MORPH); - cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, ksize); + cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Point(9, 9)); cv::Mat dilated; - cv::dilate(grey, dilated, kernel); + cv::dilate(gray, dilated, kernel); cv::Mat edges; - cv::Canny(dilated, edges, 0, CANNY, 3); + cv::Canny(dilated, edges, 84, 3); std::vector lines; - cv::HoughLinesP(edges, lines, 1.0, M_PI / 180, HOUGH); + lines.clear(); - for (int i = 0; i < lines.size(); ++i) { - cv::Point p1 = cv::Point(lines[i][0], lines[i][1]); - cv::Point p2 = cv::Point(lines[i][2], lines[i][3]); + cv::HoughLinesP(edges, lines, 1, CV_PI/180, 25); - cv::Scalar rgb = cv::Scalar(255, 0, 0); - - cv::line(edges, p1, p2, rgb, 2, 8); + std::vector::iterator it = lines.begin(); + for(; it != lines.end(); ++it) { + cv::Vec4i l = *it; + cv::line(edges, cv::Point(l[0], l[1]), cv::Point(l[2], l[3]), cv::Scalar(255,0,0), 2, 8); } std::vector> contours; cv::findContours(edges, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_TC89_KCOS); - // filter lines - std::vector>::iterator it; - int i = 0; - for (it = contours.begin(); it != contours.end(); ++it, ++i) { - if (cv::arcLength(*it, false) < 100) { - contours.erase(contours.begin() + i); + std::vector> contoursCleaned; + for (int i = 0; i < contours.size(); ++i) { + if (cv::arcLength(contours[i], false) > 100) { + contoursCleaned.push_back(contours[i]); + } + } + std::vector> contoursArea; + for (int i = 0; i < contoursCleaned.size(); ++i) { + if (cv::contourArea(contoursCleaned[i]) > 10000) { + contoursArea.push_back(contoursCleaned[i]); } } - for (it = contours.begin(); it != contours.end(); ++it, ++i) { - if (cv::contourArea(*it) < 10000) { - contours.erase(contours.begin() + i); - } + /* we probably don't want a polygon all the time?! */ + std::vector > contoursDraw (contoursArea.size()); + for (int i = 0; i < contoursArea.size(); ++i){ + cv::approxPolyDP(cv::Mat(contoursArea[i]), contoursDraw[i], 40, true); } - // create polygons - std::vector> rect; - for (int i = 0; contours.size(); ++i) { - cv::approxPolyDP(contours[i], rect, 40, true); - } - cv::Mat out; + cv::Mat mask = cv::Mat(in.size(), CV_8UC3, cv::Scalar(255, 255, 255)); + cv::drawContours(mask, contoursDraw, -1, cv::Scalar(0, 0, 0), cv::FILLED, 1); - cv::imshow("in", in); - cv::imshow("out", dilated); - cv::imshow("out", edges); - cv::imshow("out", out); - cv::waitKey(0); + cv::Mat out = cv::Mat::zeros(in.size(), CV_8UC3); + cv::bitwise_or(in, mask, out); return out; } - - static - std::vector> findSquares(cv::Mat in) - { - cv::Mat blurred(in); - cv::medianBlur(in, blurred, 9); - - cv::Mat gray0(blurred.size(), CV_8U), gray; - std::vector> contours; - - std::vector> squares; - - // find squares in every color plane of the image - for (int c = 0; c < 3; c++) { - int ch[] = {c, 0}; - cv::mixChannels(&blurred, 1, &gray0, 1, ch, 1); - - // try several threshold levels - const int threshold_level = 2; - for (int l = 0; l < threshold_level; ++l) { - // Canny helps to catch squares with gradient shading - if (l == 0) { - cv::Canny(gray0, gray, 10, 20, 3); - - // Dilate helps to remove potential holes between edge segments - cv::dilate(gray, gray, cv::Mat(), cv::Point(-1, -1)); - } else { - gray = gray0 >= (l + 1) * 255 / threshold_level; - } - - cv::findContours(gray, contours, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE); - - // Test contours - std::vector approx; - for (size_t i = 0; i < contours.size(); ++i) { - cv::approxPolyDP(cv::Mat(contours[i]), approx, cv::arcLength(cv::Mat(contours[i]), true)*0.02, true); - - // contour orientation - if (approx.size() == 4 && - fabs(cv::contourArea(cv::Mat(approx))) > 1 && - cv::isContourConvex(cv::Mat(approx)) - ) { - double maxCosine = 0; - - for (int j = 2; j < 5; ++j) { - double cosine = fabs(angle(approx[j % 4], approx[j - 2], approx[j - 1])); - maxCosine = max(maxCosine, cosine); - } - - if (maxCosine < 0.3) { - squares.push_back(approx); - } - } - } - } - } - - return squares; - } - - static - cv::Mat debugSquares( std::vector> squares, cv::Mat in) - { - for (int i = 0; i (), 0, cv::Point()); - - cv::Rect rect = boundingRect(cv::Mat(squares[i])); - cv::rectangle(in, rect.tl(), rect.br(), cv::Scalar(0,255,0), 2, 8, 0); - - cv::RotatedRect minRect = minAreaRect(cv::Mat(squares[i])); - cv::Point2f rect_points[4]; - minRect.points(rect_points); - - for ( int j = 0; j < 4; j++ ) { - cv::line(in, rect_points[j], rect_points[(j + 1) % 4], cv::Scalar(0,0,255), 1, 8); - } - } - - return in; - } }; } diff --git a/Image/Thresholding.h b/Image/Thresholding.h index 0abb933..08517d4 100644 --- a/Image/Thresholding.h +++ b/Image/Thresholding.h @@ -63,9 +63,9 @@ namespace Image { sum = intImg[x2][y2] - intImg[x2][y1 - 1] - intImg[x1 - 1][y2] + intImg[x1 - 1][y1 - 1]; bgr = in.at(j, i); - brightness = Image::ImageUtils::lightnessFromRgb(bgr[2], bgr[1], bgr[0]); + brightness = (float) Image::ImageUtils::lightnessFromRgb(bgr[2], bgr[1], bgr[0]); - color = brightness * count <= (sum * (100.0 - t) / 100.0) ? 0 : 255; + color = brightness * count <= (sum * (100.0 - t) / 100.0) && brightness < 0.9 ? 0 : 255; out.at(j, i)[0] = color; out.at(j, i)[1] = color; diff --git a/Tools/InvoicePreprocessing/CMakeLists.txt b/Tools/InvoicePreprocessing/CMakeLists.txt index c279d57..df4bfd5 100644 --- a/Tools/InvoicePreprocessing/CMakeLists.txt +++ b/Tools/InvoicePreprocessing/CMakeLists.txt @@ -2,5 +2,6 @@ cmake_minimum_required(VERSION 2.8) project( App ) find_package( OpenCV REQUIRED ) include_directories( ${OpenCV_INCLUDE_DIRS} ) +set( CMAKE_BUILD_TYPE Debug ) add_executable( App main.cpp ) target_link_libraries( App ${OpenCV_LIBS} ) \ No newline at end of file diff --git a/Tools/InvoicePreprocessing/main.cpp b/Tools/InvoicePreprocessing/main.cpp index 0744809..306a380 100644 --- a/Tools/InvoicePreprocessing/main.cpp +++ b/Tools/InvoicePreprocessing/main.cpp @@ -16,6 +16,8 @@ #include "../../Image/Thresholding.h" #include "../../Image/BillDetection.h" +const bool DEBUG = false; + int main(int argc, char** argv) { if (argc != 3) { @@ -38,22 +40,20 @@ int main(int argc, char** argv) return -1; } - /* - std::vector> squares; - Image::BillDetection::findSquares(in, squares); - Image::BillDetection::debugSquares(squares, in); - */ + cv::Mat out = in.clone(); + + out = Image::BillDetection::highlightBill(out); + if (DEBUG) cv::imshow("bill_detection", out); + + out = Image::Thresholding::integralThresholding(out); + if (DEBUG) cv::imshow("thresholding", out); - cv::Mat out = Image::Thresholding::integralThresholding(in); out = Image::Skew::deskewHoughLines(out); + if (DEBUG) cv::imshow("rotation", out); cv::imwrite(argv[2], out); - /* - cv::imshow("in", in); - cv::imshow("out", out); - cv::waitKey(0); - */ + if (DEBUG) cv::waitKey(0); return 0; } \ No newline at end of file diff --git a/tests/Image/img1.png b/tests/Image/img1.png new file mode 100644 index 0000000..01fd9e9 Binary files /dev/null and b/tests/Image/img1.png differ