当前位置:   article > 正文

基于FPGA的数字图像处理- 图像分割【4.8】_fpga 图片画面拆分

fpga 图片画面拆分

10.4 基于FPGA的局部自适应分割

在本节中,我们将针对FPGA设计高斯局部自适应分割算法,并对 算法进行仿真验证。

10.4.1 算法转换

在10.3节中我们介绍了自适应分割的原理,这里不妨再把公式列 出来如下:

由公式可以看出,窗口的分割值是对图像进行开窗,并计算窗口 内的像素均值和标准差,分割值为像素均值和标准差的加权和。 在软件中,不考虑计算效率的情况下,这个计算是轻而易举的事 情。但是,我们注意到,在计算分割值的过程中,首先要计算窗口内 像素的方差,然后才能对方差进行开方计算标准差。在FPGA里面计算 开方是一件费时费力的工作,我们将尝试对不等式进行一个等价转 换。 为了便于理解,不妨假定目前的输入像素值为din,经算法处理后 的输出数据为dout,按照式(10-28),输出dout的计算方法如下:

式(10-32)中,我们只需得到当前数据din,计算当前窗口内均 值和方差即可,这样就可避免了开方操作,简化了系统设计。 将式(10-27)代入式(10-32)中,则有

10.4.2 FPGA结构设计

同样,为充分利用FPGA的并行特性,我们采用流水线结构来实现 上述设计。由最终的算法等效表达式可知,FPGA需要完成以下计算工 作:

(1)计算当前窗口内的像素均值μ。 (2)计算当前窗口中心像素与均值之差的平方(din-μ)2 。 (3)将上式与225相乘,完成不等式左边的计算。 (4)计算当前窗口内225个所有像素值与均值之差的平方和,完 成不等式右边的计算。 (5)将第(3)步和第(4)步的结果进行比较,完成图像分割。 (6)完成行列对齐与边界处理。 根据以上设计步骤,我们给出FPGA的顶层设计框图如图10-13所 示。

        由图10-13可以看出,要完成图像的局部高斯分割工作,需要调用 一个均值计算模块mena_2d来计算当前窗口内的像素均值μ。 同时,为了在“同一时刻”计算出当前窗口内所有像素与窗口均 值的差平方,还必须要对以当前像素为中心的窗口的所有225个像素进 行缓存。对15×15个像素的缓存不是一件非常容易实现的事情,这里将指定尺寸的窗口缓存封装成一个模块,记为win_buf模块,把这个模 块的当前输出像素记为din_buf。 不等式左边的计算是非常简单的,窗口缓存的中间值即为当前像 素值,记为din_org,与均值做减法,求平方后再乘以225即可得到。 现在得到了窗口均值μ和当前窗口的像素队列225个din_buf,需 要做的是把窗口内225个din_buf分别与均值相减后计算平方。接下来 的工作就是把上面225个差平方的结果求和。同样,15×15个数的加法 运 算 也 是 非 常 麻 烦 的 , 这 里 也 将 会 其 封 装 成 一 个 模 块 , 记 为 add_tree。Add_tree的输出记为不等式右边结果。 将不等式进行比较,利用比较结果对原图像进行分割即可。

10.4.3 子模块设计

        在7.2节中,我们已经详细介绍了窗口均值的计算方法,在这里不 再详细介绍了。本节将详细介绍一个新的窗口缓存模块win_buf,以及 多数据累加模块add_tree。 1.窗口缓存模块win_buf 本模块不做任何算法上的处理,只是负责将当前输入像素的二维 窗口元素缓存并组成一个一维的向量输出。 模块的构建非常简单,对图像分别做行列方向的延迟即可。对于 行方向上的延迟,可以采用行缓冲来实现,对于列方向上的延迟,则 采用寄存器来实现。 设定需要缓存的窗口尺寸为KSZ,则需要KSZ-1个行缓存,以及 KSZ×KSZ个寄存器来实现。 我们将以7×7的窗口缓存模块为例来说明,其设计框图如图10-14 所示。

我们不难把这个电路扩展到尺寸为15×15的窗口中去,在图10-14 中也重点标注出了窗口中心像素点。这个中心像素点就代表当前处理 的像素中心,实际图中的7×7矩阵就是当前像素的开窗缓冲区。当然 也可以用此电路来实现图像的卷积运算,例如排序、Sobel算子、高斯 滤波,或者均值求取等。 2.数据累加模块add_tree 数据累加模块负责将窗口内所有元素与均值之差的平方相加,这 里还是采用之前的加法思路:每个加法器限制两个输入,这样,对于 225个数据,在第一个时钟,共有112对数据进行相加。同时把剩余的 一个数据进行缓存,第二个时钟有56对数据进行相加,同时将之前的 数据缓存,依此类推,如图10-15所示。

问题的关键在于如何描述这一个电路。当然可以采用最笨的方 法:将上述电路中所有的寄存器及加法器和中间信号均描述出来。这 样带来的工作量是非常大的,带来的问题还容易出错,代码可维护性 和可读性极差。 如果再仔细分析上述框图,就很容易发现规律:除了最后一个时 钟直接完成两个数的相加,在其他的每个时钟内,加法运算电路都是 相似的,不同的只是待加的数目不同。 为了总结出这个规律,我们列出几个时钟如下: 第1个时钟:需完成225个数据相加,共需112个加法器,113个寄 存器。 第2个时钟:需完成113个数据相加,共需56个加法器,57个寄存 器。 第3个时钟:需完成57个数据相加,共需28个加法器,29个寄存 器。

  1. 对应的C语言算法如下所示。
  2. /*num 为数组的长度,a 为待求和的数组*/
  3. /*递归结束条件为最后只剩两个元素相加*/
  4. int sum(int num,int *a)
  5. {
  6. int y[100]; //缓存if (num==2)
  7. {
  8. return (a[0]+a[1]); //直到最后剩下两个数据相加作为最
  9. 后的和返回
  10. }
  11. else
  12. {
  13. int temp=num;
  14. num=num/2;
  15. for (int i=0;i<num;i++)
  16. {
  17. y[i]=a[2*i]+a[2*i+1];
  18. }
  19. if (temp%2==0) //如果个数能被 2 整除,则没有单独剩余的
  20. 数据
  21. {
  22. sum(num,y); //对此新向量进行递归计算
  23. }
  24. else //否则就会剩一个数据无法配对,
  25. {
  26. y[num]=a[temp‐1]; //对单独的数据进行缓存
  27. sum(num+ num %2,y); //将此数据和前面形成的向量组成一个
  28. 新向量
  29. }
  30. } }

读者需注意,到目前为止,我们所有的讨论都是基于图10-15所描 述的电路图,只是用了另外一种电路描述方式而已,我们将在后面详 细介绍递归求和的代码设计。 图10-16是递归求和的框图,可见,我们只是把图10-15后面的部 分重新封装起来了而已。

3.顶层模块gauss_segment_2d 有了以上几个模块,顶层的设计就十分简单了。需实例化一个均 值求取模块mean_2d,求取当前窗口的均值,实时实例化一个窗口缓存 模块win_buf。需要注意的是,均值求取模块需要一定的latency,需 要将输入数据预期延迟对齐后再进行窗口缓存。Winbuf输出中心像素与均值进行差平方运算后,再进行乘255运算计算不等式左边结果;输 出其他像素分别与均值进行差平方运算,将计算结果送入例化的 add_tree模块计算和,作为不等式右边结果,最后根据比较结果完成 图像分割。计算框图如图10-17所示。

10.4.4 Verilog代码设计

1.窗口缓存模块win_buf
模块定义如下:

  1. module win_buf(
  2. rst_n,
  3. clk,
  4. din_valid, //输入有效
  5. din, //输入数据流
  6. dout, //输出向量
  7. dout_org, //输出中心像素值
  8. vsync, //输入场同步
  9. vsync_out, //输出场同步is_boarder, //边界信息
  10. dout_valid //输出有效
  11. );
  12. parameter DW = 14; //数据位宽
  13. parameter KSZ = 15; //处理窗口
  14. parameter IH = 512; //图像高度
  15. parameter IW = 640; //图像宽度
  16. input rst_n;
  17. input clk;
  18. input din_valid;
  19. input [DW-1:0]din;
  20. output [DW*KSZ*KSZ-1:0]dout; //输出为KSZ*KSZ向量
  21. output [DW-1:0]dout_org; //输出中心像素
  22. input vsync;
  23. output vsync_out;
  24. output is_boarder;
  25. output dout_valid;
  26. localparam num_all = KSZ * KSZ; //窗口数据总数
  27. //窗口寄存器
  28. reg [DW-1:0]p[0:num_all-1];
  29. //例化KSZ-1个行缓存
  30. generate
  31. begin : line_buf
  32. genvar i;
  33. for (i = 0; i <= KSZ - 2; i = i + 1)
  34. begin : buf_inst
  35. line_buffer #(DW,IW)line_buf_inst(
  36. .rst(rst_all),
  37. .clk(clk),
  38. .din(line_din[i][DW - 1:0]),
  39. .dout(line_dout[i][DW - 1:0]),
  40. .wr_en(line_wren[i]),
  41. .rd_en(line_rden[i]),
  42. .empty(line_empty[i]),
  43. .full(line_full[i]),
  44. .count(line_count[i])
  45. );
  46. end
  47. end
  48. endgenerate
  49. //将输入接入延时电路
  50. if (valid == 1'b1)
  51. p[0]<= #1 din;
  52. //列延迟电路
  53. for (k = 0; k <= KSZ - 1; k = k + 1)
  54. for (j = 1; j <= KSZ - 1; j = j + 1)
  55. if ((line_valid[k * KSZ + j - 1]) == 1'b1)
  56. p[k * KSZ + j]<= #1 p[k * KSZ + j - 1];
  57. //行延迟电路
  58. for (k = 1; k <= KSZ - 1; k = k + 1)
  59. if ((line_rden[k - 1]) == 1'b1)
  60. p[k * KSZ]<= #1 line_dout[k - 1];
  61. //输出窗口缓存generate
  62. begin : xhdl2
  63. genvar i;
  64. for (i = 1; i <= num_all; i = i + 1)
  65. begin : out_dat_gen
  66. assign dout[i * DW - 1:(i - 1) * DW]= p[i - 1];
  67. end
  68. end
  69. endgenerate

//输出中心像素 

assign dout_org = p[med_idx]; 2.累加和模块add_tree 模块完成指定宽度的数据向量的加法运算,模块定义如下:

  1. module add_tree(
  2. rst_n,
  3. clk,
  4. din_valid,
  5. din, //输入数据向量
  6. dout, //输出和
  7. dout_valid
  8. );
  9. parameter DW = 14; //本次递归的数据位宽
  10. parameter KSZ = 225; //本次递归的尺寸
  11. localparam KSZ_new = (((KSZ) >> 1) + (KSZ%2));//下次递
  12. 归的尺寸
  13. localparam HALF_EVEN = (KSZ >> 1); //本次需做加法的数目
  14. localparam DW_new = (DW + 1); //下次递归的数据位宽input rst_n;
  15. input clk;
  16. input din_valid;
  17. input [KSZ*DW-1:0]din; //输入数据为KSZ的数据向量
  18. output [2*DW-1:0]dout; //输出位宽定为2*DW,需注意不要溢
  19. output dout_valid;
  20. reg [DW:0]dout_r;
  21. reg dout_valid_r;
  22. reg dout_valid_tmp;
  23. reg [DW:0]din_reg;
  24. reg [KSZ_new*(DW+1)-1:0]dout_tmp;
  25. wire [2*DW_new-1:0]dout_tmp2;
  26. wire dout_valid_tmp2;
  27. assign dout = dout_tmp2[DW * 2 - 1:0];
  28. assign dout_valid = dout_valid_tmp2;
  29. generate //最后一次递归调用 只剩最后两个数据 直接相加即可
  30. if (KSZ == 2)
  31. begin : xhdl2
  32. always @(posedge clk)
  33. begin
  34. dout_r <= #1 ({1'b0,din[DW - 1:0]} + din[2 * DW -
  35. 1:DW]);
  36. dout_valid_r <= #1 din_valid;
  37. end
  38. assign dout_tmp2[DW:0]= dout_r;
  39. assign dout_tmp2[DW * 2 - 1:DW + 1]= {DW-1{1'b0}};assign dout_valid_tmp2 = dout_valid_r;
  40. end
  41. endgenerate
  42. //中间递归调用
  43. generate
  44. if ((~(KSZ == 2)))
  45. begin : xhdl3
  46. begin : xhdl0
  47. genvar i;
  48. for (i = HALF_EVEN; i >= 1; i = i - 1)//两个两个进
  49. 行加法运算
  50. begin : gen_add_pipe
  51. always @(posedge clk)
  52. begin
  53. if(din_valid==1'b1)
  54. dout_tmp[(i) * (DW + 1) - 1:(i - 1) * (DW
  55. + 1)]<=
  56. #1(({1'b0,din[(i * 2) * DW - 1:(i * 2)
  57. * DW - DW]}) +
  58. din[(i * 2 - 1) * DW - 1:(i * 2 - 1) *
  59. DW - DW]);
  60. end
  61. end
  62. end
  63. always @(posedge clk)
  64. dout_valid_tmp <= #1 din_valid;//输入尺寸为奇数 必然剩一个无法配对 需将其缓存 同时与加
  65. 法结果组成新的向量进行下一次递归运算
  66. if (KSZ % 2 == 1)
  67. begin : xhdl4
  68. always @(posedge clk)
  69. din_reg[DW:0]<=#1({1'b0,din[KSZ*DW-1:(KSZ-
  70. 1)*DW]});
  71. always @(din_reg)
  72. dout_tmp[KSZ_new * (DW + 1) - 1:(KSZ_new - 1) *
  73. (DW + 1)]
  74. <= din_reg;
  75. end
  76. //进行下一次递归运算
  77. add_tree #(DW_new,KSZ_new)
  78. addtree_inst(
  79. .rst_n(rst_n),
  80. .clk(clk),
  81. .din_valid(dout_valid_tmp),
  82. .din(dout_tmp[KSZ_new * (DW + 1) - 1:0]),
  83. .dout(dout_tmp2),
  84. .dout_valid(dout_valid_tmp2)
  85. );
  86. end
  87. endgenerate
  88. endmodule

3.顶层模块gauss_segment_2d
//模块定义如下:
 

  1. module gauss_segment_2d(
  2. rst_n,
  3. clk,
  4. din_valid, //输入有效
  5. din, //输入数据
  6. dout, //输出数据
  7. vsync, //输入场同步
  8. vsync_out, //输出场同步
  9. dout_valid //输出有效
  10. );
  11. //首先进行均值运算
  12. Mean_2D #(DW,KSZ,IH,IW)
  13. mean(
  14. .rst_n(rst_n),
  15. .clk(clk),
  16. .din(din),
  17. .din_valid(din_valid),
  18. .din_valid_delay(din_valid_delay),
  19. .din_delay(din_delay),
  20. .dout_valid(mean_valid),
  21. .vsync(vsync),
  22. .vsync_out(mean_vsync),
  23. .is_boarder(is_boarder_mean),
  24. .dout(dout_mean) //均值结果
  25. );
  26. //输入延时 等待均值运算结束
  27. always @(posedge clk)begin
  28. if (rst_all == 1'b1)
  29. begin
  30. din_valid_delay_r <= {mean_latency{1'b0}};
  31. din_delay_r <= {mean_latency*DW-1+1{1'b0}};
  32. end
  33. else
  34. begin
  35. din_valid_delay_r <= #1
  36. ({din_valid_delay_r[mean_latency -
  37. 2:0],din_valid});
  38. din_delay_r <= #1
  39. ({din_delay_r[(mean_latency - 1) * DW -
  40. 1:0],din});
  41. end
  42. end
  43. //缓存当前窗口
  44. win_buf #(DW,KSZ,IH,IW)
  45. orignal_buf(
  46. .rst_n(rst_n),
  47. .clk(clk),
  48. .din_valid(din_valid_delay_r[mean_latency -
  49. 1]),
  50. .din(din_delay_r[mean_latency*DW-1:
  51. (mean_latency-1)*DW]),
  52. .dout(din_new),
  53. .dout_org(din_org),.vsync(vsync),
  54. .is_boarder(new_boarder),
  55. .dout_valid(din_new_valid)
  56. );
  57. //计算窗口内所有像素的差平方
  58. generate
  59. begin : xhdl0
  60. genvar i;
  61. for (i = 0; i <= KSZ * KSZ - 1; i = i + 1)
  62. begin : cal_square//首先计算差值
  63. always @(*) diff_tmp[i]<= ((({din_new[(i +
  64. 1) * DW - 1:(i)
  65. * DW],4'b0000}) > ({dout_mean[DW +
  66. 2:0],1'b0}))) ?
  67. (({din_new[(i + 1) * DW - 1:(i) *
  68. DW],4'b0000}) -
  69. ({dout_mean[DW + 2:0],1'b0})) :
  70. (({dout_mean[DW + 2:0],1'b0}) -
  71. ({din_new[(i + 1) * DW-
  72. 1:(i) * DW],4'b0000}));
  73. assign #1 square_temp[i]=(diff[i]*
  74. diff[i]);//接着计算平方值
  75. always @(posedge clk)
  76. begin
  77. if (din_new_valid == 1'b1 & mean_valid ==
  78. 1'b1 & ((~
  79. (is_boarder_mean))) == 1'b1)diff[i]<= #1 diff_tmp[i];
  80. if (mul_valid == 1'b1)
  81. //将结果组成一个新的向量方便加法运算
  82. square[(i + 1) * DW_SQR - 1:(i) * DW_SQR]<
  83. = #1
  84. ({square_temp[i],4'b0000});
  85. end
  86. end
  87. end
  88. endgenerate
  89. //将窗口内平方差相加
  90. add_tree #(DW_SQR,KSZ_SQR)
  91. square_total(
  92. .rst_n(rst_n),
  93. .clk(clk),
  94. .din_valid(mul_valid_r),
  95. .din(square_all_input),
  96. .dout(add_all),
  97. .dout_valid(square_all_valid)
  98. );
  99. //不等式左边乘以225 = 128 + 64 + 32 + 1
  100. always @(posedge clk)
  101. begin
  102. if (mul_valid_r == 1'b1)
  103. begin
  104. square_org1 <=
  105. ({8'b00000000,square_org_tmp}) + ({1'b0,square_org_tmp,7'b0000000});
  106. square_org2 <=
  107. ({2'b00,square_org_tmp,6'b000000}) +
  108. ({3'b000,square_org_tmp,5'b00000});
  109. end
  110. if (mul_valid_r2 == 1'b1)
  111. square_org <= square_org1 + square_org2;
  112. square_org_r <= ({square_org_r[(latency - 1) *
  113. DW_SQR_TOTAL -
  114. 1:0],square_org});
  115. end
  116. assign square_all_input = square[KSZ * KSZ * DW_SQR -
  117. 1:0];
  118. //利用两边不等式结果做阈值分割
  119. assign square_all = add_all[DW_SQR_TOTAL - 1:0];
  120. assign square_tmp1 = ({4'b0000,square_all});
  121. assign square_tmp2 = {DW_SQR_TOTAL+4{1'b0}};
  122. assign square_tmp3 = {DW_SQR_TOTAL+4{1'b0}};
  123. always @(posedge clk)
  124. begin
  125. if (square_all_valid == 1'b1)
  126. begin
  127. sigma_square1 <= square_tmp1 + square_tmp3;
  128. sigma_square2 <= square_tmp2;
  129. end
  130. if (square_all_valid_r == 1'b1)
  131. sigma_square <= sigma_square1 + sigma_square2;end
  132. assign square_org_last = ({4'b0000,
  133. square_org_r[latency *
  134. DW_SQR_TOTAL - 1:(latency - 1) * DW_SQR_TOTAL]});
  135. assign din_final = din_org_r[(sigma_latency) * DW - 1:
  136. (sigma_latency
  137. - 1) * DW];
  138. //分割结果
  139. assign dout_cmp = ((square_org_last >
  140. sigma_square)) ? {DW{1'b0}} :{DW{1'b1}};
  141. //边界处理
  142. assign dout_no_board = (((board_r[sigma_latency -
  143. 1] | ( ~ (valid_r[sigma_latency - 1]))) == 1'b1)) ?
  144. {DW{1'b0}} : dout_cmp;
  145. //输出数据
  146. always @(posedge clk)
  147. begin
  148. if (rst_all == 1'b1)
  149. dout <= {DW{1'b0}};
  150. else
  151. dout <= #1 dout_no_board;
  152. end

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/代码探险家/article/detail/991613
推荐阅读
相关标签
  

闽ICP备14008679号