基于OpenCV C++调整代码去除与文字同色重叠水印的技术问询
调整方案说明
你原有方案依赖红水印的通道特异性,对颜色接近文字的重叠斜向水印效果不足,可按以下逻辑调整:
- 先通过傅里叶频域滤波去除周期性斜向水印的全局特征,这类半透明叠加的斜向水印在频域会呈现对称分布的高亮特征点,过滤后逆变换即可消除大部分水印痕迹,对原有文字的影响极小
- 针对频域处理后残留的水印痕迹,结合半透明水印的像素差值特征生成掩码,用快速行进修复算法(OpenCV自带inpaint函数)修复重叠区域
- 额外增加文字边缘检测逻辑,修复时避开文字边缘区域,避免文字出现模糊、缺边问题
修改后完整代码
#include <stdio.h> #include <opencv2/opencv.hpp> #include <Windows.h> #include <string> #include <filesystem> namespace fs = std::filesystem; using namespace std; using namespace cv; // 傅里叶频域滤波去除周期性水印 Mat fftWatermarkRemove(Mat& input) { Mat gray; cvtColor(input, gray, COLOR_BGR2GRAY); // 扩充图像到最佳尺寸 int m = getOptimalDFTSize(gray.rows); int n = getOptimalDFTSize(gray.cols); Mat padded; copyMakeBorder(gray, padded, 0, m - gray.rows, 0, n - gray.cols, BORDER_CONSTANT, Scalar::all(0)); // 生成傅里叶变换通道 Mat planes[] = {Mat_<float>(padded), Mat::zeros(padded.size(), CV_32F)}; Mat complexI; merge(planes, 2, complexI); dft(complexI, complexI); // 中心化频谱 int cx = complexI.cols / 2; int cy = complexI.rows / 2; Mat q0(complexI, Rect(0, 0, cx, cy)); Mat q1(complexI, Rect(cx, 0, cx, cy)); Mat q2(complexI, Rect(0, cy, cx, cy)); Mat q3(complexI, Rect(cx, cy, cx, cy)); Mat tmp; q0.copyTo(tmp); q3.copyTo(q0); tmp.copyTo(q3); q1.copyTo(tmp); q2.copyTo(q1); tmp.copyTo(q2); // 过滤频域高亮水印特征(半径可根据实际水印调整,这里设为15) Mat mask = Mat::ones(complexI.size(), CV_32F); circle(mask, Point(cx, cy), 15, Scalar(0), -1); // 去掉对称的水印特征点,可根据实际频谱调整坐标 circle(mask, Point(cx - 30, cy - 20), 8, Scalar(0), -1); circle(mask, Point(cx + 30, cy + 20), 8, Scalar(0), -1); circle(mask, Point(cx - 20, cy + 30), 8, Scalar(0), -1); circle(mask, Point(cx + 20, cy - 30), 8, Scalar(0), -1); // 应用掩码并逆变换 multiply(complexI, mask, complexI); // 去中心化 q0.copyTo(tmp); q3.copyTo(q0); tmp.copyTo(q3); q1.copyTo(tmp); q2.copyTo(q1); tmp.copyTo(q2); idft(complexI, complexI); split(complexI, planes); magnitude(planes[0], planes[1], planes[0]); Mat magI = planes[0]; normalize(magI, magI, 0, 255, NORM_MINMAX, CV_8U); // 裁剪回原尺寸 magI = magI(Rect(0, 0, gray.cols, gray.rows)); // 转三通道输出 Mat output; cvtColor(magI, output, COLOR_GRAY2BGR); return output; } int main(int argc, char** argv) { bool debugFlag = true; std::string path = "C:/test/"; // 文字边缘保护阈值,可根据实际调整 int edgeThresh = 80; int inpaintRadius = 3; for (const auto& entry : fs::directory_iterator(path)) { std::string fileName = entry.path().string(); Mat original = imread(fileName, cv::IMREAD_COLOR); if (original.empty()) continue; if (debugFlag) { imshow("original", original); } // 1. 频域滤波去除大部分斜向水印 Mat fftRemoved = fftWatermarkRemove(original); if (debugFlag) { imshow("fft_result", fftRemoved); } // 2. 生成残留水印掩码 Mat grayOrig, grayFft, mask; cvtColor(original, grayOrig, COLOR_BGR2GRAY); cvtColor(fftRemoved, grayFft, COLOR_BGR2GRAY); absdiff(grayOrig, grayFft, mask); threshold(mask, mask, 20, 255, THRESH_BINARY); // 3. 提取文字边缘,从掩码中剔除,避免破坏文字 Mat edges; Canny(grayOrig, edges, edgeThresh, edgeThresh * 2); mask.setTo(0, edges); if (debugFlag) { imshow("repair_mask", mask); } // 4. 修复残留水印区域 Mat outputImg; inpaint(original, mask, outputImg, inpaintRadius, INPAINT_TELEA); if (debugFlag) { imshow("output", outputImg); } if (debugFlag) { waitKey(0); } else { imwrite(fileName, outputImg); } } return 0; }
可调参数说明
- 频域滤波的过滤点坐标、半径:如果频域处理后还有水印残留,可先输出频谱图找到对应的高亮对称点,调整
circle函数的坐标和半径即可 - 掩码阈值
threshold的第二个参数:如果残留水印多就调低,误删文字多就调高 - 修复半径
inpaintRadius:水印越粗,半径调的越大
内容的提问来源于stack exchange,提问作者mp96
相关产品推荐
相关产品推荐

