#include "auto_crop.h" #include #include #include #include #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 || paper==16\\u5f00 || paper==A5 || paper==A6 || paper==B5 || paper==B6 || paper==Letter\"}}" }; 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) { enabled_ = deskew_ = *(bool*)val; }; 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) { crop_ = //strcmp((char*)val, WORDS_PAPER_ORIGIN_SIZE) == 0 || strcmp((char*)val, WORDS_PAPER_MAX_SIZE_CROP) == 0; 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; } int auto_crop::work(PROCIMGINFO& in, PROCIMGINFO& out) { int ret = SCANNER_ERR_OK, 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; 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(noise_ * FX_FY), 1), 1)), cv::Point(-1, -1), 1, cv::BORDER_CONSTANT, cv::Scalar::all(0)); std::vector hierarchy; std::vector> contours; hg::findContours(thre, contours, hierarchy, cv::RETR_EXTERNAL); for (std::vector& sub : contours) for (cv::Point& p : sub) p /= FX_FY; std::vector 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_) if (/*isColorBlank*/0) { 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(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& sub : contours) for (cv::Point& p : sub) { x = p.x; y = p.y; p.x = static_cast(a * x + b * y + c); p.y = static_cast(d * x + e * y + f); } contours.push_back(std::vector()); 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& in, std::vector& out) { int ret = SCANNER_ERR_OK; for(auto& v: in) { PROCIMGINFO o; chronograph watch; ret = work(v, o); 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; }