create invoice paper detection

This commit is contained in:
Dennis Eichhorn 2022-03-05 00:56:45 +01:00
parent f470d12959
commit f7c30dd375
5 changed files with 44 additions and 144 deletions

View File

@ -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<cv::Vec4i> 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<cv::Vec4i>::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<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);
std::vector<std::vector<cv::Point>> contoursCleaned;
for (int i = 0; i < contours.size(); ++i) {
if (cv::arcLength(contours[i], false) > 100) {
contoursCleaned.push_back(contours[i]);
}
}
std::vector<std::vector<cv::Point>> 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<std::vector<cv::Point> > 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<std::vector<cv::Point>> 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<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;
}
};
}

View File

@ -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<cv::Vec3b>(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<cv::Vec3b>(j, i)[0] = color;
out.at<cv::Vec3b>(j, i)[1] = color;

View File

@ -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} )

View File

@ -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<std::vector<cv::Point>> 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;
}

BIN
tests/Image/img1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 933 KiB