From 4b729a45851183510920beb12ed0a2fc040ca8da Mon Sep 17 00:00:00 2001 From: lovelyyoung <1002639516@qq.com> Date: Thu, 10 Jun 2021 14:00:59 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9UVTwain=20=E8=83=8C=E6=99=AF?= =?UTF-8?q?=E9=BB=91=E8=89=B2=E6=83=85=E5=86=B5=E4=B8=8B=E8=A3=81=E5=88=87?= =?UTF-8?q?=E5=BC=82=E5=B8=B8=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- huagao/ImageProcess/ImageApplyAutoCrop.cpp | 196 +++++++++++++-------- huagao/ImageProcess/ImageApplyAutoCrop.h | 45 ++++- 2 files changed, 162 insertions(+), 79 deletions(-) diff --git a/huagao/ImageProcess/ImageApplyAutoCrop.cpp b/huagao/ImageProcess/ImageApplyAutoCrop.cpp index 5fc5f12e..d5f0896c 100644 --- a/huagao/ImageProcess/ImageApplyAutoCrop.cpp +++ b/huagao/ImageProcess/ImageApplyAutoCrop.cpp @@ -1,13 +1,12 @@ #include "ImageApplyAutoCrop.h" #include "ImageProcess_Public.h" -#define RE 2 - CImageApplyAutoCrop::CImageApplyAutoCrop() : m_isCrop(false) , m_isDesaskew(false) , m_isFillBlank(false) , m_isConvexHull(true) + , m_isFillColor(false) , m_threshold(40) , m_noise(2) , m_indent(5) @@ -19,6 +18,7 @@ CImageApplyAutoCrop::CImageApplyAutoCrop(bool isCrop, bool isDesaskew, bool isFi , m_isDesaskew(isDesaskew) , m_isFillBlank(isFillBlank) , m_isConvexHull(isConvex) + , m_isFillColor(false) , m_threshold(threshold) , m_noise(noise) , m_indent(indent) @@ -32,45 +32,27 @@ CImageApplyAutoCrop::~CImageApplyAutoCrop() void CImageApplyAutoCrop::apply(cv::Mat& pDib, int side) { -#ifdef LOG - FileTools::write_log("imgprc.txt", "enter CImageApplyAutoCrop apply"); -#endif // LOG - if (pDib.empty()) - { -#ifdef LOG - FileTools::write_log("imgprc.txt", "exit CImageApplyAutoCrop apply"); -#endif // LOG - return; - } + (void)side; + if (pDib.empty()) return; + if (!m_isCrop && !m_isDesaskew && !m_isFillBlank && m_fixedSize.empty()) return; cv::Mat src = pDib; + cv::Mat thre; cv::Mat dst; - cv::Mat src_resize; - cv::resize(src, src_resize, cv::Size(src.cols / RE, src.rows / RE)); - cv::Mat scale_mat; - cv::Mat thre(src_resize.size(), CV_8UC1); - hg::threshold_Mat(src_resize, thre, m_threshold); - src_resize.release(); + hg::threshold_Mat(src, thre, m_threshold); - if (m_noise > RE) + if (m_noise > 0) { - cv::Mat element = getStructuringElement(cv::MORPH_RECT, cv::Size(m_noise / RE, m_noise / RE)); + cv::Mat element = getStructuringElement(cv::MORPH_RECT, cv::Size(m_noise, m_noise)); cv::morphologyEx(thre, thre, cv::MORPH_OPEN, element); - element.release(); } std::vector hierarchy; std::vector> contours; hg::findContours(thre, contours, hierarchy, cv::RETR_EXTERNAL); - std::vector maxContour = hg::getMaxContour(contours, hierarchy); + m_maxContour = hg::getMaxContour(contours, hierarchy); - for (cv::Point& item : maxContour) - { - item.x = item.x * RE - RE / 2; - item.y = item.y * RE - RE / 2; - } - - if (maxContour.size() == 0) + if (m_maxContour.size() == 0) { thre.release(); #ifdef LOG @@ -81,68 +63,83 @@ void CImageApplyAutoCrop::apply(cv::Mat& pDib, int side) thre.release(); dst.release(); - cv::RotatedRect rect = hg::getBoundingRect(maxContour); + cv::RotatedRect rect = hg::getBoundingRect(m_maxContour); + m_rect = rect; - if (m_isDesaskew) + cv::Rect boudingRect = cv::boundingRect(m_maxContour); + boudingRect.x -= 1; + boudingRect.y -= 1; + boudingRect.width += 2; + boudingRect.height += 2; + + if (m_isDesaskew && rect.angle != 0) { cv::Point2f srcTri[4]; cv::Point2f dstTri[3]; rect.points(srcTri); + for (cv::Point2f& p : srcTri) + { + p.x -= 0.5; + p.y -= 0.5; + } dstTri[0] = cv::Point2f(0, rect.size.height - 1); dstTri[1] = cv::Point2f(0, 0); dstTri[2] = cv::Point2f(rect.size.width - 1, 0); - cv::Mat warp_mat; warp_mat = cv::getAffineTransform(srcTri, dstTri); - cv::warpAffine(src, dst, warp_mat, rect.size); + cv::warpAffine(src, dst, warp_mat, rect.size, cv::INTER_NEAREST); } else - dst = src(rect.boundingRect() & cv::Rect(0, 0, src.cols, src.rows)); + dst = src(boudingRect & cv::Rect(0, 0, src.cols, src.rows)); + + m_maxContour.clear(); + m_maxContour.push_back(cv::Point(-1, dst.rows)); + m_maxContour.push_back(cv::Point(-1, -1)); + m_maxContour.push_back(cv::Point(dst.cols, -1)); + m_maxContour.push_back(cv::Point(dst.cols, dst.rows)); if (m_isFillBlank) { cv::Mat thre_dst; hg::threshold_Mat(dst, thre_dst, m_threshold); - cv::erode(thre_dst, thre_dst, cv::Mat(), cv::Point(-1, -1), m_indent); + + if (m_indent > 0) + { + std::vector rectEdge{ cv::Point(0, 0) ,cv::Point(thre_dst.cols - 1, 0), + cv::Point(thre_dst.cols - 1, thre_dst.rows - 1), cv::Point(0, thre_dst.rows - 1) }; + std::vector> rectEdges{ rectEdge }; + cv::drawContours(thre_dst, rectEdges, 0, cv::Scalar::all(0)); + cv::Mat element = cv::getStructuringElement(cv::MorphShapes::MORPH_RECT, cv::Size(m_indent, m_indent)); + cv::erode(thre_dst, thre_dst, element, cv::Point(-1, -1), 1); + } hierarchy.clear(); contours.clear(); - maxContour.clear(); + m_maxContour.clear(); + hg::findContours(thre_dst, contours, hierarchy, cv::RETR_EXTERNAL); - thre_dst.release(); - - maxContour = hg::getMaxContour(contours, hierarchy); if (m_isConvexHull) - hg::convexHull(maxContour, maxContour); + { + m_maxContour = hg::getMaxContour(contours, hierarchy); + hg::convexHull(m_maxContour, m_maxContour); + contours.clear(); + contours.push_back(m_maxContour); + } - contours.clear(); - contours.resize(2); - contours[0] = maxContour; - contours[1].push_back(cv::Point(0, dst.rows - 1)); - contours[1].push_back(cv::Point(0, 0)); - contours[1].push_back(cv::Point(dst.cols - 1, 0)); - contours[1].push_back(cv::Point(dst.cols - 1, dst.rows - 1)); + contours.push_back(std::vector()); + contours[contours.size() - 1].push_back(cv::Point(-1, dst.rows - 1)); + contours[contours.size() - 1].push_back(cv::Point(-1, -1)); + contours[contours.size() - 1].push_back(cv::Point(dst.cols, -1)); + contours[contours.size() - 1].push_back(cv::Point(dst.cols, dst.rows)); - hg::fillPolys(dst, contours, cv::Scalar(255, 255, 255)); + hg::fillPolys(dst, contours, m_isFillColor ? getBackGroudColor(pDib, rect.size.area()) : cv::Scalar(255, 255, 255)); } pDib.release(); - if ((m_isCrop && side == 0) || (side == 1 && m_fixedSize.width * m_fixedSize.height == 0)) - { + if (/*(m_isCrop && side == 0) || (side == 1 && m_fixedSize.width * m_fixedSize.height == 0)*/ m_isCrop) pDib = dst.clone(); - dst.release(); - } else { - if (m_isCrop && side == 1 && !m_fixedSize.empty()) - if (std::abs(m_fixedSize.width - dst.cols) > 50 || std::abs(m_fixedSize.height - dst.rows) > 50) - { - pDib = dst.clone(); -#ifdef LOG - FileTools::write_log("imgprc.txt", "exit CImageApplyAutoCrop apply"); -#endif // LOG - return; - } pDib = cv::Mat(m_fixedSize, dst.type(), m_isFillBlank ? cv::Scalar(255, 255, 255) : cv::Scalar(0, 0, 0)); cv::Rect roi; @@ -151,19 +148,10 @@ void CImageApplyAutoCrop::apply(cv::Mat& pDib, int side) roi.y = dst.rows > pDib.rows ? (dst.rows - pDib.rows) / 2 : 0; roi.height = cv::min(pDib.rows, dst.rows); cv::Rect rect((pDib.cols - roi.width) / 2, (pDib.rows - roi.height) / 2, roi.width, roi.height); -#if 0 - std::string outrectinfo ="copy to rect x: "+std::to_string(rect.x) + "y: "+std::to_string(rect.y) + "width: "+std::to_string(rect.width) + "height: "+std::to_string(rect.height); - std::string outroiinfo = "roi x: " + std::to_string(roi.x) + "y: " + std::to_string(roi.y) + "width: " + std::to_string(roi.width) + "height: " + std::to_string(roi.height); - std::string dstsize = "dst size: width:" + std::to_string(dst.cols) + "height: " + std::to_string(dst.rows); - std::string pDibszie= "pDib size: width: " + std::to_string(pDib.cols) + "height: " + std::to_string(pDib.rows); - FileTools::write_log("imgprc.txt", dstsize); - FileTools::write_log("imgprc.txt", pDibszie); - FileTools::write_log("imgprc.txt", outrectinfo); - FileTools::write_log("imgprc.txt", outroiinfo); -#endif // LOG - dst(roi).copyTo(pDib(rect)); - dst.release(); + for (cv::Point& p : m_maxContour) + p += roi.tl(); + dst(roi).copyTo(pDib(rect)); } #ifdef LOG FileTools::write_log("imgprc.txt", "exit CImageApplyAutoCrop apply8"); @@ -172,13 +160,14 @@ void CImageApplyAutoCrop::apply(cv::Mat& pDib, int side) void CImageApplyAutoCrop::apply(std::vector& mats, bool isTwoSide) { + m_rects.clear(); if (mats.empty()) return; if (!mats[0].empty()) { apply(mats[0], 0); + m_rects.push_back(m_rect); //cv::imwrite("1.bmp", mats[0]); } - if (isTwoSide && mats.size() > 1) { cv::Size dSize = m_fixedSize; @@ -187,11 +176,66 @@ void CImageApplyAutoCrop::apply(std::vector& mats, bool isTwoSide) if (!mats[1].empty()) { apply(mats[1], 1); + m_rects.push_back(m_rect); //cv::imwrite("1.bmp", mats[0]); } - if (!mats[0].empty()) m_fixedSize = dSize; } -} \ No newline at end of file +} + +cv::Scalar CImageApplyAutoCrop::getBackGroudColor(const cv::Mat& image, int total) +{ + if (image.channels() == 3) + { + cv::Mat image_bgr[3]; + cv::split(image, image_bgr); + + uchar bgr[3]; + for (size_t i = 0; i < 3; i++) + bgr[i] = getBackGroudChannelMean(image_bgr[i], total); + return cv::Scalar(bgr[0], bgr[1], bgr[2]); + } + else + return cv::Scalar::all(getBackGroudChannelMean(image, total)); +} + +uchar CImageApplyAutoCrop::getBackGroudChannelMean(const cv::Mat& gray, int total) +{ + cv::Mat image_clone; + cv::resize(gray, image_clone, cv::Size(), 0.25, 0.25); + + int threnshold = total / 32; + int channels[] = { 0 }; + int nHistSize[] = { 256 }; + float range[] = { 0, 256 }; + const float* fHistRanges[] = { range }; + cv::Mat hist; + cv::calcHist(&image_clone, 1, channels, cv::Mat(), hist, 1, nHistSize, fHistRanges, true, false); + + int hist_array[256]; + for (int i = 0; i < 256; i++) + hist_array[i] = hist.at(i, 0); + + int length = 1; + const int length_max = 255 - m_threshold; + while (length < length_max) + { + for (size_t i = m_threshold + 1; i < 256 - length; i++) + { + int count = 0; + uint pixSum = 0; + for (size_t j = 0; j < length; j++) + { + count += hist_array[j + i]; + pixSum += hist_array[j + i] * (i + j); + } + + if (count >= threnshold) + return pixSum / count; + } + length++; + } + return 255; +} diff --git a/huagao/ImageProcess/ImageApplyAutoCrop.h b/huagao/ImageProcess/ImageApplyAutoCrop.h index 81bed3f6..d51e56a4 100644 --- a/huagao/ImageProcess/ImageApplyAutoCrop.h +++ b/huagao/ImageProcess/ImageApplyAutoCrop.h @@ -1,3 +1,21 @@ +/* + * ==================================================== + + * 功能:自动裁剪、纠偏、除黑底 + * 作者:刘丁维 + * 生成时间:2020/4/21 + * 最近修改时间:2020/4/21 v1.0 + 2020/7/22 v1.1 增加获取图像有效区域轮廓的接口maxContour(用于配合一体机的“跳过空白页”算法,PC端暂时无需使用) + 2020/10/16 v1.2 修复自动裁剪尺寸精度丢失的BUG;提高除黑底缩进精度。 + 2020/10/28 v1.2.1 修复凹凸多边形填充背景的逻辑BUG。 + 2020/10/28 v1.2.2 修复图像处理必定会缩小尺寸的BUG。 + 2020/10/29 v1.2.3 避免无谓的纠偏(0°纠偏) + 2020/11/30 v1.3.0 增加功能,可识别文稿颜色进行填充黑底。 + * 版本号:v1.3.0 + + * ==================================================== + */ + #ifndef IMAGE_APPLY_AUTO_CROP_H #define IMAGE_APPLY_AUTO_CROP_H @@ -8,7 +26,18 @@ class CImageApplyAutoCrop : public CImageApply public: CImageApplyAutoCrop(); - CImageApplyAutoCrop(bool isCrop, bool isDesaskew, bool isFillBlank, const cv::Size& fixedSize, bool isConvex = true, double threshold = 40, int noise = 40, int indent = 5); + /* + * isCrop [in]:自动幅面裁剪使能,true自动裁剪,false为固定裁剪 + * isDesaskew [in]:自动纠偏使能,true自动纠偏,false为不纠偏 + * isFillBlank [in]:黑底填充使能,true为填充,false为不填充 + * fixedSize [in]:固定幅面尺寸,当isCrop为false时生效,结果尺寸按fixedSize大小输出,单位像素 + * isConvex [in]:黑底填充时的填充方式,true为凸多边形填充,false为凹多边形填充,默认true + * isFillColor [in]:黑底填充时采用自适应色彩填充,false为白色填充,true为自适应文稿底色填充,默认false + * threshold [in]:二值化阈值,取值范围(0, 255),默认40 + * noise [in]:除噪像素,能够消除noise宽度的背景竖条纹干扰,默认40 + * indent [in]:轮廓缩进,裁剪、纠偏或者黑底填充时,对探索到的纸张轮廓进行缩进indent像素,默认5 + */ + CImageApplyAutoCrop(bool isCrop, bool isDesaskew, bool isFillBlank, const cv::Size& fixedSize, bool isConvex = true, double threshold = 40, int noise = 2, int indent = 5); virtual ~CImageApplyAutoCrop(); @@ -26,6 +55,10 @@ public: double threshold() { return m_threshold; } + const cv::RotatedRect& rotatedROI() { return m_rect; } + + const std::vector& rotatedROIs() { return m_rects; } + int noise() { return m_noise; } int indent() { return m_indent; } @@ -50,19 +83,25 @@ public: void setFixedSize(cv::Size size) { m_fixedSize = size; } +private: + cv::Scalar getBackGroudColor(const cv::Mat& image, int total); + + uchar getBackGroudChannelMean(const cv::Mat& gray, int total); + private: bool m_isCrop; bool m_isDesaskew; bool m_isFillBlank; bool m_isConvexHull; + bool m_isFillColor; double m_threshold; int m_noise; int m_indent; cv::Size m_fixedSize; + cv::RotatedRect m_rect; std::vector m_maxContour; + std::vector m_rects; }; #endif // !IMAGE_APPLY_AUTO_CROP_H - -