|
|
|
|
|
#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();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|