利用 C/C++ 和 OpenCV 添加图片水印
利用 C/C++ 和 OpenCV 添加图片水印 🖼️在数字图像处置处罚中,添加水印是一种常见的操作,可以用于版权掩护、品牌宣传或信息标注。本文将介绍如何利用 C/C++ 和强盛的计算机视觉库 OpenCV 来实现将自界说水印(图片或文字)添加到目的图片上。
准备工作 🛠️
在开始之前,请确保你已经具备以下条件:
[*]C/C++ 编译器: 如 GCC/G++, Clang, MSVC 等。
[*]OpenCV 库: 须要预先安装并配置好 OpenCV。你可以从 OpenCV 官网 (https://opencv.org/) 下载并根据你的操作体系进行安装。
[*]一个集成开发环境 (IDE) (可选,但推荐): 如 Visual Studio, CLion, Code::Blocks 等,可以方便代码编写和项目管理。
[*]待添加水印的图片: 准备一张你想要添加水印的图片。
[*]水印图片 (可选): 如果你盼望利用图片作为水印,请准备好水印图片。
核心概念 💡
实现图片水印的核心思路是将水印图像(或文字)与原始图像进行某种形式的融合或叠加。OpenCV 提供了丰富的图像处置处罚函数,使得这个过程相对简单。
关键的 OpenCV 组件和函数包罗:
[*]cv::Mat: OpenCV 中用于存储图像数据的核心数据结构。
[*]cv::imread(): 用于加载图片到 cv::Mat 对象。
[*]cv::imwrite(): 用于将 cv::Mat 对象中的图像数据保存到文件。
[*]cv::resize(): 用于调解图像的巨细,可以将水印调解到符合尺寸。
[*]cv::Rect: 用于界说图像中的一个矩形区域 (Region of Interest, ROI),方便在特定位置操作。
[*]cv::addWeighted(): 用于对两个图像进行加权混淆,常用于实现半透明水印结果。
[*]cv::putText(): 用于在图像上绘制文字,可以实现文字水印。
实现步骤 📝
下面将分步骤介绍如何实现图片水印和文字水印。
1. 包含头文件和命名空间
首先,在你的 C++ 代码中包含须要的 OpenCV 头文件并利用 cv 命名空间:
#include <opencv2/opencv.hpp> // 包含 OpenCV 的核心头文件
#include <iostream>
using namespace cv;
using namespace std;
2. 加载原始图片和水印图片 (针对图片水印)
利用 cv::imread() 函数加载你的原始图片和水印图片。
Mat originalImage = imread("path/to/your/original_image.jpg");
Mat watermarkImage = imread("path/to/your/watermark_image.png", IMREAD_UNCHANGED); // IMREAD_UNCHANGED 保留 alpha 通道
if (originalImage.empty()) {
cerr << "错误: 无法加载原始图片!" << endl;
return -1;
}
if (watermarkImage.empty()) {
cerr << "错误: 无法加载水印图片!" << endl;
return -1;
}
注意: 如果你的水印图片是 PNG 格式并且包含透明通道 (alpha channel),利用 IMREAD_UNCHANGED 参数可以保留这些信息,从而实现更天然的融合结果。
3. 调解水印巨细 (可选)
通常,水印图片的巨细须要根据原始图片进行调解,以确保其不会过大或过小。
// 示例:将水印宽度调整为原始图片宽度的 1/5,并保持宽高比
double scaleFactor = (originalImage.cols / 5.0) / watermarkImage.cols;
Mat resizedWatermark;
resize(watermarkImage, resizedWatermark, Size(), scaleFactor, scaleFactor, INTER_LINEAR);
4. 界说水印位置
你须要确定水印在原始图片上的位置。通常可以选择图片的四个角或者中心。
// 示例:将水印放置在右下角,并留有一些边距
int margin = 10;
int x = originalImage.cols - resizedWatermark.cols - margin;
int y = originalImage.rows - resizedWatermark.rows - margin;
// 或者放置在左上角
// int x = margin;
// int y = margin;
// 或者放置在中心
// int x = (originalImage.cols - resizedWatermark.cols) / 2;
// int y = (originalImage.rows - resizedWatermark.rows) / 2;
Rect roi(x, y, resizedWatermark.cols, resizedWatermark.rows);
确保界说的位置和水印巨细不会超出原始图片的界限。
5. 将水印叠加到原始图片
方法一:利用 cv::addWeighted() (适用于半透明结果)
如果水印图片没有 alpha 通道,或者你盼望实现一个固定透明度的叠加结果,可以利用 cv::addWeighted()。
// 确保水印图像和 ROI 区域的类型和通道数一致
// 如果原始图像是 3 通道 (BGR),水印图像也应该是 3 通道
Mat watermarkBGR;
if (resizedWatermark.channels() == 4) {
cvtColor(resizedWatermark, watermarkBGR, COLOR_BGRA2BGR); // 如果有 alpha 通道,先转为 BGR
} else {
watermarkBGR = resizedWatermark;
}
Mat imageROI = originalImage(roi); // 获取原始图片中要放置水印的区域
double alpha = 0.5; // 水印的透明度 (0.0 完全透明, 1.0 完全不透明)
addWeighted(imageROI, 1.0 - alpha, watermarkBGR, alpha, 0.0, imageROI);
方法二:利用 Alpha 通道进行融合 (适用于 PNG 水印)
如果水印图片有 alpha 通道,可以实现更精细的融合,水印的非透明部门会完全覆盖,透明部门则表现配景。
if (resizedWatermark.channels() == 4) {
vector<Mat> channels;
split(resizedWatermark, channels); // 分离 RGBA 通道
Mat bgr = { channels, channels, channels };
Mat alphaChannel = channels; // 获取 alpha 通道
Mat watermarkBGR_alpha;
merge(bgr, 3, watermarkBGR_alpha); // 合并 BGR 通道
Mat imageROI = originalImage(roi);
// 使用 alpha 通道作为掩码进行融合
for (int r = 0; r < imageROI.rows; ++r) {
for (int c = 0; c < imageROI.cols; ++c) {
double alpha = alphaChannel.at<uchar>(r, c) / 255.0;
if (alpha > 0) { // 只处理非完全透明的像素
for (int channel = 0; channel < 3; ++channel) {
imageROI.at<Vec3b>(r, c) =
saturate_cast<uchar>((1.0 - alpha) * imageROI.at<Vec3b>(r, c) +
alpha * watermarkBGR_alpha.at<Vec3b>(r, c));
}
}
}
}
} else {
// 如果水印没有 alpha 通道,可以简单复制或使用 addWeighted
Mat imageROI = originalImage(roi);
resizedWatermark.copyTo(imageROI); // 直接覆盖
}
一个更简洁的利用 alpha 通道的方法是:
if (resizedWatermark.channels() == 4) {
Mat imageROI = originalImage(roi);
vector<Mat> bgra_channels;
split(resizedWatermark, bgra_channels);
// 创建一个掩码,其中 alpha > 0 的地方为 255
Mat mask;
compare(bgra_channels, 0, mask, CMP_GT);
// 提取水印的 BGR 部分
Mat watermark_bgr;
vector<Mat> bgr_channels = {bgra_channels, bgra_channels, bgra_channels};
merge(bgr_channels, watermark_bgr);
// 将水印的 BGR 部分拷贝到 ROI,使用掩码
watermark_bgr.copyTo(imageROI, mask);
} else {
// 处理没有 alpha 通道的情况
Mat imageROI = originalImage(roi);
resizedWatermark.copyTo(imageROI);
}
6. 添加文字水印
如果你想添加文字作为水印,可以利用 cv::putText() 函数。
string watermarkText = "My Copyright";
Point textOrg(originalImage.cols - 200, originalImage.rows - 30); // 文字的起始位置 (左下角)
int fontFace = FONT_HERSHEY_SIMPLEX;
double fontScale = 1.0;
Scalar color(0, 0, 255); // 字体颜色 (BGR) - 这里是红色
int thickness = 2;
int lineType = LINE_AA; // 抗锯齿
putText(originalImage, watermarkText, textOrg, fontFace, fontScale, color, thickness, lineType);
你可以调解字体、巨细、颜色、位置和透明度(通过在文字下方绘制一个半透明的矩形配景)。
7. 表现和保存结果
最后,你可以表现带有水印的图片,并将其保存到文件。
namedWindow("带水印的图片", WINDOW_AUTOSIZE);
imshow("带水印的图片", originalImage);
imwrite("path/to/your/output_image_with_watermark.jpg", originalImage);
waitKey(0); // 等待按键后关闭窗口
destroyAllWindows();
完整示例代码 (图片水印 - Alpha 融合)
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main() {
// 1. 加载原始图片和水印图片
Mat originalImage = imread("original.jpg"); // 替换为你的原始图片路径
Mat watermarkImage = imread("watermark.png", IMREAD_UNCHANGED); // 替换为你的水印图片路径
if (originalImage.empty()) {
cerr << "错误: 无法加载原始图片!" << endl;
return -1;
}
if (watermarkImage.empty()) {
cerr << "错误: 无法加载水印图片!" << endl;
return -1;
}
// 2. 调整水印大小 (可选)
Mat resizedWatermark;
double scaleFactor = (originalImage.cols / 8.0) / watermarkImage.cols; // 水印宽度为原图的1/8
if (watermarkImage.cols > originalImage.cols * 0.1) { // 仅当水印宽度大于原图10%时缩放
resize(watermarkImage, resizedWatermark, Size(), scaleFactor, scaleFactor, INTER_AREA);
} else {
resizedWatermark = watermarkImage;
}
// 3. 定义水印位置 (右下角)
int margin = 20;
int x = originalImage.cols - resizedWatermark.cols - margin;
int y = originalImage.rows - resizedWatermark.rows - margin;
// 确保 roi 不会超出原图边界
if (x < 0) x = 0;
if (y < 0) y = 0;
if (x + resizedWatermark.cols > originalImage.cols) {
resizedWatermark = resizedWatermark(Rect(0, 0, originalImage.cols - x, resizedWatermark.rows));
}
if (y + resizedWatermark.rows > originalImage.rows) {
resizedWatermark = resizedWatermark(Rect(0, 0, resizedWatermark.cols, originalImage.rows - y));
}
Rect roi(x, y, resizedWatermark.cols, resizedWatermark.rows);
Mat imageROI = originalImage(roi);
// 4. 将水印叠加到原始图片 (使用 Alpha 通道)
if (resizedWatermark.channels() == 4) { // 检查是否有 Alpha 通道
vector<Mat> channels;
split(resizedWatermark, channels); // 分离 B, G, R, Alpha 通道
Mat mask;
// Alpha 通道通常是最后一个通道 channels
// 如果你的 OpenCV 版本或水印图片通道顺序不同 (例如 ARGB),请相应调整
if (channels.size() == 4) {
mask = channels; // Alpha 通道作为掩码
} else {
// 如果分离后不是4通道,说明水印图本身就没有alpha,需要特殊处理或报错
// 这里我们创建一个全白的掩码,效果类似直接覆盖
mask = Mat(resizedWatermark.rows, resizedWatermark.cols, CV_8UC1, Scalar(255));
}
// 提取水印的 BGR 部分 (前三个通道)
Mat watermark_bgr;
vector<Mat> bgr_channels_vec;
if (channels.size() >= 3) {
bgr_channels_vec.push_back(channels);
bgr_channels_vec.push_back(channels);
bgr_channels_vec.push_back(channels);
merge(bgr_channels_vec, watermark_bgr);
// 将水印的 BGR 部分拷贝到 ROI,使用 Alpha 通道作为掩码
watermark_bgr.copyTo(imageROI, mask);
} else {
// 如果水印不是至少3通道,则直接拷贝(可能不是期望的效果)
resizedWatermark.copyTo(imageROI);
}
} else if (resizedWatermark.channels() == 3) { // 如果水印是 3 通道 BGR
// 可以选择直接覆盖或使用 addWeighted
// resizedWatermark.copyTo(imageROI); // 直接覆盖
// 或者使用 addWeighted 实现半透明
double alpha_blend = 0.7; // 透明度
addWeighted(imageROI, 1.0 - alpha_blend, resizedWatermark, alpha_blend, 0.0, imageROI);
} else {
cerr << "错误: 水印图片的通道数不受支持 (" << resizedWatermark.channels() << ")。" << endl;
// 可以选择直接拷贝单通道图像作为灰度水印
if(resizedWatermark.channels() == 1){
cvtColor(resizedWatermark, resizedWatermark, COLOR_GRAY2BGR); // 转为BGR再处理
resizedWatermark.copyTo(imageROI);
} else {
return -1;
}
}
// 5. 显示和保存结果
namedWindow("带水印的图片", WINDOW_AUTOSIZE);
imshow("带水印的图片", originalImage);
if (imwrite("output_with_watermark.jpg", originalImage)) {
cout << "成功保存带水印的图片: output_with_watermark.jpg" << endl;
} else {
cerr << "错误: 无法保存图片!" << endl;
}
waitKey(0);
destroyAllWindows();
return 0;
}
编译和运行:
你须要将上述代码保存为 .cpp 文件 (例如 add_watermark.cpp),并利用你的 C++ 编译器链接 OpenCV 库进行编译。
例如,利用 g++:
g++ add_watermark.cpp -o add_watermark $(pkg-config --cflags --libs opencv4)
./add_watermark
(如果你的 pkg-config 配置的是 opencv 而不是 opencv4,请相应修改)。
结论 🏁
通过 OpenCV,我们可以方便地在 C++ 步调中为图片添加各种类型的水印。无论是简单的文字水印还是带有透明结果的图片水印,OpenCV 都提供了相应的工具和函数来实现。关键在于理解图像的 ROI 操作以及如何有效地融合两个图像。盼望本文能帮助你乐成地为你的图片添加上自界说的水印!
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]