You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

315 lines
12 KiB
C++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include "remove_border.h"
#include <math.h>
#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<cv::Point> filter_border_box = mrect2box(filter_border);
vector<vector<cv::Point>> 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<Offset>& 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<cv::Point> new_mrectbox = mrect2box(new_mrect);
vector<vector<cv::Point>> 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<uchar>(i, j)) {
// if (!inverse) {
// if (img.at<uchar>(i, j) != bg_val) {
// cntArea++;
// }
// } else {
// if (img.at<uchar>(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<int> 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<float>(BIG_SIZE);
if (k > 1) {
int num_extra = static_cast<int>(ceil(k * 5));
for (int i = 0; i < num_extra; ++i) {
indents.push_back(-44 - 4 * i);
}
}
// 生成offsets_1
vector<Offset> 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<uchar>(i, j)) {
// img1.at<uchar>(i, j) = static_cast<uchar>(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();
}
}