FPGA实现视频图像的水印添加

时间:2024-02-29 16:09:50

  图像水印到处可见,如微博图片右下角会有原创者 ID 水印,很多 PDF 文件中也夹有水印,而视频图像同样可以添加水印,最著名的视频图像水印便是电视台的标志了。图像水印在文化产权上起到非常重要的作用,对所有者的权益起到一定的保护。

  数字图像的水印叠加公式为:fx = (1-a)f + aw
  未加水印的图像表示为 f,水印表示为 w,常数 a 控制水印和衬底图像的相对可见性。如果 a 为 1,则水印是不透明的,并且衬底图像完全是暗的;随着 a 接近 0,会逐渐看到更多的衬底图像和更少的水印,通常 a 在 0 和 1 之间。
  本篇博客整理一下如何使用 FPGA 为视频图像添加自定义的水印图案。
 
一、水印图像
  水印图像可以自己找一张喜欢的图片或字符,但不要太大,否则就喧宾夺主了。如下是我本次实验选取的水印图案:
  这是我博客的头像,我将其分辨率改为 50x50。
 
二、图像转mif文件
  网上很多图像转 16位 mif 文件的小软件,当然如果你 MATLAB 学的好,也可以用 MATLAB 来实现。此次我使用的是正点原子开发的 “PicToMif_V1.0.exe”。
 
三、rom生成
  rom的位宽设置为 16,深度设置为 2500(水印图像分辨率),然后把第二步生成的 mif 文件包含进去即可,不添加锁存器,让数据输出的延时为1个时钟周期,rden信号也不需要,有读地址就够了。
 
四、添加水印图案的设计
1、直接添加水印
  可以单独设计一个 .v 模块为水印图案模块,这个设计代码量小,我直接写在顶层模块的 TFT_driver 之后,连接管脚之前。代码如下所示:
//==========================================================================
//==                        TFT
//==========================================================================
TFT_driver u_TFT_driver 
(
    .clk                    (clk_10m                ), 
    .rst_n                  (rst_n                  ),
    .TFT_req                (rd_en                  ),
    .TFT_x                  (TFT_x                  ),
    .TFT_y                  (TFT_y                  ),
    .TFT_din                (rd_data                ),
    .TFT_clk                (TFT_clk                ),
    .TFT_de                 (TFT_de                 ),
    .TFT_pwm                (TFT_pwm                ),
    .TFT_hsync              (TFT_hsync              ),
    .TFT_vsync              (TFT_vsync              ),
    .TFT_data               (rgb                    )   
);
//==========================================================================
//==                        添加视频水印
//==========================================================================
mark_rom u_mark_rom
(
    .clock                  (clk_10m                ),
    .address                (rom_addr               ),
    .q                      (rom_data               )
);
//--------------------------------------------------------------------------
//水印范围
assign rom_rden = (TFT_x >= 1) && (TFT_x <=50) && 
                  (TFT_y >= 1) && (TFT_y <=50);

//rom地址
always @(posedge clk_10m or negedge rst_n) begin
    if(!rst_n)
        rom_addr <= 12\'b0;
    else if(rom_addr==2500-1)
        rom_addr <= 12\'b0;
    else if(rom_rden)
        rom_addr <= rom_addr + 1\'b1;
end

//数据显示 
always @(*) begin
    if(rom_rden) begin
        TFT_data <= rom_data;      //显示水印
    end
    else
        TFT_data <= rgb;               //显示原图
end

   其中 rgb 是原本输出到管脚上的,拿来到“添加视频水印”模块做进一步的处理,而 TFT_x、TFT_y 信号是 TFT_driver 里就设计好的坐标信号,这里不得不又说一下,模块设计的好,移植性就非常强!

  如果你的 TFT 或 VGA 驱动模块没有设计坐标信号,那么在后面用 de 使能信号设计一下就行,其实就是行列规划的计数器而已。

  OK来看看下效果:

  设计成功!我的FPGA开发板引脚有问题,那些红点并不是本工程的 bug 。
 
2、半透明水印
  上面添加的水印比较生硬,可不可以美化一下呢?是可以的,我们可以让其变得半透明,并且会随着图像的变换跟着变换,仿佛是融入了图像内部一样。
  代码如下所示:
always @(*) begin
    if(rom_rden) begin
        TFT_data <= rom_data + rgb;   //显示水印
    end
    else
        TFT_data <= rgb;              //显示原图
end

   将 rom_data 和 rgb 数据相加,最后实现的效果如下所示:

  ......好像有点打脸?
  是打脸,也不是打脸,怎么说呢?因此我的水印图案是彩色缤纷的,和原本像素值相加后效果不好,白色的略去了,但是其他色彩也变形了。但是如果我们把水印图案的主要符号选择为黑色,效果就会非常好,感兴趣的同学可以试试。
 
3、略去背景的水印图案
  如果不喜欢上面第2种效果,那我们再改进一下吧,水印图案的输出就不进行和原像素相加了。观察一下我的水印图案,背景是白色的,因此我们可以想办法把白色背景略去,只保留中心的图案。代码如下所示:
always @(*) begin
    if(rom_rden) begin
        if(rom_data==16\'hffff)              //背景白色显示原图
            TFT_data <= rgb;
        else
            TFT_data <= rom_data;// + rgb;  //否则显示水印
    end
    else
        TFT_data <= rgb;                    //显示原图
end

   对水印图案进行判断,如果是白色,则显示原图,否则显示水印,非水印区域也显示原图。最后效果如下所示:

  效果还是不错的,水印图案还是没有选的特别好,头发那有些白点没有略去,那的像素并不是肉眼以为的纯白,而是一些像白色的灰度数据。如果要达到最好的效果,可以好好对水印图案进行一番选择。
  实验整体效果如下所示:
  从视频上可以看到,最后的效果还是不错的,可惜我的板卡有问题,很多地方有红点,而且颜色有些失真了,此外 OV7670 摄像头的成像效果本身也不行。不过我们关注水印就够了,本次FPGA实现视频图像的水印添加实验宣告成功!
 
后记
  熟悉rom的同学会发现本工程的代码时序应该再调整一下,因为ROM给出地址到出数据是有一定延迟的。但是本工程只是为了看看水印效果,要时序完美对齐也很简单,但代码量明显增多,de,vsync,hsync等都得拿出来打拍。而且现在这样效果挺完美,就这样吧。
 
 
参考资料:[1]OpenS Lee:FPGA开源工作室(公众号)