newtx/imgproc/algs/auto_crop.cpp

339 lines
13 KiB
C++
Raw Normal View History

2024-01-24 04:05:05 +00:00
#include "auto_crop.h"
#include <huagao/hgscanner_error.h>
#include <sane/sane_ex.h>
#include <cis/cis_param.h>
#include <opencv2/imgproc/hal/hal.hpp>
#include "ImageProcess_Public.h"
#define FRONT_TOP 70
#define FX_FY 0.5f
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
static std::string device_opt_json[] = {
"{\"is-anti-skew\":{\"cat\":\"imgp\",\"group\":\"imgp\",\"title\":\"\\u81ea\\u52a8\\u7ea0\\u504f\",\"desc\":\"\\u81ea\\u52a8\\u7ea0\\u6b63\\u6b6a\\u659c\\u9001\\u5165\\u7684\\u6587\\u7a3f\\u56fe\\u50cf\",\"type\":\"bool\",\"pos\":30,\"fix-id\":34844,\"ui-pos\":-1,\"auth\":0,\"size\":4,\"cur\":true,\"default\":true,\"depend\":\"page!=\\u5bf9\\u6298\"},\"is-erase-black-frame\":{\"cat\":\"imgp\",\"group\":\"imgp\",\"title\":\"\\u6d88\\u9664\\u9ed1\\u6846\",\"desc\":\"\\u6d88\\u9664\\u6587\\u7a3f\\u8303\\u56f4\\u5916\\u7684\\u9ed1\\u8272\\u80cc\\u666f\",\"type\":\"bool\",\"pos\":35,\"fix-id\":34849,\"ui-pos\":-1,\"auth\":0,\"size\":4,\"cur\":true,\"default\":true},\"bkg-fill-mode\":{\"cat\":\"imgp\",\"group\":\"imgp\",\"title\":\"\\u80cc\\u666f\\u586b\\u5145\\u65b9\\u5f0f\",\"desc\":\"\\u9009\\u62e9\\u80cc\\u666f\\u586b\\u5145\\u65b9\\u5f0f\",\"type\":\"string\",\"pos\":36,\"fix-id\":34854,\"ui-pos\":-1,\"auth\":0,\"size\":16,\"cur\":\"\\u51f8\\u591a\\u8fb9\\u5f62\",\"default\":\"\\u51f8\\u591a\\u8fb9\\u5f62\",\"range\":[\"\\u51f8\\u591a\\u8fb9\\u5f62\",\"\\u51f9\\u591a\\u8fb9\\u5f62\"],\"depend\":\"is-erase-black-frame==true\"},\"threshold\":{\"cat\":\"imgp\",\"group\":\"imgp\",\"title\":\"\\u9608\\u503c\",\"desc\":\"\\u6587\\u7a3f\\u5e95\\u8272\\u4e0e\\u9ed1\\u8272\\u80cc\\u666f\\u7070\\u5ea6\\u503c\\u7684\\u5dee\\u503c\\u5927\\u4e8e\\u8be5\\u503c\\uff0c\\u624d\\u4f1a\\u88ab\\u8bc6\\u522b\\u4e3a\\u6587\\u7a3f\",\"type\":\"int\",\"pos\":37,\"fix-id\":34851,\"ui-pos\":-1,\"auth\":0,\"size\":4,\"cur\":40,\"default\":40,\"range\":{\"min\":30,\"max\":50,\"step\":1},\"depend\":\"is-erase-black-frame==true||paper==\\u5339\\u914d\\u539f\\u59cb\\u5c3a\\u5bf8||paper==\\u6700\\u5927\\u626b\\u63cf\\u5c3a\\u5bf8||paper==\\u6700\\u5927\\u626b\\u63cf\\u5c3a\\u5bf8\\u81ea\\u52a8\\u88c1\\u5207||is-anti-skew==true\"},\"anti-noise-level\":{\"cat\":\"imgp\",\"group\":\"imgp\",\"title\":\"\\u80cc\\u666f\\u6297\\u566a\\u7b49\\u7ea7\",\"desc\":\"\\u80fd\\u591f\\u5bb9\\u5fcd\\u7684\\u80cc\\u666f\\u6742\\u8272\\u6761\\u7eb9\\u7684\\u5bbd\\u5ea6\",\"type\":\"int\",\"pos\":38,\"fix-id\":34852,\"ui-pos\":-1,\"auth\":0,\"size\":4,\"cur\":8,\"default\":8,\"range\":{\"min\":2,\"max\":20,\"step\":1},\"depend\":\"is-erase-black-frame==true||paper==\\u5339\\u914d\\u539f\\u59cb\\u5c3a\\u5bf8||paper==\\u6700\\u5927\\u626b\\u63cf\\u5c3a\\u5bf8||paper==\\u6700\\u5927\\u626b\\u63cf\\u5c3a\\u5bf8\\u81ea\\u52a8\\u88c1\\u5207||is-anti-skew==true\"},\"margin\":{\"cat\":\"imgp\",\"group\":\"imgp\",\"title\":\"\\u8fb9\\u7f18\\u7f29\\u8fdb\",\"desc\":\"\\u5bfb\\u627e\\u6587\\u7a3f\\u8fb9\\u7f18\\u65f6\\u5bf9\\u8fb9\\u7f18\\u7684\\u4fb5\\u5165\\u7a0b\\u5ea6\",\"type\":\"int\",\"pos\":40,\"fix-id\":34853,\"ui-pos\":-1,\"auth\":0,\"size\":4,\"cur\":5,\"default\":5,\"range\":{\"min\":2,\"max\":30,\"step\":1},\"depend\":\"is-erase-black-frame==true||paper==\\u5339\\u914d\\u539f\\u59cb\\u5c3a\\u5bf8||paper==\\u6700\\u5927\\u626b\\u63cf\\u5c3a\\u5bf8||paper==\\u6700\\u5927\\u626b\\u63cf\\u5c3a\\u5bf8\\u81ea\\u52a8\\u88c1\\u5207||is-anti-skew==true\"},\"paper\":{\"cat\":\"base\",\"group\":\"base\",\"title\":\"\\u7eb8\\u5f20\\u5c3a\\u5bf8\",\"desc\":\"\\u8bbe\\u7f6e\\u51fa\\u56fe\\u5927\\u5c0f\",\"type\":\"string\",\"pos\":1000,\"fix-id\":34831,\"ui-pos\":-1,\"auth\":0,\"size\":44,\"cur\":\"\\u5339\\u914d\\u539f\\u59cb\\u5c3a\\u5bf8\",\"default\":\"\\u5339\\u914d\\u539f\\u59cb\\u5c3a\\u5bf8\",\"range\":[\"A3\",\"8\\u5f00\",\"A4\",\"16\\u5f00\",\"A5\",\"A6\",\"B4\",\"B5\",\"B6\",\"Letter\",\"Double Letter\",\"LEGAL\",\"\\u5339\\u914d\\u539f\\u59cb\\u5c3a\\u5bf8\",{\"resolution<500\":\"\\u6700\\u5927\\u626b\\u63cf\\u5c3a\\u5bf8\\u81ea\\u52a8\\u88c1\\u5207\"},{\"resolution<500\":\"\\u6700\\u5927\\u626b\\u63cf\\u5c3a\\u5bf8\"},{\"resolution<500\":\"\\u4e09\\u8054\\u8bd5\\u5377\"}]},\"lateral\":{\"cat\":\"base\",\"group\":\"base\",\"title\":\"\\u6a2a\\u5411\",\"desc\":\"\\u6a2a\\u5411\\u653e\\u7f6e\\u7eb8\\u5f20\",\"type\":\"bool\",\"pos\":1000,\"fix-id\":34924,\"ui-pos\":-1,\"auth\":0,\"affect\":6,\"visible\":0,\"size\":4,\"cur\":false,\"default\":false,\"depend\":\"paper==A4 ||
};
static void myWarpAffine(cv::InputArray _src, cv::OutputArray _dst, cv::InputArray _M0, cv::Size dsize, int flags, int borderType, const cv::Scalar& borderValue)
{
int interpolation = flags;
cv::Mat src = _src.getMat(), M0 = _M0.getMat();
cv::Mat dst = _dst.getMat();
if (dst.data == src.data)
src = src.clone();
double M[6] = { 0 };
cv::Mat matM(2, 3, CV_64F, M);
if (interpolation == cv::INTER_AREA)
interpolation = cv::INTER_LINEAR;
M0.convertTo(matM, matM.type());
if (!(flags & cv::WARP_INVERSE_MAP))
{
double D = M[0] * M[4] - M[1] * M[3];
D = D != 0 ? 1. / D : 0;
double A11 = M[4] * D, A22 = M[0] * D;
M[0] = A11; M[1] *= -D;
M[3] *= -D; M[4] = A22;
double b1 = -M[0] * M[2] - M[1] * M[5];
double b2 = -M[3] * M[2] - M[4] * M[5];
M[2] = b1; M[5] = b2;
}
cv::hal::warpAffine(src.type(), src.data, src.step, src.cols, src.rows, dst.data, dst.step, dst.cols, dst.rows,
M, interpolation, borderType, borderValue.val);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
#define OPTION_FUNC(name) auto name = [this](void* val) -> void
auto_crop::auto_crop() : image_processor("auto_crop")
{
init();
ADD_THIS_JSON();
}
auto_crop::~auto_crop()
{}
void auto_crop::init(void)
{
OPTION_FUNC(deskew)
{
2024-01-27 09:43:13 +00:00
enabled_ = deskew_ = *(bool*)val;
2024-01-24 04:05:05 +00:00
};
OPTION_FUNC(bg)
{
fill_bg_ = *(bool*)val;
};
OPTION_FUNC(bgm)
{
convex_ = strcmp((char*)val, WORDS_FILLBG_CONVEX) == 0;
};
OPTION_FUNC(thr)
{
threshold_ = *(int*)val;
};
OPTION_FUNC(noise)
{
noise_ = *(int*)val;
};
OPTION_FUNC(margin)
{
indent_ = *(int*)val;
};
OPTION_FUNC(paper)
{
2024-01-24 09:40:41 +00:00
crop_ = //strcmp((char*)val, WORDS_PAPER_ORIGIN_SIZE) == 0 ||
strcmp((char*)val, WORDS_PAPER_MAX_SIZE_CROP) == 0;
2024-01-24 04:05:05 +00:00
if(crop_)
memset(&fixed_paper_, 0, sizeof(fixed_paper_));
else
{
fixed_paper_ = paper::size((char*)val);
if(lateral_)
paper::swap_size(fixed_paper_);
}
};
OPTION_FUNC(lat)
{
if(lateral_ != *(bool*)val)
paper::swap_size(fixed_paper_);
lateral_ = *(bool*)val;
};
opt_handler_[SANE_OPT_NAME(ANTI_SKEW)] = deskew;
opt_handler_[SANE_OPT_NAME(ERASE_BLACK_FRAME)] = bg;
opt_handler_[SANE_OPT_NAME(FILL_BKG_MODE)] = bgm;
opt_handler_[SANE_OPT_NAME(THRESHOLD)] = thr;
opt_handler_[SANE_OPT_NAME(ANTI_NOISE_LEVEL)] = noise;
opt_handler_[SANE_OPT_NAME(MARGIN)] = margin;
opt_handler_[SANE_OPT_NAME(PAPER)] = paper;
opt_handler_[SANE_OPT_NAME(LATERAL)] = lat;
2024-01-24 04:05:05 +00:00
}
int auto_crop::work(PROCIMGINFO& in, PROCIMGINFO& out)
{
int ret = SCANNER_ERR_OK,
2024-01-24 09:40:41 +00:00
dWidth = fixed_paper_.cx * 1.0f / MM_PER_INCH * in.info.resolution_x + .5f,
dHeight = fixed_paper_.cy * 1.0f / MM_PER_INCH * in.info.resolution_x + .5f;
2024-01-24 04:05:05 +00:00
cv::Mat thre;
hg::threshold_Mat(in.img, thre, threshold_);
if (noise_ > 0)
cv::morphologyEx(thre, thre, cv::MORPH_OPEN, getStructuringElement(cv::MORPH_RECT, cv::Size(cv::max(static_cast<int>(noise_ * FX_FY), 1), 1)),
cv::Point(-1, -1), 1, cv::BORDER_CONSTANT, cv::Scalar::all(0));
std::vector<cv::Vec4i> hierarchy;
std::vector<std::vector<cv::Point>> contours;
hg::findContours(thre, contours, hierarchy, cv::RETR_EXTERNAL);
for (std::vector<cv::Point>& sub : contours)
for (cv::Point& p : sub)
p /= FX_FY;
std::vector<cv::Point> maxContour = hg::getMaxContour(contours, hierarchy);
if (maxContour.empty())
{
if (crop_)
out.img = in.img.clone();
else
{
cv::Rect roi = cv::Rect((in.img.cols - dWidth) / 2, FRONT_TOP, dWidth, dHeight) & cv::Rect(0, 0, in.img.cols, in.img.rows);
out.img = in.img(roi).clone();
}
return ret;
}
cv::RotatedRect rect = hg::getBoundingRect(maxContour);
if (rect.size.width < 1 || rect.size.height < 1)
{
out.img = in.img;
return ret;
}
cv::Scalar blankColor;
if (fill_bg_)
2024-01-24 09:40:41 +00:00
if (/*isColorBlank*/0)
2024-01-24 04:05:05 +00:00
{
cv::Rect boudingRect = cv::boundingRect(maxContour);
boudingRect.x *= FX_FY;
boudingRect.y *= FX_FY;
boudingRect.width *= FX_FY;
boudingRect.height *= FX_FY;
cv::Mat temp_bgc;
cv::resize(in.img(boudingRect), temp_bgc, cv::Size(200, 200));
blankColor = hg::getBackGroundColor(temp_bgc, cv::Mat(), 10);
}
else
blankColor = cv::Scalar::all(255);
else
blankColor = cv::Scalar::all(0);
if (crop_)
if (deskew_)
out.img = cv::Mat(cv::Size(rect.size), in.img.type(), blankColor);
else
out.img = cv::Mat(/*rect.boundingRect().size()*/ cv::boundingRect(maxContour).size(), in.img.type(), blankColor);
else
out.img = cv::Mat(dHeight, dWidth, in.img.type(), blankColor);
cv::Mat dstROI;
if (deskew_ && rect.angle != 0)
{
cv::RotatedRect rect_temp = rect;
if (rect_temp.size.width > out.img.cols)
rect_temp.size.width = out.img.cols;
if (rect_temp.size.height > out.img.rows)
rect_temp.size.height = out.img.rows;
cv::Point2f srcTri[4], dstTri[3];
rect_temp.points(srcTri);
srcTri[0].x -= 1;
srcTri[1].x -= 1;
srcTri[2].x -= 1;
int w = rect_temp.size.width;
int h = rect_temp.size.height;
int x = (out.img.cols - w) / 2;
int y = (out.img.rows - h) / 2;
dstTri[0] = cv::Point2f(0, h);
dstTri[1] = cv::Point2f(0, 0);
dstTri[2] = cv::Point2f(w, 0);
dstROI = out.img(cv::Rect(x, y, w, h) & cv::Rect(0, 0, out.img.cols, out.img.rows));
myWarpAffine(in.img, dstROI, cv::getAffineTransform(srcTri, dstTri), dstROI.size(), cv::INTER_LINEAR, cv::BORDER_CONSTANT, blankColor);
}
else
{
cv::Rect bounding = cv::boundingRect(maxContour);
if (bounding.width > out.img.cols)
{
bounding.x += (bounding.width - out.img.cols) / 2;
bounding.width = out.img.cols;
}
if (bounding.height > out.img.rows)
{
bounding.y += (bounding.height - out.img.rows) / 2;
bounding.height = out.img.rows;
}
dstROI = out.img(cv::Rect((out.img.cols - bounding.width) / 2, (out.img.rows - bounding.height) / 2, bounding.width, bounding.height));
in.img(bounding).copyTo(dstROI);
//cv::imwrite("out.img.bmp", out.img);
}
if (fill_bg_)
{
if (convex_)
{
hg::convexHull(maxContour, maxContour);
contours.clear();
contours.push_back(maxContour);
}
cv::Point2f srcTri[4], dstTri[3];
int w, h;
if (deskew_ && rect.angle != 0)
{
rect.points(srcTri);
srcTri[0].x -= 1;
srcTri[1].x -= 1;
srcTri[2].x -= 1;
w = rect.size.width;
h = rect.size.height;
}
else
{
//cv::Rect bounding = rect.boundingRect();
cv::Rect bounding = cv::boundingRect(maxContour);
srcTri[0] = cv::Point(bounding.x, bounding.br().y - 1);
srcTri[1] = cv::Point(bounding.x, bounding.y);
srcTri[2] = cv::Point(bounding.br().x - 1, bounding.y);
w = bounding.width;
h = bounding.height;
}
dstTri[0] = cv::Point2f((dstROI.cols - w) / 2 + indent_, (dstROI.rows - h) / 2 + h - indent_);
dstTri[1] = cv::Point2f((dstROI.cols - w) / 2 + indent_, (dstROI.rows - h) / 2 + indent_);
dstTri[2] = cv::Point2f((dstROI.cols - w) / 2 - indent_ + w, (dstROI.rows - h) / 2 + indent_);
cv::Mat warp_mat = cv::getAffineTransform(srcTri, dstTri);
double* ptr_m = reinterpret_cast<double*>(warp_mat.data);
double a = ptr_m[0];
double b = ptr_m[1];
double c = ptr_m[2];
double d = ptr_m[3];
double e = ptr_m[4];
double f = ptr_m[5];
int x, y;
for (std::vector<cv::Point>& sub : contours)
for (cv::Point& p : sub)
{
x = p.x;
y = p.y;
p.x = static_cast<int>(a * x + b * y + c);
p.y = static_cast<int>(d * x + e * y + f);
}
contours.push_back(std::vector<cv::Point>());
contours[contours.size() - 1].push_back(cv::Point(-1, dstROI.rows - 1));
contours[contours.size() - 1].push_back(cv::Point(-1, -1));
contours[contours.size() - 1].push_back(cv::Point(dstROI.cols, -1));
contours[contours.size() - 1].push_back(cv::Point(dstROI.cols, out.img.rows));
hg::fillPolys(dstROI, contours, blankColor);
}
return ret;
}
int auto_crop::set_value(const char* name/*nullptr for all options*/, void* val/*nullptr for restore*/)
{
int ret = SCANNER_ERR_OK;
if(opt_handler_.count(name))
opt_handler_[name](val);
else
ret = SCANNER_ERR_DEVICE_NOT_SUPPORT;
return ret;
}
int auto_crop::process(std::vector<PROCIMGINFO>& in, std::vector<PROCIMGINFO>& out)
{
int ret = SCANNER_ERR_OK;
for(auto& v: in)
{
PROCIMGINFO o;
chronograph watch;
ret = work(v, o);
2024-01-24 09:40:41 +00:00
2024-01-24 04:05:05 +00:00
o.info = v.info;
o.info.prc_time = watch.elapse_ms();
o.info.prc_stage = get_position();
o.info.width = o.img.cols;
o.info.height = o.img.rows;
out.push_back(o);
}
return ret;
}