mirror of
https://github.com/Karaka-Management/cOMS.git
synced 2026-01-28 02:18:40 +00:00
start with paper detection
This commit is contained in:
parent
c51b60152a
commit
f470d12959
186
Image/BillDetection.h
Normal file
186
Image/BillDetection.h
Normal file
|
|
@ -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 <stdio.h>
|
||||
#include <math.h>
|
||||
#include <opencv2/opencv.hpp>
|
||||
#include <vector>
|
||||
|
||||
#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<cv::Vec4i> 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<std::vector<cv::Point>> contours;
|
||||
cv::findContours(edges, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_TC89_KCOS);
|
||||
|
||||
// filter lines
|
||||
std::vector<std::vector<cv::Point>>::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<std::vector<cv::Point>> 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<std::vector<cv::Point>> findSquares(cv::Mat in)
|
||||
{
|
||||
cv::Mat blurred(in);
|
||||
cv::medianBlur(in, blurred, 9);
|
||||
|
||||
cv::Mat gray0(blurred.size(), CV_8U), gray;
|
||||
std::vector<std::vector<cv::Point>> contours;
|
||||
|
||||
std::vector<std::vector<cv::Point>> 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<cv::Point> 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<std::vector<cv::Point>> squares, cv::Mat in)
|
||||
{
|
||||
for (int i = 0; i <squares.size(); ++i) {
|
||||
cv::drawContours(in, squares, i, cv::Scalar(255,0,0), 1, 8, std::vector<cv::Vec4i>(), 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
|
||||
10
Image/Skew.h
10
Image/Skew.h
|
|
@ -15,20 +15,14 @@
|
|||
#include <opencv2/opencv.hpp>
|
||||
#include <vector>
|
||||
|
||||
#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();
|
||||
|
|
|
|||
|
|
@ -14,23 +14,14 @@
|
|||
#include <opencv2/opencv.hpp>
|
||||
|
||||
#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();
|
||||
|
|
|
|||
|
|
@ -9,9 +9,12 @@
|
|||
*/
|
||||
#include <stdio.h>
|
||||
#include <opencv2/opencv.hpp>
|
||||
#include <vector>
|
||||
|
||||
#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<std::vector<cv::Point>> squares;
|
||||
Image::BillDetection::findSquares(in, squares);
|
||||
Image::BillDetection::debugSquares(squares, in);
|
||||
*/
|
||||
|
||||
cv::Mat out = Image::Thresholding::integralThresholding(in);
|
||||
out = Image::Skew::deskewHoughLines(out);
|
||||
|
||||
|
|
|
|||
37
Utils/FileUtils.h
Normal file
37
Utils/FileUtils.h
Normal file
|
|
@ -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 <unistd.h>
|
||||
#include <stdbool.h>
|
||||
#elif defined __linux__
|
||||
#include <sys/stat.h>
|
||||
#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
|
||||
31
Utils/MathUtils.h
Normal file
31
Utils/MathUtils.h
Normal file
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
BIN
tests/Image/polygons.jpg
Normal file
BIN
tests/Image/polygons.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 327 KiB |
BIN
tests/Image/strip.jpg
Normal file
BIN
tests/Image/strip.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
BIN
tests/Image/strip2.jpg
Normal file
BIN
tests/Image/strip2.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
BIN
tests/Image/strip3.jpg
Normal file
BIN
tests/Image/strip3.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
BIN
tests/Image/strip4.jpg
Normal file
BIN
tests/Image/strip4.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
Loading…
Reference in New Issue
Block a user