YUV图像的水印的添加

来源:互联网 发布:淘宝卖家退款流程 编辑:程序博客网 时间:2024/06/03 11:16

小白一个,开始研究YUV,记录一下心得,如果有什么说得不对的地方,请及时纠正吐舌头

最近需要实现一个YUV增加水印的功能,作为小白完全没有思路。后来一个前辈给了一个方案:将水印图案转换为YUV,覆盖到需要添加水印的地方。


ffmpeg -i waterMark.png -pix_fmt nv21 waterMark.yuv

转换为yuv之后,将该YUV数据覆盖到源yuv图像上,即可完成水印的添加,源码如下


 void yuvAddWaterMark(int yuvType, int startX, int startY, unsigned char *waterMarkData,                 int waterMarkW, int waterMarkH, unsigned char *yuvData, int yuvW, int yuvH) {   int i=0;   int j=0;   int k=0;   switch(yuvType) {     case NV21OR21:          for(i=startY; i<waterMarkH+startY; i++) {            memcpy(yuvData+startX+i*yuvW, waterMarkData+j*waterMarkW, waterMarkW);            j++;          }          for(i=startY/2; i<(waterMarkH+startY)/2; i++) {            memcpy(yuvData+startX+yuvW*yuvH+i*yuvW, waterMarkData+waterMarkW*waterMarkH+k*waterMarkW, waterMarkW);            k++;          }          #ifdef SAVE_RET          FILE *outPutFp = fopen("Final.yuv", "w+");          fwrite(yuvData, 1, yuvW*yuvH*3/2, outPutFp);          fclose(outPutFp);          #endif          break;     case YUV420SP:      //Not FInished             break;     default:     //Not FInished             break;   } }

参数的含义:yuvType            --------------------------   yuv类型,目前只支持NV12和NV21

                    startX,startY      --------------------------   需要添加水印的位置

                    waterMarkData --------------------------   水印YUV数据,可以通过读取水印文件获取

                    waterMarkW,waterMarkH  --------------------------  水印数据的分辨率

                    yuvData  --------------------------  源YUV图像数据

                    yuvW,yuvH  --------------------------  源YUV的分辨率


测试结果如下:

添加水印前的yuv图片



添加水印后的yuv图像



但是对于一些边缘透明的png图片转换为yuv之后,边缘就会变为黑色不透明的,加了水印后可能就是这个效果

这样的效果肯定是不行的,现在就要想办法怎么把讨厌的黑边去掉,现在来分析一下所谓的google logo的yuv文件


用16进制打开google logo的YUV水印文件如下:




从头到尾大致看了一下发现0x10和0x80特别多,因此可以大胆的猜测一下,在Y分量中0x10可能是黑色的,在uv分量中0x80可能是黑色的,

为了验证猜想,做如下计算:
NV21的格式前w*h存储Y分量,后w*h/2存储uv分量,所以在1771*2/3=1181行左右的时候是Y和UV分量的分界,所以在1181行左右会有明显0x10和0x80分界,跳转到该行果然如此:


所以在覆盖YUV水印数据的时候,只要不拷贝0x10和0x80就好。我个人的思路是先在源YUV图像需要添加水印的地方裁剪一块宽和高分别为水印的宽和高的数据,获取一段buffer,然后在这段buffer里拷贝水印文件中除了0x10和0x80外别的数据,最后将拷贝完成后的buffer当作水印添加到源YUV文件中。

关于yuv的裁剪请参考我的另一篇博客 http://blog.csdn.net/magic_frank/article/details/70941111

拷贝除了0x10和0x80外别的数据的实现如下:
int fCutWaterMark(unsigned char *waterMarkSrc, unsigned char *srcYuv, int waterMarkW, int waterMarkH) {int i;unsigned char tmpData[waterMarkW*waterMarkH*3/2];unsigned char tmpWaterMark[waterMarkW*waterMarkH*3/2];memcpy(tmpData, srcYuv, waterMarkW*waterMarkH*3/2);memcpy(tmpWaterMark, waterMarkSrc, waterMarkW*waterMarkH*3/2);for(i=0; i<waterMarkW*waterMarkH*3/2; i++) {if(tmpWaterMark[i]!=0x10 && tmpWaterMark[i]!=0x80 && tmpWaterMark[i]!=0xeb) {tmpData[i] = tmpWaterMark[i];// printf("0x%X\n", tmpData[i]);}}memcpy(waterMarkSrc, tmpData, waterMarkW*waterMarkH*3/2);#if 1FILE *tarFp = fopen("afterCutWaterMark.yuv", "w+");fwrite(waterMarkSrc, 1, waterMarkW*waterMarkH*3/2, tarFp);fclose(tarFp);#endifreturn 0;}

在本例程中的执行完水印数据的拷贝后,结果如下:

最后将afterCutWaterMark当作水印添加到源YUV中即可,最终结果如下:

整个过程调用如下:
1.在要源图像要添加水印的地方裁剪,获取一段buffer
void cutYuv(int yuvType, unsigned char *tarYuv, unsigned char *srcYuv, int startW,              int startH, int cutW, int cutH, int srcW, int srcH)
获取到的buffer用yuv播放器播放如下:

2.将水印YUV中除了0x10和0x80之外的数据拷贝到刚才获取的buffer中
fCutWaterMark(unsigned char *waterMarkSrc, unsigned char *srcYuv, int waterMarkW, int waterMarkH)
拷贝过后的buffer用yuv播放器播放如下:
3.将拷贝处理过的buffer当作水印添加到源YUV图像中
void yuvAddWaterMark(int yuvType, int startX, int startY, unsigned char *waterMarkData,                 int waterMarkW, int waterMarkH, unsigned char *yuvData, int yuvW, int yuvH)
添加过后的源图像用yuv播放器播放如下:

最终实现了水印的添加。

Android系统可以用bitmap将png或jpg图片转换为yuv,然后将获取到的Camera Preview流逐帧加上水印后再编码,可以实现拍摄的视频带水印的功能。