添加裁切算法(尚有BUG)

This commit is contained in:
gb 2024-01-24 12:05:05 +08:00
parent 2dbc4e1645
commit e806adfbce
13 changed files with 1126 additions and 7 deletions

View File

@ -0,0 +1,145 @@
{
"is-anti-skew": {
"cat": "imgp",
"group": "imgp",
"title": "自动纠偏",
"desc": "自动纠正歪斜送入的文稿图像",
"type": "bool",
"pos": 30,
"fix-id": 34844,
"ui-pos": -1,
"auth": 0,
"size": 4,
"cur": true,
"default": true,
"depend": "page!=对折"
},
"is-erase-black-frame": {
"cat": "imgp",
"group": "imgp",
"title": "消除黑框",
"desc": "消除文稿范围外的黑色背景",
"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": "背景填充方式",
"desc": "选择背景填充方式",
"type": "string",
"pos": 36,
"fix-id": 34854,
"ui-pos": -1,
"auth": 0,
"size": 16,
"cur": "凸多边形",
"default": "凸多边形",
"range": ["凸多边形", "凹多边形"],
"depend": "is-erase-black-frame==true"
},
"threshold": {
"cat": "imgp",
"group": "imgp",
"title": "阈值",
"desc": "文稿底色与黑色背景灰度值的差值大于该值,才会被识别为文稿",
"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==匹配原始尺寸||paper==最大扫描尺寸||paper==最大扫描尺寸自动裁切||is-anti-skew==true"
},
"anti-noise-level": {
"cat": "imgp",
"group": "imgp",
"title": "背景抗噪等级",
"desc": "能够容忍的背景杂色条纹的宽度",
"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==匹配原始尺寸||paper==最大扫描尺寸||paper==最大扫描尺寸自动裁切||is-anti-skew==true"
},
"margin": {
"cat": "imgp",
"group": "imgp",
"title": "边缘缩进",
"desc": "寻找文稿边缘时对边缘的侵入程度",
"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==匹配原始尺寸||paper==最大扫描尺寸||paper==最大扫描尺寸自动裁切||is-anti-skew==true"
},
"paper": {
"cat": "base",
"group": "base",
"title": "纸张尺寸",
"desc": "设置出图大小",
"type": "string",
"pos": 1000,
"fix-id": 34831,
"ui-pos": -1,
"auth": 0,
"size": 44,
"cur": "匹配原始尺寸",
"default": "匹配原始尺寸",
"range": ["A3", "8开", "A4", "16开", "A5", "A6", "B4", "B5", "B6", "Letter", "Double Letter", "LEGAL", "匹配原始尺寸", {
"resolution<500": "最大扫描尺寸自动裁切"
}, {
"resolution<500": "最大扫描尺寸"
}, {
"resolution<500": "三联试卷"
}]
},
"lateral": {
"cat": "base",
"group": "base",
"title": "横向",
"desc": "横向放置纸张",
"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开 || paper==A5 || paper==A6 || paper==B5 || paper==B6 || paper==Letter"
}
}

View File

@ -81,10 +81,10 @@ void scanner_hw::init(void)
auto m = [this](void* value) -> void
{
if(strcmp((char*)value, "\347\201\260\345\272\246") == 0)
if(strcmp((char*)value, WORDS_COLOR_GRAY) == 0)
mode_ = (char*)value;
else
mode_ = "\345\275\251\350\211\262";
mode_ = WORDS_COLOR_COLOR;
};
auto r = [this](void* value) -> void
{
@ -204,7 +204,7 @@ void scanner_hw::init(void)
};
auto dbchk = [this](void* value) -> void
{
double_chk_ = strcmp((char*)value, "\347\246\201\347\224\250") != 0;
double_chk_ = strcmp((char*)value, WORDS_FORBIDDEN) != 0;
};
auto motsp = [this](void* value) -> void
{
@ -240,7 +240,7 @@ void scanner_hw::init(void)
};
OPT_HANDLER(cntm)
{
scan_cntless_ = strcmp((char*)value, "\350\277\236\347\273\255\346\211\253\346\217\217") == 0;
scan_cntless_ = strcmp((char*)value, WORDS_SCAN_CONTINUOUS) == 0;
};
OPT_HANDLER(cnt)
{

View File

@ -78,7 +78,7 @@ class scanner_hw : public sane_opt_provider
double stretch_h_ = 1.0f;
double stretch_v_ = 1.0f;
std::string paper_ = "\345\214\271\351\205\215\345\216\237\345\247\213\345\260\272\345\257\270";
std::string paper_ = WORDS_PAPER_ORIGIN_SIZE;
bool lateral_ = false;
bool lateral_en_ = false;
bool staple_chk_ = true;

View File

@ -0,0 +1,424 @@
#include "ImageProcess_Public.h"
#include <opencv2/core/core_c.h>
#include <opencv2/core/types_c.h>
#include <opencv2/imgproc/imgproc_c.h>
namespace hg
{
void convexHull(const std::vector<cv::Point>& src, std::vector<cv::Point>& dst, bool clockwise)
{
CvMemStorage* storage = cvCreateMemStorage(); //
CvSeq* ptseq = cvCreateSeq(CV_SEQ_KIND_GENERIC | CV_32SC2, sizeof(CvContour), sizeof(CvPoint), storage); //ptseqstorage
//将src的点集填充至ptseq
for (const cv::Point& item : src)
{
CvPoint p;
p.x = item.x;
p.y = item.y;
cvSeqPush(ptseq, &p);
}
//获取轮廓点
CvSeq* hull = cvConvexHull2(ptseq, nullptr, clockwise ? CV_CLOCKWISE : CV_COUNTER_CLOCKWISE, 0);
if (hull == nullptr)
{
//释放storage
cvReleaseMemStorage(&storage);
return;
}
//填充dst
dst.clear();
for (int i = 0, hullCount = hull->total; i < hullCount; i++)
dst.push_back(**CV_GET_SEQ_ELEM(CvPoint*, hull, i));
//释放storage
cvReleaseMemStorage(&storage);
}
#define R_COLOR 255
void fillConvexHull(cv::Mat& image, const std::vector<cv::Point>& points)
{
uint index_top = 0;
uint index_bottom = 0;
for (size_t i = 0, length = points.size(); i < length; i++)
{
if (points[i].y < points[index_top].y)
index_top = i;
if (points[i].y > points[index_bottom].y)
index_bottom = i;
}
std::vector<cv::Point> edge_left;
uint temp = index_top;
while (temp != index_bottom)
{
edge_left.push_back(points[temp]);
temp = (temp + points.size() - 1) % points.size();
}
edge_left.push_back(points[index_bottom]);
std::vector<cv::Point> edge_right;
temp = index_top;
while (temp != index_bottom)
{
edge_right.push_back(points[temp]);
temp = (temp + points.size() + 1) % points.size();
}
edge_right.push_back(points[index_bottom]);
std::vector<int> left_edge_x;
std::vector<int> left_edge_y;
for (size_t i = 0, length = edge_left.size() - 1; i < length; i++)
{
int y_top = edge_left[i].y;
int x_top = edge_left[i].x;
int y_bottom = edge_left[i + 1].y;
int x_bottom = edge_left[i + 1].x;
for (int y = y_top; y < y_bottom; y++)
if (y >= 0 && y_top != y_bottom && y < image.rows)
{
left_edge_x.push_back(((x_bottom - x_top) * y + x_top * y_bottom - x_bottom * y_top) / (y_bottom - y_top));
left_edge_y.push_back(y);
}
}
size_t step = image.step;
unsigned char* ptr;
ptr = image.data + static_cast<uint>(left_edge_y[0]) * step;
for (size_t i = 0, length = left_edge_x.size(); i < length; i++)
{
int pix = left_edge_x[i];
if (pix < image.cols - 1 && pix > 0)
memset(ptr + i * step, R_COLOR, static_cast<size_t>((pix + 1) * image.channels()));
}
std::vector<int> right_edge_x;
std::vector<int> right_edge_y;
for (size_t i = 0, length = edge_right.size() - 1; i < length; i++)
{
int y_top = edge_right[i].y;
int x_top = edge_right[i].x;
int y_bottom = edge_right[i + 1].y;
int x_bottom = edge_right[i + 1].x;
for (int y = y_top; y < y_bottom; y++)
if (y_top != y_bottom && y < image.rows && y >= 0)
{
right_edge_x.push_back(((x_bottom - x_top) * y + x_top * y_bottom - x_bottom * y_top) / (y_bottom - y_top));
right_edge_y.push_back(y);
}
}
ptr = image.data + static_cast<uint>(right_edge_y[0]) * step;
for (size_t i = 0, length = right_edge_x.size(); i < length; i++)
{
int pix = right_edge_x[i];
if (pix < image.cols - 1 && pix > 0)
memset(ptr + i * step + pix * image.channels(), R_COLOR, step - static_cast<size_t>(pix * image.channels()));
}
if (edge_left[0].y > 0)
memset(image.data, R_COLOR, static_cast<size_t>(edge_left[0].y) * step);
if (edge_left.back().y < image.rows - 1)
memset(image.data + static_cast<size_t>(edge_left.back().y) * step, R_COLOR,
static_cast<size_t>(image.rows - edge_left.back().y) * step);
}
void fillPolys(cv::Mat& image, const std::vector<std::vector<cv::Point>>& contours, const cv::Scalar& color)
{
if (contours.empty()) return;
size_t count = contours.size();
cv::Point** pointss = new cv::Point*[count];
int* npts = new int[count];
for (size_t i = 0; i < count; i++)
{
size_t length = contours[i].size();
npts[i] = length;
pointss[i] = new cv::Point[length];
for (size_t j = 0; j < length; j++)
pointss[i][j] = contours[i][j];
}
cv::fillPoly(image, const_cast<const cv::Point**>(pointss), npts, count, color);
for (size_t i = 0; i < count; i++)
delete[] pointss[i];
delete[] pointss;
delete[] npts;
}
void findContours(const cv::Mat& src, std::vector<std::vector<cv::Point>>& contours, std::vector<cv::Vec4i>& hierarchy, int retr, int method, cv::Point offset)
{
CvMat c_image;
c_image = cvMat(src.rows, src.cols, src.type(), src.data);
c_image.step = src.step[0];
c_image.type = (c_image.type & ~cv::Mat::CONTINUOUS_FLAG) | (src.flags & cv::Mat::CONTINUOUS_FLAG);
cv::MemStorage storage(cvCreateMemStorage());
CvSeq* _ccontours = nullptr;
#if CV_VERSION_REVISION <= 6
cvFindContours(&c_image, storage, &_ccontours, sizeof(CvContour), retr, method, CvPoint(offset));
#else
cvFindContours(&c_image, storage, &_ccontours, sizeof(CvContour), retr, method, CvPoint{ offset.x, offset.y });
#endif
if (!_ccontours)
{
contours.clear();
return;
}
cv::Seq<CvSeq*> all_contours(cvTreeToNodeSeq(_ccontours, sizeof(CvSeq), storage));
size_t total = all_contours.size();
contours.resize(total);
cv::SeqIterator<CvSeq*> it = all_contours.begin();
for (size_t i = 0; i < total; i++, ++it)
{
CvSeq* c = *it;
reinterpret_cast<CvContour*>(c)->color = static_cast<int>(i);
int count = c->total;
int* data = new int[static_cast<size_t>(count * 2)];
cvCvtSeqToArray(c, data);
for (int j = 0; j < count; j++)
{
contours[i].push_back(cv::Point(data[j * 2], data[j * 2 + 1]));
}
delete[] data;
}
hierarchy.resize(total);
it = all_contours.begin();
for (size_t i = 0; i < total; i++, ++it)
{
CvSeq* c = *it;
int h_next = c->h_next ? reinterpret_cast<CvContour*>(c->h_next)->color : -1;
int h_prev = c->h_prev ? reinterpret_cast<CvContour*>(c->h_prev)->color : -1;
int v_next = c->v_next ? reinterpret_cast<CvContour*>(c->v_next)->color : -1;
int v_prev = c->v_prev ? reinterpret_cast<CvContour*>(c->v_prev)->color : -1;
hierarchy[i] = cv::Vec4i(h_next, h_prev, v_next, v_prev);
}
storage.release();
}
cv::Scalar getBackGroundColor(const cv::Mat& image, const cv::Mat& mask, int threshold)
{
float range[] = { 0, 256 };
const float* ranges = { range };
int histSize = 256;
cv::Scalar bgc;
if (image.channels() == 3)
{
cv::Mat mv[3];
cv::split(image, mv);
cv::Mat hist[3];
for (int i = 0; i < 3; i++)
{
cv::calcHist(&mv[i], 1, 0, mask, hist[i], 1, &histSize, &ranges);
int index_max = 0;
int max_value = 0;
for (size_t j = threshold; j < 256; j++)
if (hist[i].at<float>(j) > max_value)
{
index_max = j;
max_value = hist[i].at<float>(j);
}
bgc[i] = index_max;
}
}
else
{
cv::Mat hist;
cv::calcHist(&image, 1, 0, mask, hist, 1, &histSize, &ranges);
int index_max = 0;
int max_value = 0;
for (size_t j = threshold; j < 256; j++)
if (hist.at<float>(j) > max_value)
{
index_max = j;
max_value = hist.at<float>(j);
}
bgc = cv::Scalar::all(index_max);
}
return bgc;
}
cv::RotatedRect getBoundingRect(const std::vector<cv::Point>& contour)
{
if (contour.empty()) return {};
cv::RotatedRect rect = minAreaRect(contour);
if (rect.angle < -45)
{
rect.angle += 90;
float temp = rect.size.width;
rect.size.width = rect.size.height;
rect.size.height = temp;
}
if (rect.angle > 45)
{
rect.angle -= 90;
float temp = rect.size.width;
rect.size.width = rect.size.height;
rect.size.height = temp;
}
return rect;
}
std::vector<cv::Point> getMaxContour(const std::vector<std::vector<cv::Point>>& contours, const std::vector<cv::Vec4i>& hierarchy)
{
std::vector<cv::Point> maxContour;
if (contours.size() < 1) return {};
for (size_t i = 0, length = hierarchy.size(); i < length; i++)
if (hierarchy[i][3] == -1)
for (const auto &item : contours[i])
maxContour.push_back(item);
return maxContour;
}
std::vector<cv::Point> getVertices(const cv::RotatedRect& rect)
{
cv::Point2f box[4];
rect.points(box);
std::vector<cv::Point> points;
for (int i = 0; i < 4; i++)
points.push_back(cv::Point(box[i]));
return points;
}
void polyIndent(std::vector<cv::Point>& points, const cv::Point& center, int indent)
{
static cv::Point zero(0, 0);
for (cv::Point& item : points)
{
#if 0
cv::Point vec = item - center;
if (vec != zero)
{
int length = vec.x * vec.x + vec.y * vec.y;
float x = cv::sqrt(static_cast<float>(vec.x * vec.x / length)) * indent;
float y = cv::sqrt(static_cast<float>(vec.y * vec.y / length)) * indent;
if (vec.x < 0) x *= -1.0f;
if (vec.y < 0) y *= -1.0f;
item.x -= static_cast<int>(x);
item.y -= static_cast<int>(y);
}
#else
if (item.x > center.x)
item.x -= indent;
else
item.x += indent;
if (item.y > center.y)
item.y -= indent;
else
item.y += indent;
#endif
}
}
cv::Mat transforColor(const cv::Mat& src)
{
if (src.channels() == 1) return src.clone();
std::vector<cv::Mat> channels(3);
cv::split(src, channels);
cv::Mat temp, dst;
bitwise_or(channels[0], channels[1], temp);
bitwise_or(channels[2], temp, dst);
temp.release();
for (cv::Mat& index : channels)
index.release();
return dst;
}
#define DEFAULT_THRESHOLD 30
#define THRESHOLD_OFFSET 10
void threshold_Mat(const cv::Mat& src, cv::Mat& dst, double thre)
{
cv::Mat temp;
if (src.channels() == 3)
{
#ifdef USE_ONENCL
if (cl_res.context)
transforColor_threshold_opencl(src, dst, static_cast<uchar>(thre));
else
#endif
{
//temp = transforColor(src);
cv::cvtColor(src, temp, cv::COLOR_BGR2GRAY);
}
}
else
temp = src;
if (thre > 0)
cv::threshold(temp, dst, thre, 255, cv::THRESH_BINARY);
else
{
std::vector<int> unusual_cols;
std::vector<uchar> unusual_gray;
uchar* firstLine = temp.ptr<uchar>(0);
uchar* lastLine = temp.ptr<uchar>(temp.rows - 1);
uchar temp_gray;
for (size_t i = 0, length = temp.step; i < length; i++)
{
temp_gray = cv::min(firstLine[i], lastLine[i]);
if (temp_gray > DEFAULT_THRESHOLD)
{
unusual_cols.push_back(i);
unusual_gray.push_back(temp_gray);
}
}
cv::threshold(temp, dst, DEFAULT_THRESHOLD + THRESHOLD_OFFSET, 255, cv::THRESH_BINARY);
for (size_t i = 0; i < unusual_cols.size(); i++)
dst(cv::Rect(unusual_cols[i], 0, 1, temp.rows)) = 0;
}
}
cv::Point warpPoint(const cv::Point& p, const cv::Mat& warp_mat)
{
double src_data[3] = { static_cast<double>(p.x), static_cast<double>(p.y), 1 };
cv::Mat src(3, 1, warp_mat.type(), src_data); //warp_mat.type() == CV_64FC1
cv::Mat dst = warp_mat * src;
double* ptr = reinterpret_cast<double*>(dst.data);
return cv::Point(static_cast<int>(ptr[0]), static_cast<int>(ptr[1]));
}
int distanceP2P(const cv::Point& p1, const cv::Point& p2)
{
return cv::sqrt(cv::pow(p1.x - p2.x, 2) + cv::pow(p1.y - p2.y, 2));
}
float distanceP2L(const cv::Point& p, const cv::Point& l1, const cv::Point& l2)
{
//求直线方程
int A = 0, B = 0, C = 0;
A = l1.y - l2.y;
B = l2.x - l1.x;
C = l1.x * l2.y - l1.y * l2.x;
//代入点到直线距离公式
return ((float)abs(A * p.x + B * p.y + C)) / ((float)sqrtf(A * A + B * B));
}
}

View File

@ -0,0 +1,144 @@
/*
* ====================================================
* ImageProcess里面多个类反复使用
*
* 2020/4/21
* 2020/4/21
* 2021/07/12 v1.1 getBoundingRect中 angle > 90
* 2021/07/22 v1.2 convexHull中BUG
* 2023/11/02 v1.3 threshold_Mat,thre<0
* 2023/12/01 v1.4 getBackGroundColor算法
* 2023/12/02 v1.4.1 getBackGroundColor增加threshold阈值
* 2023/12/04 v1.4.2 opencv版本接口
* 2023/12/05 v1.4.3 getBackGroundColor支持单通道图像背景色识别
* v1.4.3
* ====================================================
*/
#ifndef IMAGE_PROCESS_PUBLIC_H
#define IMAGE_PROCESS_PUBLIC_H
#include "opencv2/opencv.hpp"
#include <vector>
namespace hg
{
/*
*
* src:
* dst:
* clockwise: true为顺时针排序false为逆时针排序
*/
void convexHull(const std::vector<cv::Point>& src, std::vector<cv::Point>& dst, bool clockwise = false);
/*
*
* image:
* points:
*/
void fillConvexHull(cv::Mat& image, const std::vector<cv::Point>& points);
/*
*
* image:
* contours:
* color:
*/
void fillPolys(cv::Mat& image, const std::vector<std::vector<cv::Point>>& contours, const cv::Scalar& color);
/*
*
* src:
* contours:
* hierarchy: contours的数量对应retr选项不同
* retr:
* method:
* offset: 0,0
*/
void findContours(const cv::Mat& src, std::vector<std::vector<cv::Point>>& contours, std::vector<cv::Vec4i>& hierarchy,
int retr = cv::RETR_LIST, int method = cv::CHAIN_APPROX_SIMPLE, cv::Point offset = cv::Point(0, 0));
/// <summary>
/// 获取图片文稿底色
/// </summary>
/// <param name="image">图像,三通道</param>
/// <param name="mask">掩膜</param>
/// <param name="threshold">阈值,用于排除黑色背景</param>
/// <returns>文稿底色</returns>
cv::Scalar getBackGroundColor(const cv::Mat& image, const cv::Mat& mask = cv::Mat(), int threshold = 20);
/*
*
* contour:
* :
*/
cv::RotatedRect getBoundingRect(const std::vector<cv::Point>& contour);
/*
* :
* contours:
* hierarchy: contours对应
* :
*/
std::vector<cv::Point> getMaxContour(const std::vector<std::vector<cv::Point>>& contours, const std::vector<cv::Vec4i>& hierarchy);
/*
* :
* contours:
* hierarchy: contours对应
* :
*/
std::vector<cv::Point> getVertices(const cv::RotatedRect& rect);
/*
* :
* points:
* center: center点缩进
* indent:
*/
void polyIndent(std::vector<cv::Point>& points, const cv::Point& center, int indent);
/*
* : src为彩色图像时
* src:
* dst:
* thre: thre < 0
*/
void threshold_Mat(const cv::Mat& src, cv::Mat& dst, double thre);
/*
* :
* src:
* :
*/
cv::Mat transforColor(const cv::Mat& src);
/*
* : 仿
* p:
* warp_mat: 仿
* :
*/
cv::Point warpPoint(const cv::Point& p, const cv::Mat& warp_mat);
/*
* :
* p1: 1
* p2: 2
* :
*/
int distanceP2P(const cv::Point& p1, const cv::Point& p2);
/*
* : 线
* p:
* l1: 线1
* l2: 线2
* : 线
*/
float distanceP2L(const cv::Point& p, const cv::Point& l1, const cv::Point& l2);
}
#endif // !IMAGE_PROCESS_C_H

337
imgproc/algs/auto_crop.cpp Normal file
View File

@ -0,0 +1,337 @@
#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 || 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)
{
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_FULL_NAME(ANTI_SKEW)] = deskew;
opt_handler_[SANE_FULL_NAME(ERASE_BLACK_FRAME)] = bg;
opt_handler_[SANE_FULL_NAME(FILL_BKG_MODE)] = bgm;
opt_handler_[SANE_FULL_NAME(THRESHOLD)] = thr;
opt_handler_[SANE_FULL_NAME(ANTI_NOISE_LEVEL)] = noise;
opt_handler_[SANE_FULL_NAME(MARGIN)] = margin;
opt_handler_[SANE_FULL_NAME(PAPER)] = paper;
opt_handler_[SANE_FULL_NAME(LATERAL)] = lat;
}
int auto_crop::work(PROCIMGINFO& in, PROCIMGINFO& out)
{
int ret = SCANNER_ERR_OK,
dWidth = fixed_paper_.cx,
dHeight = fixed_paper_.cy;
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_)
if (/*isColorBlank*/1)
{
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);
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;
}

39
imgproc/algs/auto_crop.h Normal file
View File

@ -0,0 +1,39 @@
// perform crop, deskew, fill background ...
//
// Date: 2024-01-24
#pragma once
#include <imgprc/img_processor.h>
#include <map>
#include <base/paper.h>
class auto_crop : public image_processor
{
bool crop_ = true; // auto crop
bool deskew_ = true;
bool fill_bg_ = true;
bool convex_ = true;
bool fill_clr_ = false;
int threshold_ = 10;
int indent_ = 5;
int noise_ = 8;
SIZE fixed_paper_ = {0, 0};
bool lateral_ = false;
std::map<std::string, std::function<void(void*)>> opt_handler_;
void init(void);
int work(PROCIMGINFO& in, PROCIMGINFO& out);
public:
auto_crop();
protected:
~auto_crop();
public:
virtual int set_value(const char* name/*nullptr for all options*/, void* val/*nullptr for restore*/) override;
public:
virtual int process(std::vector<PROCIMGINFO>& in, std::vector<PROCIMGINFO>& out) override;
};

View File

@ -7,6 +7,7 @@
#include "./algs/rebuild.h"
#include "./algs/stretch.h"
#include "./algs/auto_crop.h"
static std::string device_opt_json[] = {
"{\"is-multiout\":{\"cat\":\"base\",\"group\":\"base\",\"title\":\"\\u591a\\u6d41\\u8f93\\u51fa\",\"desc\":\"\\u540c\\u65f6\\u8f93\\u51fa\\u591a\\u79cd\\u989c\\u8272\\u6a21\\u5f0f\\u7684\\u56fe\\u50cf\",\"type\":\"bool\",\"fix-id\":34817,\"ui-pos\":10,\"auth\":0,\"size\":4,\"cur\":false,\"default\":false},\"multiout-type\":{\"cat\":\"base\",\"group\":\"base\",\"title\":\"\\u591a\\u6d41\\u8f93\\u51fa\\u7c7b\\u578b\",\"desc\":\"\\u9009\\u62e9\\u591a\\u6d41\\u8f93\\u51fa\\u7684\\u7c7b\\u578b\",\"type\":\"string\",\"fix-id\":34818,\"ui-pos\":11,\"auth\":0,\"enabled\":false,\"size\":66,\"cur\":\"\\u5f69\\u8272+\\u7070\\u5ea6+\\u9ed1\\u767d\",\"default\":\"\\u5f69\\u8272+\\u7070\\u5ea6+\\u9ed1\\u767d\",\"range\":[\"\\u5f69\\u8272+\\u7070\\u5ea6+\\u9ed1\\u767d\",\"\\u5f69\\u8272+\\u7070\\u5ea6\",\"\\u5f69\\u8272+\\u9ed1\\u767d\",\"\\u7070\\u5ea6+\\u9ed1\\u767d\"],\"depend\":\"is-multiout==true\"},\"mode\":{\"cat\":\"base\",\"group\":\"base\",\"title\":\"\\u989c\\u8272\\u6a21\\u5f0f\",\"desc\":\"\\u9009\\u62e9\\u8272\\u5f69\\u6a21\\u5f0f\",\"type\":\"string\",\"fix-id\":34819,\"ui-pos\":15,\"auth\":0,\"size\":24,\"cur\":\"24\\u4f4d\\u5f69\\u8272\",\"default\":\"24\\u4f4d\\u5f69\\u8272\",\"range\":[\"24\\u4f4d\\u5f69\\u8272\",\"256\\u7ea7\\u7070\\u5ea6\",\"\\u9ed1\\u767d\",\"\\u989c\\u8272\\u81ea\\u52a8\\u8bc6\\u522b\"],\"depend\":\"is-multiout!=true\"},\"dump-img\":{\"cat\":\"base\",\"group\":\"\\u9ad8\\u7ea7\\u8bbe\\u7f6e\",\"title\":\"\\u8f93\\u51fa\\u4e2d\\u95f4\\u56fe\\u50cf\",\"desc\":\"\\u8f93\\u51fa\\u5404\\u7b97\\u6cd5\\u4e2d\\u95f4\\u7ed3\\u679c\\u56fe\\u50cf\",\"type\":\"bool\",\"ui-pos\":10,\"auth\":0,\"affect\":2,\"size\":4,\"cur\":false,\"default\":false}}"
@ -201,9 +202,11 @@ int imgproc_mgr::load_processor(const char* path)
// ADD_IMG_PROCESSOR(rebuild);
ADD_IMG_PROCESSOR(stretch);
ADD_IMG_PROCESSOR(auto_crop);
std::sort(processors_.begin(), processors_.end(), &imgproc_mgr::sort_processor_by_pos);
return ret;
}
int imgproc_mgr::clear(void)

View File

@ -61,4 +61,11 @@ namespace paper
{
return paperi.size(name);
}
void swap_size(SIZE& s)
{
s.cx ^= s.cy;
s.cy ^= s.cx;
s.cx ^= s.cy;
}
};

View File

@ -9,4 +9,5 @@ namespace paper
{
// get known paper size in mm
SIZE size(const char* name);
void swap_size(SIZE& s);
};

View File

@ -137,3 +137,6 @@ extern uint64_t GetCurrentThreadId(void);
#define pid_t int
#endif
#include "words.h"

16
sdk/base/words.h Normal file
View File

@ -0,0 +1,16 @@
// const multi-bytes-words definition
//
// Date: 2024-01-24
#pragma once
#define WORDS_COLOR_COLOR "\345\275\251\350\211\262"
#define WORDS_COLOR_GRAY "\347\201\260\345\272\246"
#define WORDS_PAPER_ORIGIN_SIZE "\345\214\271\351\205\215\345\216\237\345\247\213\345\260\272\345\257\270"
#define WORDS_PAPER_MAX_SIZE_CROP "\346\234\200\345\244\247\346\211\253\346\217\217\345\260\272\345\257\270\350\207\252\345\212\250\350\243\201\345\210\207"
#define WORDS_FORBIDDEN "\347\246\201\347\224\250"
#define WORDS_SCAN_CONTINUOUS "\350\277\236\347\273\255\346\211\253\346\217\217"
#define WORDS_FILLBG_CONVEX "\345\207\270\345\244\232\350\276\271\345\275\242"

View File

@ -60,8 +60,8 @@ add_packagedirs("sdk")
add_defines("BUILD_AS_DEVICE")
add_defines("VER_MAIN=2")
add_defines("VER_FAMILY=200")
add_defines("VER_DATE=20240123")
add_defines("VER_BUILD=32")
add_defines("VER_DATE=20240124")
add_defines("VER_BUILD=8")
target("conf")
set_kind("phony")