问题并不复杂,打开图片和作为水印的logo,然后再读取图片中作为镂空的背景部分。接着把logo镂空部分去除,然后复制到目标图片上就完成了工作,主要的工作代码只有7行。
主要函数使用copyTo,点击链接是opencv官方的说明文档。
opencv的编译,需要在命令行给出头文件和链接库的额外参数,建议写一个脚本来编译,这里也贴出来(本例中使用当前的opencv4):
#!/bin/bash
g++ -std=c++11 -o $1 $1.cpp `pkg-config --cflags --libs opencv4`
使用脚本来编译和执行使用如下命令(假设源码名称为wmv1.cpp):
$ ./mkcv4.sh wmv1
$ ./wmv1
在一张样本的图片上运行这个程序,得到的结果效果如下:
版本2
不管有多么不高兴,生活总要继续,工作也得推动下去。
其实用户挑毛病永远不是最可怕的,可怕的是用户不挑毛病,并且还不买单。
所以既然用户有反馈,我们逐条解决就好了。
首先看“水印效果”的问题,opencv中有专门的函数addWeighted处理两幅图片之间的重叠互动问题。用起来更简单,连蒙版mask部分都不需要了:
const float _alpha=0.5;
Mat image = imread(picfile);
Mat logo = imread(logofile);
Mat imageROI;
imageROI = image(Rect(mx,my,logo.cols,logo.rows));
addWeighted(imageROI, 1.0, logo, _alpha, 0, imageROI);
imwrite(outputfile,image);
水印尺寸偏大的问题,水印文件本身肯定是固定的。但在大的图片中,水印肯定显得小,小的图片中,水印就会显得大。因此需要水印图片的尺寸是可以变化的,是一个合理的需求。
opencv中调整图片的尺寸很容易,我们可以要求用户输入一个水印logo尺寸的宽度,随后保持logo的比例,计算出来logo的新高度。然后调整logo的尺寸就可以了。
int neww,newh;
neww = (int)_logowidth;
newh = (int)(logo.rows * ((float)neww / logo.cols));
Size dsize=Size(neww,newh);
resize(logo,logo,dsize);
文件名、logo位置问题,都可以由程序运行时,用户输入的参数来确定,这个再简单不过。
很快,第二版新鲜出炉:
#include
#include
#include
#include
using namespace std;
using namespace cv;
#define PATH_MAX 1024
const float _alpha=0.5;
char _picfile[PATH_MAX];
char _outputfile[PATH_MAX];
char _logofile[PATH_MAX];
int _logowidth;
int _mx,_my;
int main(int argc, char **argv){
if (argc != 7) {
printf("Wrong parament!\n");
return 1;
}
strcpy(_picfile,argv[1]);
strcpy(_outputfile,argv[2]);
strcpy(_logofile,argv[3]);
_logowidth=atol(argv[4]);
_mx=atol(argv[5]);
_my=atol(argv[6]);
Mat image = imread(_picfile);
Mat logo = imread(_logofile);
Mat imageROI;
int neww,newh;
neww = (int)_logowidth;
newh = (int)(logo.rows * ((float)neww / logo.cols));
Size dsize=Size(neww,newh);
resize(logo,logo,dsize);
imageROI = image(Rect(_mx,_my,logo.cols,logo.rows));
addWeighted(imageROI, 1.0, logo, _alpha, 0, imageROI);
imwrite(_outputfile,image);
}
我们再次编译、执行来试一试:
$ ./mkcv4.sh wmv2
$ ./wmv2 IMG_20190521_125150.jpg IMG_20190521_125150-logoed.jpg logo.png 150 100 100
得到的图片如下:
3) || (_position<0))
_position=0;
break;
case \'c\':
_copy = 1; //meas true
break;
default:
usage();
}
}
if (strlen(_srcFilename) == 0) {
usage();
exit(1);
};
if (strlen(_dstFilename) == 0) {
addPostfix(_srcFilename,_dstFilename);
};
return 0;
}
/*
position = 0, logo on right,bottom
position = 1, logo on left,bottom
position = 2, logo on left,top
position = 3, logo on right,top
*/
void getPosition(int position,Mat image,Mat logo,int *X,int *Y){
// x/y _margin using image.cols,not rows
switch(position){
case 0:
*X=(image.cols-logo.cols) - (image.cols * _margin);
*Y=(image.rows-logo.rows) - (image.cols * _margin);
break;
case 1:
*X=image.cols * _margin;
*Y=(image.rows-logo.rows) - image.cols * _margin;
break;
case 2:
*X=image.cols * _margin;
*Y=image.cols * _margin;
break;
case 3:
*X=(image.cols-logo.cols) - (image.cols * _margin);
*Y=image.cols * _margin;
break;
default:
*X=(image.cols-logo.cols) - (image.cols * _margin);
*Y=(image.rows-logo.rows) - (image.cols * _margin);
break;
};
return;
}
void markIt(const char *srcpic, const char *logopic, const char *dstpic, int position=0){
Mat image = imread(srcpic);
Mat logo = imread(logopic);
Mat imageROI;
int markx,marky;
Mat mask=imread(logopic,0);
if (_scale <1){
float scale=(image.cols * _scale) / logo.cols;
Size dsize=Size(logo.cols*scale,logo.rows*scale);
resize(logo,logo,dsize);
resize(mask,mask,dsize);
} else if(_scale > 1) {
int neww,newh;
neww = (int)_scale;
newh = (int)(logo.rows * ((float)neww / logo.cols));
Size dsize=Size(neww,newh);
resize(logo,logo,dsize);
resize(mask,mask,dsize);
};
logo.rows);
getPosition(position,image,logo,&markx,&marky);
imageROI = image(Rect(markx,marky,logo.cols,logo.rows));
if (_copy){
logo.copyTo(imageROI,mask);
} else {
addWeighted(imageROI, 1.0, logo, _alpha, 0, imageROI);
}
imwrite(dstpic,image);
}
int main(int argc, char **argv){
getOptions(argc,argv);
dumpDefault();
markIt(_srcFilename,_logoFilename,_dstFilename,_position);
return 0;
}
从完成的程序代码上看同样也是如此,大量的代码都是用于处理参数和默认值逻辑,实际加水印的代码,几乎没有什么变化。
技术人员不能只沉迷于技术,技术人员的升职加薪,往往得益于其它经验的积累,比如行业经验,比如沟通协调经验。
假设我们当前目录准备了一张图片叫DSCF2183.jpg:
这是最简的运行模式,只需要一个输入文件。水印文件自动缩放到目标图片宽度的30%,然后透明叠加在右下角:
补充
作为一个命令行程序,第三版已经基本可以满足应用见用户了。忘了提醒你注意附加在程序内部的程序使用文档,千万注意保证文档的完善、准确。很多优秀的产品,用户能不能用的好,往往是由文档的水平决定的。
回到最初的话题,如果是自己作为这个用户,那还有一个小需求没有被满足。那就是,我的图片量很大,并且分布在多篇游记的复杂目录结构中。如何同时为多幅图片添加水印?
这算的上非常个性化的需求,当然可以实现在程序中。但在没有大量用户支持的情况下,这种需求可能只是增加了程序的复杂度,但并没有多少人用。
对于这种需求,完全可以使用外围脚本的形式来解决。使用bash写这样的脚本,也不过几行代码而已:
#!/bin/bash
files=$(find $1 -name "*jpg" -o -name "*png" -o -name "*jpeg")
for file in $files
do
wmv3 -i $file -o $file
done
把脚本设置为可执行,然后把脚本和主程序都拷贝到系统的可执行文件夹:
$ chmod +x markall.sh
$ sudo cp markall.sh /usr/bin
$ sudo cp wmv3 /usr/bin
这次为再多的图片加水印也不怕了,比如我们有一个测试文件夹,是这样的结构:
只要如此执行就可以为文件夹下面,及其子文件夹中所有的jpg/jpeg/png文件添加水印:
$ markall.sh test
至此,才可以真的完活,收工!