#include "remove_border.h" #include #include "../common.h" #include "../image_utilities.h" using namespace std; /** * 根据边框内边缘获取去除边框所用掩膜 * * args: * imgshape: 图片宽高信息 * shapeType: 印章形状信息 * filter_border: 边框内边缘 * returns: * filterMask: 除边框外的印章掩码 */ cv::Mat get_filterBorderMask(const cv::Size& imgshape, const string& shapeType, const cv::RotatedRect& filter_border) { // 创建全黑图像作为基础 cv::Mat empty = cv::Mat::zeros(imgshape, CV_8UC1); if (shapeType == "ellipse" || shapeType == "circle") { // 绘制填充的椭圆 cv::ellipse(empty, filter_border, cv::Scalar(255), -1); } else { vector filter_border_box = mrect2box(filter_border); vector> contours; contours.push_back(filter_border_box); cv::drawContours(empty, contours, -1, cv::Scalar(255), -1); } // 创建掩码:绘制区域为255,其他区域为0 cv::Mat filterMask; cv::compare(empty, 255, filterMask, cv::CMP_EQ); return filterMask; } /** * 枚举offsets,找到最合适的边界 * * args: * img: 图 * shapeType: 形状类型 * mrect: 一个最小包围矩形 * offsets: 平移变换组合 * inverse: True以背景点数量最大时为最佳,False以前景点数量最大时为最佳 * bg_val: 背景值 * returns: * best_mrect: 最佳边界,是一个最小包围矩形 */ cv::RotatedRect _find_bestfit(const cv::Mat& img, const string& shapeType, const cv::RotatedRect& mrect, const vector& offsets, bool inverse, int bg_val) { // 如果offsets为空,返回原始mrect if (offsets.empty()) { return mrect; } // 提取mrect的参数 cv::Point2f center = mrect.center; cv::Size2f wh = mrect.size; float angle = mrect.angle; // 初始化最佳分数和最佳mrect float best_score = 0; cv::RotatedRect best_mrect = mrect; const int color = 255; // 遍历offsets中的每个变换参数 for (const auto& offset : offsets) { try { float tx = offset.tx; float ty = offset.ty; float ws = offset.ws; float hs = offset.hs; float ta = offset.ta; // 创建全黑图像 cv::Mat empty = cv::Mat::zeros(img.size(), CV_8UC1); // 计算新的mrect cv::Point2f new_center(center.x + tx, center.y + ty); // 确保宽高为正数 float new_width = wh.width + ws; float new_height = wh.height + hs; if (new_width <= 0 || new_height <= 0) { continue; } cv::Size2f new_wh(new_width, new_height); float new_angle = angle + ta; cv::RotatedRect new_mrect(new_center, new_wh, new_angle); // 绘制轮廓并计算内部面积 float innerArea = 0; const int BS = 1; // 线宽,与Python版本保持一致 if (shapeType == "ellipse" || shapeType == "circle") { // 使用线宽 1 绘制边界线,与Python版本保持一致 cv::ellipse(empty, new_mrect, color, BS); innerArea = (new_wh.width * new_wh.height / 4) * M_PI; } else { vector new_mrectbox = mrect2box(new_mrect); vector> contours; contours.push_back(new_mrectbox); cv::drawContours(empty, contours, -1, color, BS); innerArea = new_wh.width * new_wh.height; } // 创建轮廓掩码 cv::Mat cntMask;//得到黑底白色轮廓(线宽1),未进行内部填充 cv::compare(empty, color, cntMask, cv::CMP_EQ); // 原始逐像素遍历代码(已注释,保留用于参考) // int cntArea = 0; // for (int i = 0; i < img.rows; ++i) { // for (int j = 0; j < img.cols; ++j) { // if (cntMask.at(i, j)) { // if (!inverse) { // if (img.at(i, j) != bg_val) { // cntArea++; // } // } else { // if (img.at(i, j) == bg_val) { // cntArea++; // } // } // } // } // } // 使用OpenCV向量化操作计算轮廓区域的像素数(性能优化) cv::Mat condMask;//提取二值化图img的背景像素,白底黑字(印章边框+文字) if (!inverse) { cv::compare(img, bg_val, condMask, cv::CMP_NE);//黑底图走这一句 } else { cv::compare(img, bg_val, condMask, cv::CMP_EQ);//白底图走这一句,把二值图的白色背景提取出来。 } cv::Mat resultMask; cv::bitwise_and(cntMask, condMask, resultMask);//对黑底轮廓图和白底二值图进行按位取与运算,得到重合都是白色的掩膜。 int cntArea = cv::countNonZero(resultMask);//统计掩膜中的非零元素即255,因为初始化阶段已把最佳边框赋值为最小外接矩形,因此不存在会有最佳边框在印章外的情况。 //通过offset数组设定了一个范围(不太可能无限缩小到内部文字圈)对最小外接矩形进行了不断缩减试探寻求最佳边框,通过找寻背景像素(白)最多的点得到最佳边框。因为文字区域和边框区域为黑,背景为白,在规定的范围内最多白背景的地方为最佳边框。 // 更新最佳分数和最佳mrect float score = cntArea; if (best_score < score) { best_score = score; best_mrect = new_mrect; } } catch (const exception& e) { cout << "处理偏移时出错: " << e.what() << endl; continue; } catch (...) { cout << "处理偏移时出错: 未知异常" << endl; continue; } } return best_mrect; } /** * 获取去除边框所用掩膜 * * args: * img: 图 * shapeinfo: 形状信息 * bg_val: 图片背景色 * returns: * filterMask: 用来去掉边框的掩膜mask * new_shapeinfo: 新形状信息 * inner_border: 边框内边缘 */ cv::Mat _getFilterMask(const cv::Mat& img, const ShapeInfo& shapeinfo, cv::RotatedRect& inner_border, int bg_val) { try { // 确保输入是灰度图 if (img.empty()) { cout << "错误: 输入图像为空" << endl; return cv::Mat(); } if (img.channels() != 1) { cout << "错误: 输入必须是灰度图" << endl; return cv::Mat(); } // 获取形状类型 string shapeType = shapeinfo.type; if (shapeType.empty()) { shapeType = "unknown"; } // 对图像进行阈值处理,生成二值图像 cv::Mat binary; // 使用THRESH_BINARY,与Python版本保持一致 cv::threshold(img, binary, 200, 255, cv::THRESH_BINARY | cv::THRESH_OTSU); // 获取最佳边框 cv::RotatedRect best_border = shapeinfo.minAreaRect; // 计算缩进值列表 const int BIG_SIZE = 365; vector indents; for (int indent = -18; indent > -44; indent -= 2) { indents.push_back(indent); } // 计算k值并扩展indents float k = min(best_border.size.width, best_border.size.height) / static_cast(BIG_SIZE); if (k > 1) { int num_extra = static_cast(ceil(k * 5)); for (int i = 0; i < num_extra; ++i) { indents.push_back(-44 - 4 * i); } } // 生成offsets_1 vector offsets_1; for (int indent : indents) { offsets_1.emplace_back(0, 0, indent, indent, 0); } // 获取边框内边缘 cv::RotatedRect filter_border = _find_bestfit(binary, shapeType, best_border, offsets_1, true, bg_val); // 获取去除边框所需要的掩膜 cv::Mat filterMask = get_filterBorderMask(img.size(), shapeType, filter_border); // 设置inner_border inner_border = filter_border; return filterMask; } catch (const exception& e) { cout << "_getFilterMask出错: " << e.what() << endl; return cv::Mat(); } catch (...) { cout << "_getFilterMask出错: 未知异常" << endl; return cv::Mat(); } } /** * 去除边框 * * args: * img: 图片 * shapeinfo: 形状信息 * bg_val: 图片的背景颜色,默认是白底黑字, 如果是黑底白字的二值图,请设置bg_val为0 * returns: * img1: 去掉边框后的图片 * mask: 去掉边框所用掩膜 * new_shapeinfo: 拟合得更好的形状 * inner_border: 边框内边缘 */ cv::Mat remove_border(const cv::Mat& img, const ShapeInfo& shapeinfo, cv::Mat& mask, ShapeInfo& new_shapeinfo, cv::RotatedRect& inner_border, int bg_val) { try { // 确保输入是灰度图 if (img.empty()) { cout << "错误: 输入图像为空" << endl; return cv::Mat(); } if (img.channels() != 1) { cout << "错误: 输入必须是灰度图" << endl; return cv::Mat(); } // 调用_getFilterMask获取掩膜、新形状信息和边框内边缘 mask = _getFilterMask(img, shapeinfo, inner_border, bg_val); // 检查mask是否为空 if (mask.empty()) { cout << "警告: 生成的mask为空,返回原始图像" << endl; // 返回原始图像的副本 new_shapeinfo = shapeinfo; return img.clone(); } // 确保mask与img尺寸相同 if (mask.size() != img.size()) { cout << "警告: mask尺寸与图像尺寸不同,返回原始图像" << endl; new_shapeinfo = shapeinfo; return img.clone(); } // 复制图片 cv::Mat img1 = img.clone(); // 应用掩膜:将非mask区域设置为bg_val // 原始逐像素遍历代码(已注释,保留用于参考) // for (int i = 0; i < img1.rows; ++i) { // for (int j = 0; j < img1.cols; ++j) { // if (!mask.at(i, j)) { // img1.at(i, j) = static_cast(bg_val); // } // } // } // 使用OpenCV向量化操作(性能优化) cv::Mat invertedMask; cv::bitwise_not(mask, invertedMask); img1.setTo(bg_val, invertedMask); // 使用计算出的inner_border更新new_shapeinfo new_shapeinfo = shapeinfo; new_shapeinfo.minAreaRect = inner_border; return img1; } catch (const exception& e) { cout << "remove_border出错: " << e.what() << endl; // 发生异常时返回原始图像的副本 new_shapeinfo = shapeinfo; return img.clone(); } catch (...) { cout << "remove_border出错: 未知异常" << endl; // 发生未知异常时返回原始图像的副本 new_shapeinfo = shapeinfo; return img.clone(); } }