diff --git a/Image/BillDetection.h b/Image/BillDetection.h new file mode 100644 index 0000000..327a361 --- /dev/null +++ b/Image/BillDetection.h @@ -0,0 +1,186 @@ +/** + * Karaka + * + * @package Image + * @copyright Dennis Eichhorn + * @license OMS License 1.0 + * @version 1.0.0 + * @link https://karaka.app + */ +#ifndef IMAGE_BILL_DETECTION_H +#define IMAGE_BILL_DETECTION_H + +#include +#include +#include +#include + +#include "../Utils/MathUtils.h" + +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 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 dilated; + cv::dilate(grey, dilated, kernel); + + cv::Mat edges; + cv::Canny(dilated, edges, 0, CANNY, 3); + + std::vector lines; + cv::HoughLinesP(edges, lines, 1.0, M_PI / 180, HOUGH); + + 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::Scalar rgb = cv::Scalar(255, 0, 0); + + cv::line(edges, p1, p2, rgb, 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); + } + } + + for (it = contours.begin(); it != contours.end(); ++it, ++i) { + if (cv::contourArea(*it) < 10000) { + contours.erase(contours.begin() + i); + } + } + + // create polygons + std::vector> rect; + for (int i = 0; contours.size(); ++i) { + cv::approxPolyDP(contours[i], rect, 40, true); + } + + cv::Mat out; + + cv::imshow("in", in); + cv::imshow("out", dilated); + cv::imshow("out", edges); + cv::imshow("out", out); + cv::waitKey(0); + + 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; + } + }; +} + +#endif \ No newline at end of file diff --git a/Image/Skew.h b/Image/Skew.h index d293670..90d5ffd 100644 --- a/Image/Skew.h +++ b/Image/Skew.h @@ -15,20 +15,14 @@ #include #include -#define deg2rad(angle) \ - ({ __typeof__ (angle) _angle = (angle); \ - (_angle) * M_PI / 180.0; }) - -#define rad2deg(angle) \ - ({ __typeof__ (angle) _angle = (angle); \ - (_angle) * 180.0 / M_PI; }) +#include "../Utils/MathUtils.h" namespace Image { class Skew { private: public: - static inline + static cv::Mat deskewHoughLines(cv::Mat in, int maxDegree = 45) { cv::Size dim = in.size(); diff --git a/Image/Thresholding.h b/Image/Thresholding.h index ae704c7..0abb933 100644 --- a/Image/Thresholding.h +++ b/Image/Thresholding.h @@ -14,23 +14,14 @@ #include #include "ImageUtils.h" - -#define max(a, b) \ - ({ __typeof__ (a) _a = (a); \ - __typeof__ (b) _b = (b); \ - _a > _b ? _a : _b; }) - -#define min(a, b) \ - ({ __typeof__ (a) _a = (a); \ - __typeof__ (b) _b = (b); \ - _a < _b ? _a : _b; }) +#include "../Utils/MathUtils.h" namespace Image { class Thresholding { private: public: - static inline + static cv::Mat integralThresholding(cv::Mat in) { cv::Size dim = in.size(); diff --git a/Tools/InvoicePreprocessing/main.cpp b/Tools/InvoicePreprocessing/main.cpp index ec5ff37..0744809 100644 --- a/Tools/InvoicePreprocessing/main.cpp +++ b/Tools/InvoicePreprocessing/main.cpp @@ -9,9 +9,12 @@ */ #include #include +#include +#include "../../Utils/FileUtils.h" #include "../../Image/Skew.h" #include "../../Image/Thresholding.h" +#include "../../Image/BillDetection.h" int main(int argc, char** argv) { @@ -21,6 +24,12 @@ int main(int argc, char** argv) return -1; } + if (!Utils::FileUtils::file_exists(argv[1])) { + printf("Image file doesn't exist.\n"); + + return -1; + } + cv::Mat in; in = cv::imread(argv[1], cv::IMREAD_UNCHANGED); if (!in.data) { @@ -29,6 +38,12 @@ int main(int argc, char** argv) return -1; } + /* + std::vector> squares; + Image::BillDetection::findSquares(in, squares); + Image::BillDetection::debugSquares(squares, in); + */ + cv::Mat out = Image::Thresholding::integralThresholding(in); out = Image::Skew::deskewHoughLines(out); diff --git a/Utils/FileUtils.h b/Utils/FileUtils.h new file mode 100644 index 0000000..680372c --- /dev/null +++ b/Utils/FileUtils.h @@ -0,0 +1,37 @@ +/** + * Karaka + * + * @package Utils + * @copyright Dennis Eichhorn + * @license OMS License 1.0 + * @version 1.0.0 + * @link https://karaka.app + */ +#ifndef UTILS_TEST_UTILS_H +#define UTILS_TEST_UTILS_H + +#ifdef _WIN32 +#include +#include +#elif defined __linux__ +#include +#endif + +namespace Utils { + class FileUtils { + private: + + public: + static inline + bool file_exists (char *filename) { + #ifdef _WIN32 + return access(filename, F_OK) == 0; + #elif defined __linux__ + struct stat buffer; + return stat(filename, &buffer) == 0; + #endif + } + }; +} + +#endif \ No newline at end of file diff --git a/Utils/MathUtils.h b/Utils/MathUtils.h new file mode 100644 index 0000000..f05f5e2 --- /dev/null +++ b/Utils/MathUtils.h @@ -0,0 +1,31 @@ +/** + * Karaka + * + * @package Image + * @copyright Dennis Eichhorn + * @license OMS License 1.0 + * @version 1.0.0 + * @link https://karaka.app + */ +#ifndef UTILS_MATH_UTILS_H +#define UTILS_MATH_UTILS_H + +#define max(a, b) \ + ({ __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + _a > _b ? _a : _b; }) + +#define min(a, b) \ + ({ __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + _a < _b ? _a : _b; }) + +#define deg2rad(angle) \ + ({ __typeof__ (angle) _angle = (angle); \ + (_angle) * M_PI / 180.0; }) + +#define rad2deg(angle) \ + ({ __typeof__ (angle) _angle = (angle); \ + (_angle) * 180.0 / M_PI; }) + +#endif \ No newline at end of file diff --git a/Utils/TestUtils.h b/Utils/TestUtils.h index 7739b29..5d8ec1a 100644 --- a/Utils/TestUtils.h +++ b/Utils/TestUtils.h @@ -16,13 +16,25 @@ #define ASSERT_EQUALS(a, b, t1, t2) \ ({ __typeof__ (a) _a = (a); \ __typeof__ (b) _b = (b); \ - if (_a == _b) { printf("."); } else { printf("F"); printf("\n\n%s - %i: ", __FILE__, __LINE__); printf(t1, a); printf(" != "); printf(t2, b); return 0; } \ + if (_a == _b) { \ + printf("."); \ + } else { \ + printf("F"); \ + printf("\n\n%s - %i: ", __FILE__, __LINE__); \ + printf(t1, a); printf(" != "); printf(t2, b); printf("\n"); \ + return 0; } \ }) #define ASSERT_EQUALS__DELTA(a, b, delta, t1, t2) \ ({ __typeof__ (a) _a = (a); \ __typeof__ (b) _b = (b); \ - if (abs(_a - _b) <= delta) { printf("."); } else { printf("F"); printf("\n\n%s - %i: ", __FILE__, __LINE__); printf(t1, a); printf(" != "); printf(t2, b); printf("\n"); return 0; } \ + if (abs(_a - _b) <= delta) { \ + printf("."); \ + } else { \ + printf("F"); \ + printf("\n\n%s - %i: ", __FILE__, __LINE__); \ + printf(t1, a); printf(" != "); printf(t2, b); printf("\n"); \ + return 0; } \ }) #endif \ No newline at end of file diff --git a/tests/Image/ImageUtilsTest.cpp b/tests/Image/ImageUtilsTest.cpp index 09dcf56..9f55e10 100644 --- a/tests/Image/ImageUtilsTest.cpp +++ b/tests/Image/ImageUtilsTest.cpp @@ -25,9 +25,9 @@ int main(int argc, char** argv) ASSERT_EQUALS(black, 0.0, "%f", "%f"); float other = Image::ImageUtils::lightnessFromRgb(125, 125, 125); - ASSERT_EQUALS__DELTA(other, 0.524, 0.00001, "%f", "%f"); + ASSERT_EQUALS__DELTA(other, 0.524, 0.001, "%f", "%f"); - printf("\n"); + printf("\n\n"); return 0; } \ No newline at end of file diff --git a/tests/Image/polygons.jpg b/tests/Image/polygons.jpg new file mode 100644 index 0000000..115eb0b Binary files /dev/null and b/tests/Image/polygons.jpg differ diff --git a/tests/Image/strip.jpg b/tests/Image/strip.jpg new file mode 100644 index 0000000..ca19cf7 Binary files /dev/null and b/tests/Image/strip.jpg differ diff --git a/tests/Image/strip2.jpg b/tests/Image/strip2.jpg new file mode 100644 index 0000000..de45934 Binary files /dev/null and b/tests/Image/strip2.jpg differ diff --git a/tests/Image/strip3.jpg b/tests/Image/strip3.jpg new file mode 100644 index 0000000..83f10f1 Binary files /dev/null and b/tests/Image/strip3.jpg differ diff --git a/tests/Image/strip4.jpg b/tests/Image/strip4.jpg new file mode 100644 index 0000000..bdc95bb Binary files /dev/null and b/tests/Image/strip4.jpg differ