Compare commits

..

2 Commits

25
.gitignore vendored

@ -0,0 +1,25 @@
# IDE 配置
.vscode/
# 编译产物
build/
*.exe
*.dll
*.obj
*.a
# CMake 缓存
CMakeCache.txt
CMakeFiles/
cmake_install.cmake
*.ninja
.ninja_deps
.ninja_log
# 测试输出
test_image/
# 系统文件
.DS_Store
Thumbs.db
desktop.ini

@ -0,0 +1,43 @@
cmake_minimum_required(VERSION 3.10)
project(SealProcessing)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})
include_directories(${CMAKE_SOURCE_DIR})
include_directories(${CMAKE_SOURCE_DIR}/extract_seal_main)
include_directories(${CMAKE_SOURCE_DIR}/remove_border)
set(COMMON_SOURCES
common.cpp
image_utilities.cpp
)
set(EXTRACT_SEAL_SOURCES
extract_seal_main/hsv_color.cpp
extract_seal_main/image_morphology.cpp
extract_seal_main/adjust_image.cpp
extract_seal_main/color_filter.cpp
extract_seal_main/extract_seal_main.cpp
)
set(REMOVE_BORDER_SOURCES
remove_border/remove_border.cpp
remove_border/remove_border_main.cpp
)
add_library(seal_lib STATIC
${COMMON_SOURCES}
${EXTRACT_SEAL_SOURCES}
${REMOVE_BORDER_SOURCES}
)
target_link_libraries(seal_lib ${OpenCV_LIBS})
add_executable(test_extract_seal extract_seal_main/test_extract_seal.cpp)
target_link_libraries(test_extract_seal seal_lib ${OpenCV_LIBS})
add_executable(test_remove_border remove_border/test_remove_border.cpp)
target_link_libraries(test_remove_border seal_lib ${OpenCV_LIBS})

@ -0,0 +1,219 @@
# Seal Processing - 印章处理模块
印章检测与处理的 C++ 模块,包含印章提取和边框移除功能。
## 环境要求
- **CMake** >= 3.10
- **MinGW-w64** (通过 MSYS2 安装)
- **OpenCV** >= 4.0 (通过 MSYS2 安装: `mingw-w64-x86_64-opencv`)
### 安装依赖MSYS2
```bash
pacman -S mingw-w64-x86_64-toolchain
pacman -S mingw-w64-x86_64-cmake
pacman -S mingw-w64-x86_64-opencv
```
---
## 项目结构
```
Final project/
├── image/ # 测试图片目录
│ ├── 1.png
│ └── 2.png
└── c/ # C++ 项目目录
├── CMakeLists.txt # CMake 配置文件
├── build.bat # 一键编译脚本
├── README.md # 说明文档
├── common.cpp # 公共函数
├── common.h
├── image_utilities.cpp # 图像工具
├── image_utilities.h
├── extract_seal_main/ # 印章提取模块
│ ├── extract_seal_main.cpp
│ ├── extract_seal_main.h
│ ├── adjust_image.cpp
│ ├── adjust_image.h
│ ├── color_filter.cpp
│ ├── color_filter.h
│ ├── hsv_color.cpp
│ ├── hsv_color.h
│ ├── image_morphology.cpp
│ ├── image_morphology.h
│ └── test_extract_seal.cpp
└── remove_border/ # 边框移除模块
├── remove_border.cpp
├── remove_border.h
├── remove_border_main.cpp
├── remove_border_main.h
└── test_remove_border.cpp
```
**编译后生成的目录(不会被 Git 追踪):**
- `c/build/` - 编译输出目录,包含可执行文件
- `c/extract_seal_main/test_image/` - 印章提取测试输出
- `c/remove_border/test_image/` - 边框移除测试输出
---
## 编译
### 方法1使用 build.bat在 cmd.exe 中运行)
**命令提示符 (cmd)** 中运行:
```cmd
cd "c:\Users\用户名\Desktop\Final project\c"
build.bat
```
或在 **PowerShell** 中运行:
```powershell
cd "c:\Users\用户名\Desktop\Final project\c"
cmd /c .\build.bat
```
### 方法2手动 CMake 编译(推荐)
```powershell
cd "c:\Users\用户名\Desktop\Final project\c"
mkdir build -ErrorAction SilentlyContinue
cd build
cmake .. -G "MinGW Makefiles" -DOpenCV_DIR="C:/msys64/mingw64/lib/cmake/opencv4"
cmake --build .
```
**注意:** 请将 `用户名` 替换为您的 Windows 用户名。
---
## 运行 test_extract_seal印章提取
### 功能
从图片中提取印章,输出裁剪后的印章图像。
### 运行命令
```powershell
cd "c:\Users\用户名\Desktop\Final project\c\build"
.\test_extract_seal.exe
```
### 程序会自动处理
**输入:**
- 从 `build/` 目录运行时,程序会扫描 `../../image/` 目录
- 即 `Final project/image/` 目录下的图片
- 支持格式:`.png`, `.jpg`, `.jpeg`, `.bmp`
**输出:**
- 自动保存在 `../extract_seal_main/test_image/` 目录
- 即 `Final project/c/extract_seal_main/test_image/` 目录
- 每个印章生成 3 个文件:
- `seal_{图片名}_{序号}_cropped.png` - 原始裁剪区域
- `seal_{图片名}_{序号}_ex_img.png` - 提取后的印章图像
- `seal_{图片名}_{序号}_adj_img.png` - 校正后的印章图像
### 关于检测数据
程序内部已预设了 `1.png``2.png` 的印章检测数据det。如需使用其他图片请参考"测试说明"章节。
---
## 运行 test_remove_border边框移除
### 功能
对提取的印章进行边框移除处理。
### 运行命令
```powershell
cd "c:\Users\用户名\Desktop\Final project\c\build"
.\test_remove_border.exe
```
---
## 测试说明
### 关于印章检测数据det
目前 C++ 程序**不包含印章检测功能**(即原 Python 项目 seal_ocr 中的 detect 部分)。
程序中使用的印章边框数据det是通过运行**原 Python 项目**获得后,在 C++ 代码中进行了硬编码设定。
**如需使用自己的测试图片:**
1. 使用原 Python 项目 seal_ocr 运行图片获取检测数据det
2. 修改 C++ 测试文件中硬编码的 det 数据
3. 重新编译并运行程序
### 各测试程序的差异
| 测试程序 | 处理范围 | 检测数据 | 说明 |
|---------|---------|----------|------|
| `test_extract_seal.cpp` | 多张图片 | 预设 1.png, 2.png | 自动扫描 `image/` 目录,但仅对预设的图片有检测数据 |
| `test_remove_border.cpp` | 单张图片 | 预设 1.png | 仅处理 1.png需要手动修改代码中的图片路径和 det 数据 |
**注意:** 两个测试程序都预设了检测数据det如需处理其他图片需要先使用原 Python 项目获取 det 数据,然后修改代码。
---
## 快速开始
```powershell
# 1. 进入项目目录
cd "c:\Users\用户名\Desktop\Final project\c"
# 2. 编译(使用 CMake
mkdir build -ErrorAction SilentlyContinue
cd build
cmake .. -G "MinGW Makefiles" -DOpenCV_DIR="C:/msys64/mingw64/lib/cmake/opencv4"
cmake --build .
# 3. 运行印章提取测试
.\test_extract_seal.exe
# 4. 运行边框移除测试
.\test_remove_border.exe
```
**注意:** 请将 `用户名` 替换为您的 Windows 用户名。
---
## 常见问题
### 1. `cd` 命令报错:找不到路径
**原因:** 路径中有空格 `Final project`
**解决:** 用引号包围路径
```powershell
cd "c:\Users\用户名\Desktop\Final project\c"
```
### 2. CMake 找不到 OpenCV
确保指定正确的 OpenCV 路径:
```powershell
cmake .. -G "MinGW Makefiles" -DOpenCV_DIR="C:/msys64/mingw64/lib/cmake/opencv4"
```
### 3. 编译失败
检查:
- MinGW-w64 是否已正确安装
- 环境变量 PATH 是否包含 `C:\msys64\mingw64\bin`
### 4. 运行时找不到 DLL
`C:\msys64\mingw64\bin` 添加到系统 PATH或复制相关 DLL 到可执行文件目录。

@ -0,0 +1,76 @@
@echo off
chcp 65001>nul
setlocal
set "PROJECT_DIR=%~dp0"
set "BUILD_DIR=%PROJECT_DIR%build"
echo ========================================
echo Building SealProcessing project with CMake
echo ========================================
echo.
if not exist "%BUILD_DIR%" mkdir "%BUILD_DIR%"
cd /d "%BUILD_DIR%"
echo [1/3] Configuring CMake...
rem 尝试多个可能的 OpenCV 路径
set "OPENCV_PATHS=C:/msys64/mingw64/lib/cmake/opencv4;C:/mingw64/lib/cmake/opencv4;C:/opencv/build/x64/mingw/lib"
set "CMAKE_COMMAND=cmake .. -G "MinGW Makefiles""
rem 优先尝试我们指定的路径,忽略环境变量
set "FOUND_OPENCV=0"
echo Trying multiple OpenCV paths...
for %%p in (%OPENCV_PATHS%) do (
if exist "%%p" (
echo Found OpenCV at: %%p
set "CMAKE_COMMAND=%CMAKE_COMMAND% -DOpenCV_DIR="%%p""
set "FOUND_OPENCV=1"
goto :cmake_config
)
)
rem 如果没有找到,尝试使用环境变量
if %FOUND_OPENCV%==0 (
if defined OpenCV_DIR (
echo Warning: Using OpenCV_DIR from environment variable: %OpenCV_DIR%
echo This might not be compatible with MinGW.
set "CMAKE_COMMAND=%CMAKE_COMMAND% -DOpenCV_DIR="%OpenCV_DIR%""
) else (
echo Warning: No OpenCV directory found. CMake will try to find it automatically.
)
)
:cmake_config
%CMAKE_COMMAND%
if errorlevel 1 (
echo.
echo CMake configuration failed!
pause
exit /b 1
)
echo.
echo [2/3] Building project...
cmake --build .
if errorlevel 1 (
echo.
echo Build failed!
pause
exit /b 1
)
echo.
echo ========================================
echo Build successful!
echo ========================================
echo Executables are in: %BUILD_DIR%
echo.
echo Available targets:
echo - test_extract_seal.exe
echo - test_remove_border.exe
echo.
pause

@ -0,0 +1,17 @@
#include "common.h"
ShapeInfo::ShapeInfo()
: type(""), minAreaRect(cv::Point2f(0, 0), cv::Size2f(0, 0), 0), implement(false) {
ignores = {"implement", "needInits", "ignores"};
needInits = {};
}
ShapeInfo::ShapeInfo(const string& t, const cv::RotatedRect& rect,
const vector<cv::Point>& pts,
const vector<string>& ign,
const vector<string>& ni, bool imp)
: type(t), minAreaRect(rect), points(pts), ignores(ign), needInits(ni), implement(imp) {}
ShapeInfo ShapeInfo::copy() const {
return ShapeInfo(type, minAreaRect, points, ignores, needInits, implement);
}

@ -0,0 +1,92 @@
#ifndef COMMON_H
#define COMMON_H
#include <opencv2/core.hpp>
#include <string>
#include <vector>
#include <tuple>
using namespace std;
struct ShapeInfo {
string type;
cv::RotatedRect minAreaRect;
vector<cv::Point> points;
vector<string> ignores;
vector<string> needInits;
bool implement;
ShapeInfo();
ShapeInfo(const string& t, const cv::RotatedRect& rect,
const vector<cv::Point>& pts,
const vector<string>& ign,
const vector<string>& ni, bool imp);
ShapeInfo copy() const;
};
enum SHAPETYPE {
CIRCULAR = 0,
ELLIPTICAL = 1,
SQUARE = 2,
DIAMOND = 3,
TRIANGLE = 4
};
inline const char* const SHAPETYPE_NAMES[] = {"circle", "ellipse", "square", "diamond", "triangle"};
struct SealInfo {
int x1;
int y1;
int x2;
int y2;
double conf;
int shape;
cv::Mat cropped_img;
cv::Mat extracted_img;
std::string color_name;
ShapeInfo shape_info;
int angle;
cv::Mat adj_img;
cv::Mat rm_bd_img;
std::vector<cv::Mat> txt_img;
std::vector<cv::Mat> rec_img;
};
namespace seal_data {
class SealDataManager {
public:
static SealDataManager& getInstance() {
static SealDataManager instance;
return instance;
}
void setSealSet(const std::vector<SealInfo>& seal_set) {
this->seal_set = seal_set;
}
std::vector<SealInfo>& getSealSet() {
return seal_set;
}
bool hasSealSet() const {
return !seal_set.empty();
}
void clearSealSet() {
seal_set.clear();
}
private:
SealDataManager() {}
~SealDataManager() {}
SealDataManager(const SealDataManager&) = delete;
SealDataManager& operator=(const SealDataManager&) = delete;
std::vector<SealInfo> seal_set;
};
} // namespace seal_data
#endif

@ -0,0 +1,142 @@
#include "adjust_image.h"
#include "image_morphology.h"
#include <opencv2/imgproc.hpp>
#include <cmath>
#include <algorithm>
using namespace std;
static tuple<cv::Mat, int, ShapeInfo> adjust_image_impl(const cv::Mat& img, const ShapeInfo& shapeinfo, int rotate_range) {
int final_angle = 0;
cv::Mat final_img;
int img_h = img.rows;
int img_w = img.cols;
string shapetype = shapeinfo.type;
cv::RotatedRect minAreaRect = shapeinfo.minAreaRect;
cv::Point2f center = minAreaRect.center;
cv::Size2f wh = minAreaRect.size;
double wh_ratio = max(wh.width / wh.height, wh.height / wh.width);
cv::Mat gray;
cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY);
cv::Mat binary;
cv::threshold(gray, binary, 200, 255, cv::THRESH_BINARY_INV | cv::THRESH_OTSU);
if (shapetype == "ellipse" || shapetype == "circle") {
binary = wipe_star(binary, minAreaRect);
}
binary = wipe_noise(binary);
ShapeInfo new_shapeinfo = shapeinfo.copy();
cv::Mat fck_gray = wipe_noise_gray(gray);
if (shapetype == "ellipse" || shapetype == "circle") {
fck_gray = my_dilate(fck_gray, cv::Size(16, 16), 1, WHITE_BG);
}
double min_cal = 1e10;
cv::Mat first_M;
int first_angle = 0;
for (int angle = -rotate_range; angle < rotate_range; ++angle) {
cv::Mat M = cv::getRotationMatrix2D(center, angle, 1.0);
cv::Mat fck_gray1;
cv::warpAffine(fck_gray, fck_gray1, M, cv::Size(img_w, img_h), cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar(255));
int center_x = static_cast<int>(center.x);
center_x = min(max(1, center_x), img_w - 1);
int left_w = center_x;
int right_start = (img_w % 2 == 0) ? center_x : (center_x + 1);
int right_w = img_w - right_start;
int min_w = min(left_w, right_w);
cv::Mat left, right;
if (min_w > 0) {
left = fck_gray1(cv::Rect(0, 0, min_w, img_h)).clone();
cv::Mat right_part = fck_gray1(cv::Rect(right_start, 0, min_w, img_h));
cv::flip(right_part, right, 1);
} else {
left = cv::Mat::zeros(img_h, 1, fck_gray1.type());
right = cv::Mat::zeros(img_h, 1, fck_gray1.type());
}
cv::Mat diff;
cv::absdiff(left, right, diff);
int cal = cv::countNonZero(diff);
if (cal < min_cal) {
min_cal = cal;
first_M = M.clone();
first_angle = angle;
}
}
cv::Mat first_gray;
cv::warpAffine(gray, first_gray, first_M, cv::Size(img_w, img_h), cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar(255));
final_angle = first_angle;
if (wh_ratio < 1.1 && abs(first_angle) > 45) {
cv::Mat binary2;
cv::threshold(first_gray, binary2, 200, 255, cv::THRESH_BINARY_INV | cv::THRESH_OTSU);
int x_range[2] = {static_cast<int>(center.x - wh.width / 2 * 0.4),
static_cast<int>(center.x + wh.width / 2 * 0.4)};
int y_range[2] = {static_cast<int>(center.y - wh.height / 2 * 0.85),
static_cast<int>(center.y + wh.height / 2 * 0.85)};
int tx1 = max(0, x_range[0]);
int ty1 = max(0, y_range[0]);
int tx2 = min(static_cast<int>(first_gray.cols), x_range[1]);
int ty2 = min(static_cast<int>(first_gray.rows), y_range[1]);
if (tx1 < tx2 && ty1 < ty2) {
int range_w = tx2 - tx1;
int range_h = ty2 - ty1;
if (range_w > 0 && range_h > 0) {
cv::Mat hahaha = binary2(cv::Rect(tx1, ty1, range_w, range_h)).clone();
int up_h = max(1, static_cast<int>(hahaha.rows / 4.0));
int down_y = static_cast<int>(hahaha.rows / 4.0 * 3);
down_y = min(max(down_y, 0), hahaha.rows - up_h);
if (up_h > 0 && down_y >= 0 && down_y + up_h <= hahaha.rows) {
cv::Mat up = hahaha(cv::Rect(0, 0, hahaha.cols, up_h)).clone();
cv::Mat down = hahaha(cv::Rect(0, down_y, hahaha.cols, up_h)).clone();
int up_cnt = cv::countNonZero(up);
int down_cnt = cv::countNonZero(down);
double up_div_down = max(up_cnt, down_cnt) / max(1, min(up_cnt, down_cnt));
if (up_div_down > 2) {
if (up_cnt < down_cnt) {
final_angle = first_angle + 180;
}
}
}
}
}
}
cv::Mat final_M = cv::getRotationMatrix2D(center, final_angle, 1);
cv::warpAffine(img, final_img, final_M, cv::Size(img_w, img_h), cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar(255, 255, 255));
new_shapeinfo.minAreaRect = cv::RotatedRect(new_shapeinfo.minAreaRect.center,
new_shapeinfo.minAreaRect.size, 0);
return make_tuple(final_img, final_angle, new_shapeinfo);
}
tuple<cv::Mat, int, ShapeInfo> adjust_img(const cv::Mat& img, const ShapeInfo& shapeinfo) {
string shapetype = shapeinfo.type;
if (shapetype == "ellipse" || shapetype == "circle") {
return adjust_image_impl(img, shapeinfo, 90);
} else if (shapetype == "square") {
return adjust_image_impl(img, shapeinfo, 30);
} else {
return make_tuple(img.clone(), 0, shapeinfo.copy());
}
}

@ -0,0 +1,14 @@
#ifndef ADJUST_IMAGE_H
#define ADJUST_IMAGE_H
#include <opencv2/core.hpp>
#include <vector>
#include <string>
#include <tuple>
#include "../common.h"
using namespace std;
tuple<cv::Mat, int, ShapeInfo> adjust_img(const cv::Mat& img, const ShapeInfo& shapeinfo);
#endif

@ -0,0 +1,111 @@
#include "color_filter.h"
#include "hsv_color.h"
#include "../image_utilities.h"
#include <opencv2/imgproc.hpp>
#include <iostream>
#include <cmath>
#include <stdexcept>
using namespace std;
pair<cv::Mat, cv::Mat> filter_color(const cv::Mat& img, const vector<vector<vector<int>>>& ranges, int fillval /* = 255 */) {
cv::Mat out = cv::Mat::ones(img.size(), img.type()) * fillval;
cv::Mat hsv;
cv::cvtColor(img, hsv, cv::COLOR_BGR2HSV);
cv::Mat mask = cv::Mat::zeros(img.size(), CV_8UC1);
for (const auto& range_pair : ranges) {
cv::Scalar lower(range_pair[0][0], range_pair[0][1], range_pair[0][2]);
cv::Scalar upper(range_pair[1][0], range_pair[1][1], range_pair[1][2]);
cv::Mat temp_mask;
cv::inRange(hsv, lower, upper, temp_mask);
mask += temp_mask;
}
cv::Mat pos = mask == 255;
for (int y = 0; y < img.rows; y++) {
for (int x = 0; x < img.cols; x++) {
if (pos.at<uchar>(y, x)) {
out.at<cv::Vec3b>(y, x) = img.at<cv::Vec3b>(y, x);
}
}
}
return {out, mask};
}
pair<cv::Mat, string> color_fliter_process(const cv::Mat& img, int threshold) {
try {
cv::Mat grayscale;
cv::cvtColor(img, grayscale, cv::COLOR_BGR2GRAY);
cv::Mat binary = block_threshold(grayscale);
cv::Mat mask = binary > 0;
vector<string> color_names = {"red"};
vector<cv::Scalar> color_values = {{0, 0, 255}};
int best_color_index = 0;
double max_color_area = -1e10;
cv::Mat final_mask = mask.clone();
for (int i = 0; i < color_names.size(); i++) {
string color_name = color_names[i];
vector<vector<vector<int>>> ranges = get_color_range(color_name).check;
auto [_, color_mask] = filter_color(img, ranges);
cv::Mat color_threshold = color_mask == 255;
cv::Mat combined_mask;
cv::bitwise_and(color_threshold, mask, combined_mask);
double color_area = cv::countNonZero(combined_mask);
if (color_area > max_color_area) {
best_color_index = i;
max_color_area = color_area;
vector<vector<vector<int>>> full_ranges = get_color_range(color_name).full;
vector<vector<vector<int>>> strict_ranges = get_color_range(color_name).ranges2;
vector<vector<vector<int>>> combined_ranges;
for (int r = 0; r < full_ranges.size(); r++) {
vector<vector<int>> range_pair;
vector<int> lower;
vector<int> upper;
for (int k = 0; k < 3; k++) {
int lower_val = static_cast<int>(full_ranges[r][0][k] * (100 - threshold)/100.0 + strict_ranges[r][0][k] * threshold/100.0);
int upper_val = static_cast<int>(full_ranges[r][1][k] * (100 - threshold)/100.0 + strict_ranges[r][1][k] * threshold/100.0);
lower.push_back(lower_val);
upper.push_back(upper_val);
}
range_pair.push_back(lower);
range_pair.push_back(upper);
combined_ranges.push_back(range_pair);
}
auto [__, final_threshold] = filter_color(img, combined_ranges);
final_mask = final_threshold == 255;
cv::bitwise_and(final_mask, mask, final_mask);
}
}
cv::Mat output = cv::Mat(img.size(), img.type(), cv::Scalar(255, 255, 255));
cv::Scalar color_value = color_values[best_color_index];
for (int y = 0; y < img.rows; y++) {
for (int x = 0; x < img.cols; x++) {
if (final_mask.at<uchar>(y, x)) {
output.at<cv::Vec3b>(y, x) = cv::Vec3b(color_value[0], color_value[1], color_value[2]);
}
}
}
return {output, color_names[best_color_index]};
} catch (const exception& e) {
cout << "Exception in color_fliter_process: " << e.what() << endl;
return {img, "red"};
} catch (...) {
cout << "Unknown exception in color_fliter_process" << endl;
return {img, "red"};
}
}

@ -0,0 +1,16 @@
#ifndef COLOR_FILTER_H
#define COLOR_FILTER_H
#include <opencv2/core.hpp>
#include <utility>
#include <string>
#include <vector>
#include "hsv_color.h"
using namespace std;
pair<cv::Mat, cv::Mat> filter_color(const cv::Mat& img, const vector<vector<vector<int>>>& ranges, int fillval = 255);
pair<cv::Mat, string> color_fliter_process(const cv::Mat& img, int threshold);
#endif

@ -0,0 +1,127 @@
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <vector>
#include <map>
#include <string>
#include <iostream>
using namespace std;
using namespace cv;
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>
#include "../common.h"
#include "color_filter.h"
#include "adjust_image.h"
vector<SealInfo> extract_seal_main(const Mat& img, const vector<vector<double>>& det) {
int i = 0;
vector<SealInfo> seal_set;
for (const auto& seal : det) {
int x1 = int(seal[0]);
int y1 = int(seal[1]);
int x2 = int(seal[2]);
int y2 = int(seal[3]);
double conf = seal[4];
if (conf < 0.6) {
continue;
}
int shape = int(seal[5]);
int roi_x = max(0, x1);
int roi_y = max(0, y1);
int roi_width = min(x2 - x1, img.cols - roi_x);
int roi_height = min(y2 - y1, img.rows - roi_y);
if (roi_width <= 0 || roi_height <= 0) {
cout << "Warning: Invalid ROI for detection box, skipping" << endl;
continue;
}
Mat cropped_img = img(Rect(roi_x, roi_y, roi_width, roi_height));
auto [extracted_img, color_name] = color_fliter_process(cropped_img, 50);
vector<Mat> channels;
split(extracted_img, channels);
Mat red_channel = channels[0];
Scalar mean_val = mean(red_channel);
double red_average = mean_val[0];
if (red_average > 240) {
continue;
}
SealInfo seal_info;
seal_info.x1 = x1;
seal_info.y1 = y1;
seal_info.x2 = x2;
seal_info.y2 = y2;
seal_info.conf = conf;
seal_info.shape = shape;
seal_info.cropped_img = cropped_img;
seal_info.extracted_img = extracted_img;
seal_info.color_name = color_name;
ShapeInfo si;
if (shape >= 0 && shape < 5) {
si.type = SHAPETYPE_NAMES[shape];
} else {
si.type = "unknown";
}
int width = x2 - x1;
int height = y2 - y1;
si.minAreaRect = {{width / 2.0f, height / 2.0f}, {float(width), float(height)}, 0};
auto [adj_img, angle, new_si] = adjust_img(seal_info.extracted_img, ShapeInfo(si.type, si.minAreaRect, si.points, si.ignores, si.needInits, si.implement));
seal_info.shape_info = ShapeInfo(new_si.type, new_si.minAreaRect, new_si.points, new_si.ignores, new_si.needInits, new_si.implement);
seal_info.angle = angle;
seal_info.adj_img = adj_img;
seal_set.push_back(seal_info);
}
// 存储到单例类中,实现数据共享
seal_data::SealDataManager::getInstance().setSealSet(seal_set);
return seal_set;
}
// int main() {
// // TODO: 修改为你的图片路径
// string img_path = "..\\..\\image\\2.png";
//
// Mat img = imread(img_path);
// if (img.empty()) {
// cout << "Error: Cannot read image " << img_path << endl;
// return 1;
// }
//
// cout << "Image dimensions: " << img.cols << "x" << img.rows << endl;
//
// vector<vector<double>> det = {{256.0, 384.0, 471.0, 592.0, 0.980, 2},
// {511.0, 423.0, 665.0, 581.0, 0.969, 2}};
//
// cout << "Number of detection boxes: " << det.size() << endl;
// for (const auto& seal : det) {
// cout << "Detection box: x1=" << seal[0] << ", y1=" << seal[1] << ", x2=" << seal[2] << ", y2=" << seal[3] << endl;
// }
//
// vector<SealInfo> seal_info = extract_seal_main(img, det);
//
// for (size_t i = 0; i < seal_info.size(); ++i) {
// // TODO: 修改为你的输出路径
// string filename = "test_image\\extracted_seal_";
// filename += to_string(i) + ".png";
// imwrite(filename, seal_info[i].adj_img);
// cout << "Test seal " << i << " saved to: " << filename << endl;
// cout << " Shape: " << seal_info[i].shape_info.type << endl;
// cout << " Center: (" << seal_info[i].shape_info.minAreaRect.center.x << ", " << seal_info[i].shape_info.minAreaRect.center.y << ")" << endl;
// cout << " Size: (" << seal_info[i].shape_info.minAreaRect.size.width << ", " << seal_info[i].shape_info.minAreaRect.size.height << ")" << endl;
// cout << " Angle: " << seal_info[i].angle << endl;
// }
//
// return 0;
// }

@ -0,0 +1,13 @@
#ifndef EXTRACT_SEAL_MAIN_H
#define EXTRACT_SEAL_MAIN_H
#include <opencv2/core.hpp>
#include <vector>
using namespace std;
struct SealInfo;
vector<SealInfo> extract_seal_main(const cv::Mat& img, const vector<vector<double>>& det);
#endif

@ -0,0 +1,33 @@
#include "hsv_color.h"
#include <stdexcept>
using namespace std;
static const map<string, ColorRange> COLOR_RANGES = {
{"red", {
{{{130, 50, 100}, {180, 255, 255}}, {{0, 50, 100}, {6, 255, 255}}},
{{{130, 70, 100}, {180, 255, 255}}, {{0, 70, 100}, {12, 255, 255}}},
{{{130, 43, 60}, {180, 255, 255}}, {{0, 43, 60}, {12, 255, 255}}},
{{{130, 5, 5}, {180, 255, 255}}, {{0, 5, 5}, {25, 255, 255}}}
}},
{"blue", {
{{{78, 50, 50}, {124, 255, 255}}},
{{{78, 70, 50}, {124, 255, 255}}},
{{{78, 43, 20}, {124, 255, 255}}},
{{{78, 43, 0}, {124, 255, 255}}}
}},
{"black", {
{{{0, 0, 0}, {180, 255, 43}}},
{{{0, 0, 0}, {180, 255, 43}}},
{{{0, 0, 0}, {180, 255, 43}}},
{{{0, 0, 0}, {180, 255, 43}}}
}}
};
const ColorRange& get_color_range(const string& color) {
auto it = COLOR_RANGES.find(color);
if (it != COLOR_RANGES.end()) {
return it->second;
}
throw invalid_argument("Color not found: " + color);
}

@ -0,0 +1,20 @@
#ifndef HSV_COLOR_H
#define HSV_COLOR_H
#include <opencv2/core.hpp>
#include <vector>
#include <string>
#include <map>
using namespace std;
struct ColorRange {
vector<vector<vector<int>>> ranges1;
vector<vector<vector<int>>> ranges2;
vector<vector<vector<int>>> check;
vector<vector<vector<int>>> full;
};
const ColorRange& get_color_range(const string& color);
#endif

@ -0,0 +1,116 @@
#include "image_morphology.h"
#include <opencv2/imgproc.hpp>
#include <cmath>
#include <algorithm>
#include <cassert>
using namespace std;
const string WHITE_BG = "white";
const string BLACK_BG = "black";
cv::Mat my_dilate(const cv::Mat& img, const cv::Size& ksize, int iter, const string& bg_type) {
assert(bg_type == WHITE_BG || bg_type == BLACK_BG);
cv::Mat kernel(ksize.height, ksize.width, CV_8UC1, cv::Scalar(255));
cv::Mat result;
if (bg_type == BLACK_BG) {
cv::dilate(img, result, kernel, cv::Point(-1, -1), iter);
} else {
cv::erode(img, result, kernel, cv::Point(-1, -1), iter);
}
return result;
}
cv::Mat wipe_noise(const cv::Mat& img) {
cv::Mat result = img.clone();
int num_objects;
cv::Mat labels, stats, centroids;
num_objects = cv::connectedComponentsWithStats(img, labels, stats, centroids, 8);
int img_min = min(img.rows, img.cols);
double scale = pow(max(1.0, img_min / 300.0), 2);
int threshold_area = static_cast<int>(10 * scale);
for (int index = 0; index < num_objects; ++index) {
int area = stats.at<int>(index, 4);
if (area < threshold_area) {
result.setTo(0, labels == index);
}
}
return result;
}
cv::Mat wipe_noise_gray(const cv::Mat& img) {
cv::Mat result = img.clone();
cv::Mat blur;
cv::GaussianBlur(img, blur, cv::Size(3, 3), 0);
cv::Mat binary;
cv::threshold(blur, binary, 200, 255, cv::THRESH_BINARY_INV | cv::THRESH_OTSU);
int num_objects;
cv::Mat labels, stats, centroids;
num_objects = cv::connectedComponentsWithStats(binary, labels, stats, centroids, 8);
int img_min = min(img.rows, img.cols);
double scale = pow(max(1.0, img_min / 300.0), 2);
int threshold_area = static_cast<int>(10 * scale);
for (int index = 0; index < num_objects; ++index) {
int area = stats.at<int>(index, 4);
if (area < threshold_area) {
result.setTo(255, labels == index);
}
}
return result;
}
cv::Mat wipe_star(const cv::Mat& img, const cv::RotatedRect& minAreaRect) {
cv::Mat result = img.clone();
cv::Point2f center = minAreaRect.center;
cv::Size2f wh = minAreaRect.size;
double r = max(wh.width, wh.height) / min(wh.width, wh.height);
int base_wh = static_cast<int>(wh.height / 2 + wh.height / 20);
double _W = base_wh * r;
double _H = base_wh;
int num_objects;
cv::Mat labels, stats, centroids;
num_objects = cv::connectedComponentsWithStats(img, labels, stats, centroids, 8);
for (int index = 1; index < num_objects; ++index) {
int x = stats.at<int>(index, 0);
int y = stats.at<int>(index, 1);
int w = stats.at<int>(index, 2);
int h = stats.at<int>(index, 3);
cv::Point2f _center(x + w / 2.0, y + h / 2.0);
double center_dis = sqrt(pow(_center.x - center.x, 2) + pow(_center.y - center.y, 2));
double hehe = min(w, h);
if (hehe > max(_W, _H) * 0.25 && hehe < min(_W, _H) && center_dis < max(_W, _H) * 0.2) {
result.setTo(0, labels == index);
}
}
if (r < 1.2) {
int y1 = static_cast<int>(center.y - wh.height * 0.15);
int y2 = static_cast<int>(center.y + wh.height * 0.15);
int x1 = static_cast<int>(center.x - wh.width * 0.15);
int x2 = static_cast<int>(center.x + wh.width * 0.15);
int tx1 = max(0, x1);
int ty1 = max(0, y1);
int tx2 = min(static_cast<int>(result.cols), x2);
int ty2 = min(static_cast<int>(result.rows), y2);
if (tx1 < tx2 && ty1 < ty2) {
result(cv::Rect(tx1, ty1, tx2 - tx1, ty2 - ty1)).setTo(0);
}
}
return result;
}

@ -0,0 +1,20 @@
#ifndef IMAGE_MORPHOLOGY_H
#define IMAGE_MORPHOLOGY_H
#include <opencv2/core.hpp>
#include <string>
using namespace std;
extern const string WHITE_BG;
extern const string BLACK_BG;
cv::Mat my_dilate(const cv::Mat& img, const cv::Size& ksize = cv::Size(5, 5), int iter = 1, const string& bg_type = BLACK_BG);
cv::Mat wipe_noise(const cv::Mat& img);
cv::Mat wipe_noise_gray(const cv::Mat& img);
cv::Mat wipe_star(const cv::Mat& img, const cv::RotatedRect& minAreaRect);
#endif

@ -0,0 +1,145 @@
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>
#include <vector>
#include <string>
#include <filesystem>
#include "../common.h"
#include "extract_seal_main.h"
using namespace std;
namespace fs = std::filesystem;
struct ImageTask {
string img_path;
vector<vector<double>> det;
};
int main() {
// TODO: 修改为你的图片目录路径
// 示例: "c:\\Users\\用户名\\Desktop\\Final project\\image" 或使用相对路径 "..\\..\\image"
string image_dir = "..\\..\\image";
vector<ImageTask> tasks;
cout << "Scanning image directory: " << image_dir << endl;
try {
for (const auto& entry : fs::directory_iterator(image_dir)) {
if (entry.is_regular_file()) {
string ext = entry.path().extension().string();
if (ext == ".png" || ext == ".jpg" || ext == ".jpeg" || ext == ".bmp") {
ImageTask task;
task.img_path = entry.path().string();
string filename = entry.path().filename().string();
if (filename == "2.png") {
task.det = {
{256.0, 384.0, 471.0, 592.0, 0.980, 2},
{511.0, 423.0, 665.0, 581.0, 0.969, 2}
};
} else if (filename == "1.png") {
task.det = {
{408.0, 32.0, 559.0, 186.0, 0.974, 0},
{267.0, 61.0, 348.0, 143.0, 0.973, 2},
{54.0, 36.0, 212.0, 147.0, 0.972, 1}
};
}
tasks.push_back(task);
cout << "Found image: " << filename << endl;
}
}
}
} catch (const fs::filesystem_error& e) {
cout << "Error scanning directory: " << e.what() << endl;
return 1;
}
if (tasks.empty()) {
cout << "No image files found in directory." << endl;
return 1;
}
cout << "\nTotal images found: " << tasks.size() << endl;
for (size_t task_idx = 0; task_idx < tasks.size(); ++task_idx) {
const ImageTask& task = tasks[task_idx];
string img_path = task.img_path;
vector<vector<double>> det = task.det;
cout << "\n========================================" << endl;
cout << "Processing image " << task_idx + 1 << "/" << tasks.size() << endl;
cout << "File: " << fs::path(img_path).filename().string() << endl;
cout << "========================================" << endl;
cv::Mat img = cv::imread(img_path);
if (img.empty()) {
cout << "Error: Cannot read image " << img_path << endl;
if (task_idx < tasks.size() - 1) {
cout << "Press Enter to continue to next image..." << endl;
cin.ignore(numeric_limits<streamsize>::max(), '\n');
}
continue;
}
cout << "Image dimensions: " << img.cols << "x" << img.rows << endl;
cout << "Number of detection boxes: " << det.size() << endl;
for (size_t i = 0; i < det.size(); ++i) {
cout << "Detection box " << i+1 << ": x1=" << det[i][0] << ", y1=" << det[i][1]
<< ", x2=" << det[i][2] << ", y2=" << det[i][3]
<< ", conf=" << det[i][4] << ", shape=" << det[i][5] << endl;
}
cout << "\nStart extracting seals..." << endl;
vector<SealInfo> seal_set = extract_seal_main(img, det);
cout << "Total extracted seals: " << seal_set.size() << endl << endl;
// TODO: 修改为你的输出目录路径
// 示例: "c:\\Users\\用户名\\Desktop\\Final project\\c\\extract_seal_main\\test_image" 或使用相对路径 "..\\extract_seal_main\\test_image"
string output_dir = "..\\extract_seal_main\\test_image";
fs::create_directories(output_dir);
for (size_t i = 0; i < seal_set.size(); ++i) {
cout << "========== Seal " << i << " ==========" << endl;
string img_name = fs::path(img_path).stem().string();
string cropped_filename = output_dir + "\\seal_" + img_name + "_" + to_string(i) + "_cropped.png";
string ex_filename = output_dir + "\\seal_" + img_name + "_" + to_string(i) + "_ex_img.png";
string adj_filename = output_dir + "\\seal_" + img_name + "_" + to_string(i) + "_adj_img.png";
cv::imwrite(cropped_filename, seal_set[i].cropped_img);
cv::imwrite(ex_filename, seal_set[i].extracted_img);
cv::imwrite(adj_filename, seal_set[i].adj_img);
cout << "Saved files:" << endl;
cout << " " << cropped_filename << " (cropped_img)" << endl;
cout << " " << ex_filename << " (extracted_img)" << endl;
cout << " " << adj_filename << " (adj_img)" << endl;
cout << endl << "region: [" << seal_set[i].x1 << ", " << seal_set[i].y1 << ", "
<< seal_set[i].x2 << ", " << seal_set[i].y2 << "]" << endl;
cout << "shape: " << seal_set[i].shape << endl;
cout << "color_name: " << seal_set[i].color_name << endl;
cout << "angle: " << seal_set[i].angle << endl;
cout << "shape_info.type: " << seal_set[i].shape_info.type << endl;
cout << "shape_info.minAreaRect: center=("
<< seal_set[i].shape_info.minAreaRect.center.x << ", "
<< seal_set[i].shape_info.minAreaRect.center.y << "), size=("
<< seal_set[i].shape_info.minAreaRect.size.width << ", "
<< seal_set[i].shape_info.minAreaRect.size.height << ")" << endl;
cout << endl;
}
if (task_idx < tasks.size() - 1) {
cout << "Press Enter to process next image..." << endl;
cin.ignore(numeric_limits<streamsize>::max(), '\n');
}
}
cout << "\nAll images processed!" << endl;
return 0;
}

@ -0,0 +1,100 @@
#include "image_utilities.h"
#include <opencv2/imgproc.hpp>
#include <vector>
#include <cmath>
#include <iostream>
#include <exception>
using namespace std;
vector<cv::Point> mrect2box(const cv::RotatedRect& mrect) {
cv::Point2f boxPoints[4];
mrect.points(boxPoints);
vector<cv::Point> result;
for (int i = 0; i < 4; ++i) {
result.emplace_back(cv::Point(static_cast<int>(boxPoints[i].x), static_cast<int>(boxPoints[i].y)));
}
return result;
}
cv::Mat block_threshold(const cv::Mat& gray) {
try {
int height = gray.rows;
int width = gray.cols;
vector<int> divisions = {8, 13};
vector<int> block_sizes;
for (int div : divisions) {
int size = max(19, (height + width) / 2 / div);
if (size % 2 == 0) size++;
block_sizes.push_back(size);
}
vector<pair<int, int>> block_strategies;
for (int size : block_sizes) {
block_strategies.push_back({size, 0});
block_strategies.push_back({size, size / 2});
}
cv::Mat otsu;
double global_threshold = cv::threshold(gray, otsu, 0, 255, cv::THRESH_BINARY_INV | cv::THRESH_OTSU);
cv::Mat adaptive_ref;
cv::adaptiveThreshold(gray, adaptive_ref, 255, cv::ADAPTIVE_THRESH_GAUSSIAN_C, cv::THRESH_BINARY_INV, block_sizes[0], 20);
cv::Mat result = cv::Mat::zeros(gray.size(), CV_8UC1);
for (auto& strategy : block_strategies) {
int block_size = strategy.first;
int offset = strategy.second;
cv::Mat current_result = cv::Mat::zeros(gray.size(), CV_8UC1);
for (int y = offset; y < height; y += block_size) {
for (int x = offset; x < width; x += block_size) {
int end_y = min(y + block_size, height);
int end_x = min(x + block_size, width);
cv::Mat block_roi = gray(cv::Rect(x, y, end_x - x, end_y - y));
cv::Scalar mean, stddev;
cv::meanStdDev(block_roi, mean, stddev);
double variance = stddev[0] * stddev[0];
cv::Mat block_result;
if (variance < 80) {
block_result = cv::Mat::zeros(block_roi.size(), CV_8UC1);
if (mean[0] < global_threshold) {
block_result = cv::Mat::ones(block_roi.size(), CV_8UC1) * 255;
}
} else {
cv::threshold(block_roi, block_result, 0, 255, cv::THRESH_BINARY_INV | cv::THRESH_OTSU);
cv::Scalar ref_mean, block_mean;
cv::Mat adaptive_block = adaptive_ref(cv::Rect(x, y, end_x - x, end_y - y));
cv::meanStdDev(adaptive_block, ref_mean, cv::noArray());
cv::meanStdDev(block_result, block_mean, cv::noArray());
if (abs(ref_mean[0] - block_mean[0]) > 200) {
block_result = 255 - block_result;
}
}
block_result.copyTo(current_result(cv::Rect(x, y, end_x - x, end_y - y)));
}
}
cv::bitwise_or(result, current_result, result);
}
return result;
} catch (const exception& e) {
cout << "Exception in block_threshold: " << e.what() << endl;
return cv::Mat();
} catch (...) {
cout << "Unknown exception in block_threshold" << endl;
return cv::Mat();
}
}

@ -0,0 +1,13 @@
#ifndef IMAGE_UTILITIES_H
#define IMAGE_UTILITIES_H
#include <opencv2/core.hpp>
#include <vector>
using namespace std;
cv::Mat block_threshold(const cv::Mat& gray);
vector<cv::Point> mrect2box(const cv::RotatedRect& mrect);
#endif

@ -0,0 +1,315 @@
#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: TrueFalse
* 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_val0
* 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();
}
}

@ -0,0 +1,84 @@
#ifndef REMOVE_BORDER_H
#define REMOVE_BORDER_H
#include <opencv2/opencv.hpp>
#include <string>
#include <vector>
#include "../common.h"
#include "../image_utilities.h"
using namespace std;
/**
*
* Pythonoffset(tx, ty, ws, hs, ta)
*/
struct Offset {
float tx; // x方向平移
float ty; // y方向平移
float ws; // 宽度缩放
float hs; // 高度缩放
float ta; // 角度调整
Offset(float tx = 0.0f, float ty = 0.0f, float ws = 0.0f, float hs = 0.0f, float ta = 0.0f)
: tx(tx), ty(ty), ws(ws), hs(hs), ta(ta) {}
};
/**
*
*
* args:
* imgshape:
* shapeType:
* filter_border:
* returns:
* filterMask:
*/
cv::Mat get_filterBorderMask(const cv::Size& imgshape, const string& shapeType, const cv::RotatedRect& filter_border);
/**
* offsets
*
* args:
* img:
* shapeType:
* mrect:
* offsets:
* inverse: TrueFalse
* 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 = false, int bg_val = 255);
/**
*
*
* 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 = 255);
/**
*
*
* args:
* img:
* shapeinfo:
* bg_val: , bg_val0
* 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 = 255);
#endif // REMOVE_BORDER_H

@ -0,0 +1,45 @@
#include "remove_border_main.h"
#include "remove_border.h"
#include <opencv2/imgproc.hpp>
using namespace std;
void remove_border_main(vector<SealInfo>& seal_set, bool is_debug) {
int i = 0;
for (auto& seal : seal_set) {
if (seal.adj_img.empty()) {
cout << "警告: seal.adj_img为空跳过处理" << endl;
continue;
}
cv::Mat gray;
cv::cvtColor(seal.adj_img, gray, cv::COLOR_BGR2GRAY);
cv::Mat mask;
ShapeInfo new_shapeinfo;
cv::RotatedRect inner_border;
try {
cv::Mat rm_bd_img = remove_border(gray, seal.shape_info, mask, new_shapeinfo, inner_border, 255);
seal.rm_bd_img = rm_bd_img;
seal.shape_info = new_shapeinfo;
if (is_debug) {
string window_name = "extracted_img_" + to_string(i);
cout << "Debug: Processed seal " << i << " (" << window_name << ")" << endl;
}
} catch (const exception& e) {
cout << "处理印章时出错: " << e.what() << endl;
} catch (...) {
cout << "处理印章时出错: 未知异常" << endl;
}
i++;
}
if (is_debug) {
cout << "Debug: Processing completed" << endl;
}
}

@ -0,0 +1,10 @@
#ifndef REMOVE_BORDER_MAIN_H
#define REMOVE_BORDER_MAIN_H
#include "../common.h"
using namespace std;
void remove_border_main(vector<SealInfo>& seal_set, bool is_debug = false);
#endif

@ -0,0 +1,100 @@
#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
#include <fstream>
#include <filesystem>
#include "../extract_seal_main/extract_seal_main.h"
#include "remove_border.h"
#include "remove_border_main.h"
using namespace std;
namespace fs = std::filesystem;
void process_seal_remove_border() {
// TODO: 修改为你的测试图片路径
// 示例: "c:\\Users\\用户名\\Desktop\\Final project\\image\\1.png" 或使用相对路径 "..\\..\\image\\1.png"
string img_path = "..\\..\\image\\1.png";
cout << "Image path: " << img_path << endl;
ifstream file(img_path);
if (!file.good()) {
cout << "Error: Image file does not exist!" << endl;
return;
}
file.close();
cv::Mat img = cv::imread(img_path);
if (img.empty()) {
cout << "Error: Cannot read image!" << endl;
return;
}
cout << "Image loaded: " << img.cols << "x" << img.rows << endl;
vector<vector<double>> det = {
{408.0, 32.0, 559.0, 186.0, 0.974, 0},
{267.0, 61.0, 348.0, 143.0, 0.973, 2},
{54.0, 36.0, 212.0, 147.0, 0.972, 1}
};
cout << "Number of detection boxes: " << det.size() << endl;
cout << "Extracting seals..." << endl;
try {
vector<SealInfo> seal_set = extract_seal_main(img, det);
cout << "Total extracted seals: " << seal_set.size() << endl;
if (seal_set.empty()) {
cout << "Error: No seals extracted!" << endl;
return;
}
for (size_t i = 0; i < seal_set.size(); ++i) {
cout << "Seal " << i << ":" << endl;
cout << " Shape: " << seal_set[i].shape_info.type << endl;
cout << " Adjusted image size: " << seal_set[i].adj_img.cols << "x" << seal_set[i].adj_img.rows << endl;
}
cout << endl << "Removing borders..." << endl;
remove_border_main(seal_set, false);
string output_dir = "..\\remove_border\\test_image";
fs::create_directories(output_dir);
cout << endl << "========== Results after remove_border ==========" << endl;
for (size_t i = 0; i < seal_set.size(); ++i) {
cout << endl << "========== Seal " << i << " ==========" << endl;
if (seal_set[i].rm_bd_img.empty()) {
cout << " Warning: rm_bd_img is empty!" << endl;
continue;
}
string save_path = output_dir + "\\seal_" + to_string(i) + "_rm_bd_img.png";
cv::imwrite(save_path, seal_set[i].rm_bd_img);
cout << "rm_bd_img saved: " << save_path << endl;
cout << "rm_bd_img size: " << seal_set[i].rm_bd_img.cols << "x" << seal_set[i].rm_bd_img.rows << endl;
cout << "shape_info.type: " << seal_set[i].shape_info.type << endl;
cout << "shape_info.minAreaRect: center=("
<< seal_set[i].shape_info.minAreaRect.center.x << ", "
<< seal_set[i].shape_info.minAreaRect.center.y << "), size=("
<< seal_set[i].shape_info.minAreaRect.size.width << ", "
<< seal_set[i].shape_info.minAreaRect.size.height << "), angle="
<< seal_set[i].shape_info.minAreaRect.angle << endl;
}
cout << endl << "Done!" << endl;
} catch (const exception& e) {
cout << "Error: " << e.what() << endl;
} catch (...) {
cout << "Error: Unknown exception occurred!" << endl;
}
}
int main() {
process_seal_remove_border();
return 0;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Loading…
Cancel
Save