经过前面的知识,我们知道了一个 8 bit 的灰度数据数值是 0~255,代表着从黑到白。而二值图像则是灰度图的特殊化,那我们开动脑筋想一想,是否可以设置一下阈值来制造二值图像,使之能呈现一些特殊的效果呢?
答案是可以的,那本章博客就来整理一下FPGA实现钢笔画和浮雕画的实现。
一、钢笔画
钢笔画的实现比较简单,核心思想就是设置一个阈值,灰度图和阈值进行对比,从而输出全1或全0的图像。
1、MATLAB实现
1 %-------------------------------------------------------------------------- 2 % 钢笔画 3 %-------------------------------------------------------------------------- 4 clc; 5 clear all; 6 RGB = imread('Lenna.jpg'); %读取图像 7 gray = rgb2gray(RGB); %灰度图 8 9 [ROW,COL,N] = size(gray); %获得图像尺寸[高度,长度,维度] 10 pen = zeros(ROW,COL); 11 value = 80; 12 13 for i = 1:ROW 14 for j = 1:COL 15 pen(i,j) = gray(i,j); 16 if gray(i,j) > value 17 pen(i,j) = 255; 18 else 19 pen(i,j) = 0; 20 end 21 end 22 end 23 24 subplot(2,1,1);imshow(gray); title('灰度图'); 25 subplot(2,1,2);imshow(pen);title('钢笔画');
这里我的阈值 value 设置为100,它的范围是 0-255,点击运行,我们得到如下结果:
结果还是不错的,得到了钢笔画的效果,唉如果自己用笔能画得这么好看就好了 ^_^
2、FPGA实现
这个算法比较简单,Verilog写起来也是一气呵成,之前我们实现了 RGB565 转 YCbCr444,Y 分量代表灰度数据,把它提取出来做阈值比较处理即可,关键代码如下所示:
assign pen_de = Y_de; assign pen_hsync = Y_hsync; assign pen_vsync = Y_vsync; assign pen_data = (Y_data > value) ? 16'hffff : 16'h0000;
为了方便查看不同阈值带来的效果,阈值 value 由外部引入,从两个按键获得,按键key[0]增加阈值,按键[1]减小阈值。按键获得阈值的代码如下所示:
1 //************************************************************************** 2 // *** 名称 : key_value.v 3 // *** 作者 : xianyu_FPGA 4 // *** 博客 : https://www.cnblogs.com/xianyufpga/ 5 // *** 日期 : 2019-08-10 6 // *** 描述 : 按键获得阈值 7 //************************************************************************** 8 9 module key_value 10 //========================< 端口 >========================================== 11 ( 12 input wire clk , 13 input wire rst_n , 14 input wire [ 1:0] key_vld , 15 output reg [ 7:0] value 16 ); 17 //========================================================================== 18 //== 代码 19 //========================================================================== 20 always @(posedge clk or negedge rst_n) begin 21 if(!rst_n) begin 22 value <= 8'd0; 23 end 24 else if(key_vld[0]) begin 25 value <= value + 8'd10; 26 end 27 else if(key_vld[1]) begin 28 value <= value - 8'd10; 29 end 30 else if(value<10) begin //255不是10的倍数,跳过1-10数值 31 value <= 8'd0; 32 end 33 else if(value>245) begin //跳过251-255数值 34 value <= 8'd250; 35 end 36 end 37 38 39 40 endmodule
阈值的增减幅度设置为 10,阈值的范围是 0~255,但 255 不是10的整数倍,所以阈值为0又按下减阈值按键时,会出现246,236等数值,不好看,所以我设计时将阈值改为0-250。
此外,这个阈值除了提供给图像处理外,我还传给了数码管模块,这样就能直接看到当前的阈值了。
3、上板验证
当阈值为80时,我们得到如下的图像:
和MATLAB进行对比,除了因板卡坏了导致颜色失真外,效果是一样的,此次实验成功。
完整实验的视频如下所示:(板子有问题,黑色显示成了红色)
实现了钢笔画后,我们可以通过同样的方式,改变输出的灰度值来实现铅笔画,此外还有其他更特别的效果也可以采用多个阈值对不同颜色做输出处理。
二、浮雕效果
浮雕是雕刻的一种,雕刻者在一块平板上将他要塑造的形象雕刻出来,使它脱离原来材料的平面。浮雕是雕塑与绘画结合的产物,用压缩的办法来处理对象,靠透视等因素来表现三维空间,并只供一面或两面观看。浮雕一般是附属在另一平面上的,因此在建筑上使用更多,用具器物上也经常可以看到。由于其压缩的特性,所占空间较小,所以适用于多种环境的装饰。近年来,它在城市美化环境中占了越来越重要的地位。浮雕在内容、形式和材质上与原调一样丰富多彩。浮雕的材料有石头、木头、象牙和金属等。
本次设计同样是基于 YCbCr 的灰度数据Y分量来进行的,浮雕效果的算法公式为:NewPixel(i,j)= Pixel(i,j+1)- Pixel(i,j)+ value,其中 i 为图像高度,j 为图像宽度,pixel为像素点,value为阈值(0-255)。
1、MATLAB实现
1 %-------------------------------------------------------------------------- 2 % 浮雕画 3 %-------------------------------------------------------------------------- 4 clc; 5 clear all; 6 RGB = imread('sun.jpg'); %读取图像 7 gray = rgb2gray(RGB); %灰度图 8 9 [ROW,COL,N] = size(gray); %获得图像尺寸[高度,长度,维度] 10 emboss = zeros(ROW,COL); 11 value = 100; 12 13 for i = 1:ROW 14 for j=1:COL-1 15 emboss(i,j) = gray(i,j+1) - gray(i,j) + value; 16 if emboss(i,j) > 255 17 emboss(i,j) = 255; 18 elseif emboss(i,j) < 0 19 emboss(i,j) = 0; 20 else 21 emboss(i,j) = emboss(i,j); 22 end 23 end 24 end 25 emboss = uint8(emboss); 26 27 subplot(2,1,1);imshow(gray); title('灰度图'); 28 subplot(2,1,2);imshow(emboss);title('浮雕画');
原图如下所示:
阈值范围是 0-255,这里我设置为80,点击运行MATLAB,我们得到如下结果:
浮雕效果出来了,
2、FPGA实现
首先考虑一个问题,如何实现公式 NewPixel(i,j)= Pixel(i,j+1)- Pixel(i,j)+ value ,尤其是 j+1 怎么处理?答案是打拍,通过打拍获得 Y_data_r,它的数据就相当于是 j,而原数据就相当于是 j+1。
always @(posedge clk or negedge rst_n) begin if(!rst_n) begin Y_data_r <= 8'd0; end else begin Y_data_r <= Y_data; end end assign emboss = Y_data - Y_data_r + value; //-255 ~ 510
这里要注意一下 emboss 信号,它可能出现负数,因此我们得从极端情况来设计,极端情况下 Y_data、Y_data_r 和 value 是 0 或255,那 emboss 的值就是 -255~510之间,因此我们可以将emboss信号定义成有符号数: wire signed [ 9:0] emboss; 。
处理好这步后,就可以进行浮雕画的处理了,代码如下所示:
always @(posedge clk or negedge rst_n) begin if(!rst_n) begin emboss_data <= 16'h0000; end else if(emboss > 255) begin emboss_data <= 16'hffff; end else if(emboss < 0) begin emboss_data <= 16'h0000; end else begin emboss_data <= {emboss[7:3],emboss[7:2],emboss[7:3]}; end end
到这里数据方面就设计完成了,但是还没有完,千万千万不要忘了信号同步!行场信号、使能信号如果不同步,最后的图像肯定出问题。信号同步的代码如下所示:
//========================================================================== //== 信号同步 //========================================================================== always @(posedge clk or negedge rst_n) begin if(!rst_n) begin Y_de_r <= 2'b0; Y_hsync_r <= 2'b0; Y_vsync_r <= 2'b0; end else begin Y_de_r <= {Y_de_r, Y_de}; Y_hsync_r <= {Y_hsync_r, Y_hsync}; Y_vsync_r <= {Y_vsync_r, Y_vsync}; end end assign emboss_de = Y_de_r[1]; assign emboss_hsync = Y_hsync_r[1]; assign emboss_vsync = Y_vsync_r[1];
3、上板验证
和上面一样,我用按键提供value阈值,数码管实时显示当前阈值,当阈值为80时,得到如下结果:
和 MATLAB 效果是一样的,实验成功。
完整实验的视频如下所示:(板子有问题,黑色显示成了红色)
参考资料:[1] OpenS Lee:FPGA开源工作室(公众号)