首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

2024嵌入式FPGA竞赛最佳创意奖-红外瞳孔追踪系统(教学向)

  • 25-03-06 02:30
  • 3245
  • 9884
blog.csdn.net

致谢

        特此感谢B站大磊FPGA的多目标帧差法视频,这是我写出这个作品的基础;我的老师刘宁,他给了我最初的灵感;我的队友hqy和zcy,他们是这个项目团队不可缺少的一环;CrazyBingo对于FPGA图像处理的贡献,我参考的很多代码都有他的影子。


前言 

        本人为南邮大三电科学生,技术有限,能获此殊荣噱头占比更多,算是用简单的逻辑实现了比较花哨的效果。不过FPGA代码都是我一行行敲出来的,除摄像头HDMI输出demo外未用IP核,除大磊FPGA多目标帧差外未参考其他代码。项目完成后成就感还是相当足的。这将近两个月的时间FPGA图像处理方面的编程思维开阔了不少,期望以后能更进一步。

        作品部分演示效果已在B站发布视频,期待一键三连加关注(づ ̄3 ̄)づ╭❤️~
        


一、硬件基础(仅本项目使用,非必须)

易灵思Ti60F100,红外补光灯,VS-SC130GS(特制装载红外窄带滤波片摄像头),云台,眼球模型(开源项目),及部分3D打印支架等杂项。

二、系统框图及基本说明

        上图为设计主要模块或功能,本篇文章仅解释说明本人编写的关键模块。

        不同的厂家对摄像头基本上都会提供历程,也就是读写视频流这一步最终输出行、场同步,像素时钟,de,以及数据信号(需注意代码中行、场同步信号等是高有效还是低有效),这是基础不过多赘述,小白可看正点原子RGB-LCD彩条显示实验。

        该项目通过红外窄带滤波摄像头的得到的图像受自然光,灯光等外界干扰小,基本取决于人为的红外补光灯。由此,眼部因暗瞳效应产生的光斑极为明显,再通过一定的图像处理手段,可通过光斑定位深色瞳孔,从而捕获眼动方向。基本如下:

可控阈值二值化:对采集到的像素进行可控阈值二值化处理,以突出瞳孔等反射的亮斑。


多目标追踪定位:运用多目标追踪算法,定位并筛选出两个瞳孔亮斑的位置。


深色瞳孔提取:在瞳孔亮斑的一定相对位置处提取深色瞳孔,并进行目标追踪。


瞳孔坐标计算:得到瞳孔的坐标后,实时与参考点(眨眼或外部设定)对比,计算出眼球的                           方向。


实时画框叠加:FPGA 将获取到的相关坐标信息实时画框叠加到原始视频流中,通过 HDMI
                         接口输出实时的视频流。

三、主体模块说明

1.binarization

首先通过二值化,当亮度超过人为设定阈值之后将monoc拉高,作为信号触发目标追踪模块处理:

  1. module binarization
  2. (
  3. input clk ,
  4. input rst_n ,
  5. input ycbcr_vs ,
  6. input ycbcr_hs ,
  7. input ycbcr_de ,
  8. input [7:0] luminance , //pixel_data
  9. output reg post_vs ,
  10. output reg post_hs ,
  11. output reg post_de ,
  12. output reg monoc // monochrome(1=white,0=black)
  13. );
  14. reg ycbcr_vs_d;
  15. reg ycbcr_hs_d;
  16. reg ycbcr_de_d;
  17. wire [7:0] try = 8'd220; //threshold
  18. //binarization
  19. always @(posedge clk or negedge rst_n) begin
  20. if(!rst_n)
  21. monoc <= 1'b0;
  22. else if(ycbcr_vs)begin
  23. if (luminance > try) //threshold
  24. monoc <= 1'b1;
  25. else
  26. monoc <= 1'b0;
  27. end
  28. else
  29. monoc <= 1'b0;
  30. end
  31. //lag 1 clocks signal sync
  32. always@(posedge clk or negedge rst_n) begin
  33. if(!rst_n) begin
  34. post_vs <= 1'd0;
  35. post_hs <= 1'd0;
  36. post_de <= 1'd0;
  37. end
  38. else begin
  39. post_vs <= ycbcr_vs;
  40. post_hs <= ycbcr_hs;
  41. post_de <= ycbcr_de;
  42. end
  43. end
  44. endmodule

2.VIP_multi_target_detect(⭐)

        此代码是我从大磊视频中的代码修改而来,下面我将简单讲解,想了解代码细节推荐点击之前给的链接看看原视频,大磊老师讲的非常详细形象。

        该模块用于获取 二值化得出的亮斑 的边界:target_pos_out,注意:边界由43位组成:{Flag,ymax[41:32],xmax[31:21],ymin[20:11],xmin[10:0]}Flag位为标志位,代表该边界是否有效,后续为下、右、上、左四个边界,所有操作在一帧内完成,也就是一个vs周期内。众所周知,人有两个眼睛,所以在一般情况下,二值化输出的是两个相隔一段距离的亮斑区域——白像素之间间隔在MIN_DIST以内的白像素集,MIN_DIST自由设定。该模块我就直接设定为输出两个亮斑边界。此处祭出本人帅照以示说明,箭头所指即为暗瞳效应产生的亮斑区域:

        首先,per_frame_clken 拉高后,每个时钟x_cnt自增,到达一行像素后,x_cnt清零,y_cnt自增,由此即对每个有效像素进行了坐标标记。

        再看130行处,通过投票机制判定二值化白像素是新的亮斑区域还是落在之前亮斑区域之内的像素。target_flag用于标记亮斑区域,当target_flag[0] == 0时,证明暂未出现亮斑区域;target_flag[1] == 0时,证明暂未出现第二个亮斑区域。[1:0]new_target_flag是两个投票箱,对应两个投票区域。打个比方,在一片1280*720平米的空地上,将要按照从左到右,从上到下的顺序一颗一颗栽种一种根系范围是方形的树,树占地1平米,根系占地4*MIN_DIST²米,树可能种活也可能死,且当一棵活树在另外一棵活树的根系范围之内时,二者根系结合形成一个种群,当栽种成功两个种群时即完成指标,剩下未在根系范围内的树不再考虑。活树——亮点,死树——暗点,种群——亮斑区域。

        此刻,可能有人问了:“你二值化代码这么简单,怎么保证亮点就是暗瞳效应产生的呢?有没有改进方法?”

        有的,兄弟,有的。我们先考虑相对常见的问题:摄像头坏点——腐蚀处理;红外光强过弱导致无亮斑——见下文;红外光强过强导致一大片亮斑——见下文;鼻尖反光干扰——模块从左到右,从上到下,抓取到两个亮斑区域即止,一般人鼻子在两个眼睛下方,是抓不到的;眼睛上方背景光源干扰——见下文。

        那么就有人要问了:“我有三只眼睛怎么办,你这垃圾代码不废了吗?有没有改进方法?”

        没有,兄弟,抱歉。本眼动作品暂不支持多人运动以及外星人。若急需请赞助经费,标注外星人,我将在后续开展外星人专属业务。

  1. module VIP_multi_target_detect
  2. (
  3. input clk,
  4. input rst_n,
  5. input per_frame_vsync,
  6. input per_frame_hs,
  7. input per_frame_clken,
  8. input per_img_Bit, //white
  9. output reg [42:0] target_pos_out1, // {Flag,ymax[41:32],xmax[31:21],ymin[20:11],xmin[10:0]}
  10. output reg [42:0] target_pos_out2,
  11. input [ 9:0] MIN_DIST //min domain
  12. );
  13. //720p
  14. parameter [10:0] IMG_HDISP = 11'd1280;
  15. parameter [9:0] IMG_VDISP = 10'd720 ;
  16. //lag 1 clocks signal sync
  17. reg per_frame_vsync_r;
  18. reg per_frame_hs_r ;
  19. reg per_frame_clken_r;
  20. reg per_img_Bit_r ;
  21. always@(posedge clk or negedge rst_n)begin
  22. if(!rst_n)begin
  23. per_frame_vsync_r <= 0;
  24. per_frame_hs_r <= 0;
  25. per_frame_clken_r <= 0;
  26. per_img_Bit_r <= 0;
  27. end
  28. else begin
  29. per_frame_vsync_r <= per_frame_vsync ;
  30. per_frame_hs_r <= per_frame_hs ;
  31. per_frame_clken_r <= per_frame_clken ;
  32. per_img_Bit_r <= per_img_Bit ;
  33. end
  34. end
  35. wire vsync_pos_flag;//rising edge of vs
  36. wire vsync_neg_flag;//falling edge if vs
  37. assign vsync_pos_flag = per_frame_vsync & (!per_frame_vsync_r);
  38. assign vsync_neg_flag = !(per_frame_vsync) & per_frame_vsync_r;
  39. //
  40. //count the input pixel of v/h to get v/h coordinates
  41. reg [10:0] x_cnt;
  42. reg [9:0] y_cnt;
  43. always@(posedge clk or negedge rst_n) begin
  44. if(!rst_n) begin
  45. x_cnt <= 11'd0;
  46. y_cnt <= 10'd0;
  47. end
  48. else if(!per_frame_vsync) begin
  49. x_cnt <= 11'd0;
  50. y_cnt <= 10'd0;
  51. end
  52. else if(per_frame_clken) begin
  53. if(x_cnt < IMG_HDISP - 1) begin
  54. x_cnt <= x_cnt + 1'b1;
  55. y_cnt <= y_cnt;
  56. end
  57. else begin
  58. x_cnt <= 11'd0;
  59. y_cnt <= y_cnt + 1'b1;
  60. end
  61. end
  62. else begin
  63. x_cnt <= 11'd0;
  64. y_cnt <= y_cnt;
  65. end
  66. end
  67. // lag 1 clocks signal sync
  68. reg [10:0] x_cnt_r;
  69. reg [9:0] y_cnt_r;
  70. always @(posedge clk or negedge rst_n) begin
  71. if (!rst_n)
  72. x_cnt_r <= 11'd0;
  73. else
  74. x_cnt_r <= x_cnt;
  75. end
  76. always @(posedge clk or negedge rst_n) begin
  77. if (!rst_n)
  78. y_cnt_r <= 10'd0;
  79. else
  80. y_cnt_r <= y_cnt;
  81. end
  82. // reg the boundaries of each moving object
  83. reg [42:0] target_pos1;
  84. reg [42:0] target_pos2;
  85. // valid mark
  86. wire [1:0]target_flag;
  87. // left/right/up/down fields for each target
  88. wire [10:0] target_left1 ;
  89. wire [10:0] target_right1 ;
  90. wire [9:0] target_top1 ;
  91. wire [9:0] target_bottom1 ;
  92. wire [10:0] target_left2 ;
  93. wire [10:0] target_right2 ;
  94. wire [9:0] target_top2 ;
  95. wire [9:0] target_bottom2 ;
  96. assign target_flag[0] = target_pos1[42];
  97. assign target_bottom1 = (target_pos1[41:32] < IMG_VDISP-1 - MIN_DIST ) ? (target_pos1[41:32] +MIN_DIST) : IMG_VDISP-1;
  98. assign target_right1 = (target_pos1[31:21] < IMG_HDISP-1 - MIN_DIST ) ? (target_pos1[31:21] +MIN_DIST) : IMG_HDISP-1;
  99. assign target_top1 = (target_pos1[20:11] > 10'd0 + MIN_DIST ) ? (target_pos1[20:11] -MIN_DIST) : 10'd0;
  100. assign target_left1 = (target_pos1[10: 0] > 11'd0 + MIN_DIST ) ? (target_pos1[10: 0] -MIN_DIST) : 11'd0;
  101. assign target_flag[1] = target_pos2[42];
  102. assign target_bottom2 = (target_pos2[41:32] < IMG_VDISP-1 - MIN_DIST ) ? (target_pos2[41:32] +MIN_DIST) : IMG_VDISP-1;
  103. assign target_right2 = (target_pos2[31:21] < IMG_HDISP-1 - MIN_DIST ) ? (target_pos2[31:21] +MIN_DIST) : IMG_HDISP-1;
  104. assign target_top2 = (target_pos2[20:11] > 10'd0 + MIN_DIST ) ? (target_pos2[20:11] -MIN_DIST) : 10'd0;
  105. assign target_left2 = (target_pos2[10: 0] > 11'd0 + MIN_DIST ) ? (target_pos2[10: 0] -MIN_DIST) : 11'd0;
  106. reg target_cnt;
  107. reg [1:0] new_target_flag; //ballot box
  108. always @(posedge clk or negedge rst_n) begin
  109. if (!rst_n) begin
  110. target_pos1 <= {1'b0, 10'd0, 11'd0, 10'd0, 11'd0};
  111. target_pos2 <= {1'b0, 10'd0, 11'd0, 10'd0, 11'd0};
  112. new_target_flag <= 2'd0;
  113. target_cnt <= 1'd0;
  114. end
  115. // initialize
  116. else if(vsync_pos_flag) begin
  117. target_pos1 <= {1'b0, 10'd0, 11'd0, 10'd0, 11'd0};
  118. target_pos2 <= {1'b0, 10'd0, 11'd0, 10'd0, 11'd0};
  119. new_target_flag <= 2'd0;
  120. target_cnt <= 1'd0;
  121. end
  122. else begin
  123. // first clk,judge new target/target in domain
  124. if(per_frame_clken && per_img_Bit ) begin
  125. if(target_flag[0] == 1'b0) // if reg flag0 invalid,vote the bit as a new target
  126. new_target_flag[0] <= 1'b1;
  127. else begin // if reg flag0 valid,judge whether the target is in domain
  128. if((x_cnt < target_left1 || x_cnt > target_right1 || y_cnt < target_top1 || y_cnt > target_bottom1)) //if out of domain,vote the bit as a new target
  129. new_target_flag[0] <= 1'b1;
  130. else
  131. new_target_flag[0] <= 1'b0;
  132. end
  133. if(target_flag[1] == 1'b0) // same as flag0
  134. new_target_flag[1] <= 1'b1;
  135. else begin
  136. if((x_cnt < target_left2 || x_cnt > target_right2 || y_cnt < target_top2 || y_cnt > target_bottom2))
  137. new_target_flag[1] <= 1'b1;
  138. else
  139. new_target_flag[1] <= 1'b0;
  140. end
  141. end
  142. else begin
  143. new_target_flag <= 2'b0;
  144. end
  145. // second clk,according to the vote,update target
  146. if(per_frame_clken_r && per_img_Bit_r) begin
  147. if(new_target_flag == 2'b11) begin // unanimous vote,new target appear
  148. if(target_cnt == 1'b0)begin
  149. target_cnt <=1'd1;
  150. target_pos1 <= {1'b1, y_cnt_r, x_cnt_r, y_cnt_r, x_cnt_r};
  151. end
  152. else if(target_cnt == 1'b1 && target_pos2[42] != 1'b1 && (x_cnt_r < target_left1 || x_cnt_r > target_right1 || y_cnt_r < target_top1 || y_cnt_r > target_bottom1))begin
  153. target_pos2 <= {1'b1, y_cnt_r, x_cnt_r, y_cnt_r, x_cnt_r};
  154. target_cnt <=1'd1;
  155. end
  156. end
  157. else if (new_target_flag > 2'd0 && new_target_flag != 2'b11) begin // target in domain
  158. if(new_target_flag[0] == 1'b0) begin // flag0 == 0, target in domain1
  159. target_pos1[42] <= 1'b1;
  160. if(x_cnt_r < target_pos1[10: 0]) // expand boundary
  161. target_pos1[10: 0] <= x_cnt_r;
  162. if(x_cnt_r > target_pos1[31:21])
  163. target_pos1[31:21] <= x_cnt_r;
  164. if(y_cnt_r < target_pos1[20:11])
  165. target_pos1[20:11] <= y_cnt_r;
  166. if(y_cnt_r > target_pos1[41:32])
  167. target_pos1[41:32] <= y_cnt_r;
  168. end
  169. else if(new_target_flag[1] == 1'b0) begin // flag1 == 0, target in domain2
  170. target_pos2[42] <= 1'b1;
  171. if(x_cnt_r < target_pos2[10: 0])
  172. target_pos2[10: 0] <= x_cnt_r;
  173. if(x_cnt_r > target_pos2[31:21])
  174. target_pos2[31:21] <= x_cnt_r;
  175. if(y_cnt_r < target_pos2[20:11])
  176. target_pos2[20:11] <= y_cnt_r;
  177. if(y_cnt_r > target_pos2[41:32])
  178. target_pos2[41:32] <= y_cnt_r;
  179. end
  180. end
  181. end
  182. end
  183. end
  184. //reg output after a frame
  185. always @(posedge clk or negedge rst_n)
  186. if(!rst_n) begin
  187. target_pos_out1 <= {1'd0,10'd0,11'd0,10'd0,11'd0};
  188. target_pos_out2 <= {1'd0,10'd0,11'd0,10'd0,11'd0};
  189. end
  190. else if(vsync_neg_flag) begin
  191. target_pos_out1 <= target_pos1;
  192. target_pos_out2 <= target_pos2;
  193. end
  194. endmodule

3.VIP_multi_target_detect_black

在已经标定亮斑区域之后,我们便可以进一步在亮斑附近标定瞳孔(红框部分):

       

        相对于上一个多目标追踪的代码,标定瞳孔的单目标追踪代码就简单很多了,只需要例化该模块两次,分别将左右眼亮斑接入target_pos_in即可。不同的是,该模块的per_img_Bit是通过二值化筛选出足够黑的部分(代码类似,自行修改),够黑则拉高。而vsync_neg_flag用于表示一帧的结束,也就代表着模块数据处理完毕,用于各模块协调。

        flag_black用于标定当前像素输入否已经在亮斑的某个区域范围之内(根据实际补光灯与人眼的相对位置调节参数),当per_frame_clken(像素时序),per_img_Bit(黑色瞳孔),flag_black(像素范围)同时满足时,当前像素即为组成黑色瞳孔的像素,即可根据当前像素坐标拓展框定范围,并在一帧结束后作为瞳孔坐标信息target_pos_out输出给后续画框模块处理。

        这样也就在一定程度上解决了眼睛上方背景光源干扰的问题,因为光干扰一般是一片较亮的区域,二值化后周围小范围内不会存在足够黑到per_img_Bit拉高的程度,干扰也就在该模块被阻断了。

  1. module VIP_multi_target_detect_black
  2. (
  3. input clk,
  4. input rst_n,
  5. input per_frame_vsync,
  6. input per_frame_clken,
  7. input per_img_Bit, //balck
  8. input [42:0] target_pos_in,//{Flag,ymax[41:32],xmax[31:21],ymin[20:11],xmin[10:0]}
  9. output reg [42:0] target_pos_out,//{Flag,ymax[41:32],xmax[31:21],ymin[20:11],xmin[10:0]}
  10. output vsync_neg_flag //mark the ending of a frame
  11. );
  12. parameter [10:0] IMG_HDISP = 11'd1280;
  13. parameter [9:0] IMG_VDISP = 10'd720;
  14. reg per_frame_vsync_r;
  15. always@(posedge clk or negedge rst_n)
  16. begin
  17. if(!rst_n)
  18. per_frame_vsync_r <= 0;
  19. else
  20. per_frame_vsync_r <= per_frame_vsync ;
  21. end
  22. wire vsync_neg_flag;//falling edge
  23. assign vsync_neg_flag = !(per_frame_vsync) & per_frame_vsync_r;
  24. reg [10:0] x_cnt;
  25. reg [9:0] y_cnt;
  26. reg [9:0] up_reg ;
  27. reg [9:0] down_reg ;
  28. reg [10:0] left_reg ;
  29. reg [10:0] right_reg;
  30. reg flag_reg;
  31. //count the input pixel of v/h to get v/h coordinates
  32. always@(posedge clk or negedge rst_n)
  33. begin
  34. if(!rst_n) begin
  35. x_cnt <= 11'd0;
  36. y_cnt <= 10'd0;
  37. end
  38. else if(!per_frame_vsync) begin
  39. x_cnt <= 11'd0;
  40. y_cnt <= 10'd0;
  41. end
  42. else if(per_frame_clken) begin
  43. if(x_cnt < IMG_HDISP - 1) begin
  44. x_cnt <= x_cnt + 1'b1;
  45. y_cnt <= y_cnt;
  46. end
  47. else begin
  48. x_cnt <= 11'd0;
  49. y_cnt <= y_cnt + 1'b1;
  50. end
  51. end
  52. end
  53. //mark the area from target_pos_in
  54. reg flag_black;
  55. always@(posedge clk or negedge rst_n)
  56. if(!rst_n)
  57. flag_black <= 1'b0;
  58. else if(!per_frame_vsync)
  59. flag_black <= 1'b0;
  60. else if(target_pos_in[42] && per_frame_clken && y_cnt < target_pos_in[41:32] + 5'd20 && x_cnt < target_pos_in[31:21] + 5'd20 && y_cnt > target_pos_in[20:11] - 6'd32 && x_cnt > target_pos_in[10:0] - 5'd20)
  61. flag_black <= 1'b1;
  62. else
  63. flag_black <= 1'b0;
  64. //mark pupil
  65. always@(posedge clk or negedge rst_n) begin
  66. if(!rst_n) begin
  67. up_reg <= target_pos_in[41:32] + 5'd20;
  68. down_reg <= 10'd0;
  69. left_reg <= target_pos_in[31:21] + 5'd20;
  70. right_reg <= 10'd0;
  71. flag_reg <= 1'b0;
  72. end
  73. else if(!per_frame_vsync)begin
  74. up_reg <= target_pos_in[41:32] + 5'd20;
  75. down_reg <= 10'd0;
  76. left_reg <= target_pos_in[31:21] + 5'd20;
  77. right_reg <= 11'd0;
  78. flag_reg <= 1'b0;
  79. end
  80. else if (per_frame_clken && per_img_Bit && flag_black) begin
  81. flag_reg <= 1'b1;
  82. if (x_cnt < left_reg)
  83. left_reg <= x_cnt;
  84. else
  85. left_reg <= left_reg;
  86. if (x_cnt > right_reg)
  87. right_reg <= x_cnt;
  88. else
  89. right_reg <= right_reg;
  90. if (y_cnt < up_reg)
  91. up_reg <= y_cnt;
  92. else
  93. up_reg <= up_reg;
  94. if (y_cnt > down_reg)
  95. down_reg <= y_cnt;
  96. else
  97. down_reg <= down_reg;
  98. end
  99. end
  100. always @(posedge clk or negedge rst_n) begin
  101. if (!rst_n) begin
  102. target_pos_out <= {1'd0,10'd0,11'd0,10'd0,11'd0};
  103. end
  104. else if (vsync_neg_flag) begin
  105. target_pos_out <= {flag_reg,down_reg,right_reg,up_reg,left_reg};
  106. end
  107. end
  108. endmodule

4.VIP_Video_add_rectangular

        该模块为实时画框模块,视频中呈现的瞳孔框,定位框(locater),视线追踪框(sight frame),人员标记(person number)都由该模块在原视频上叠加红框实现。此处代码基本逻辑在大磊的视频里同样解释了,再次推荐观看。各框效果如下图:

        模块per_img_red/green/blue为图片实时像素,由于红外窄带滤波灰度摄像头采集到信号为raw8格式,所以本项目中三者都为8位灰度值。此处target_pos_output为前模块输出的两个瞳孔坐标。rx_person为队友做的AI人脸识别,据此信号判断人员编号。flag_rst信号为定位框的重置信号,该信号产生模块见下文,信号用于重置与当前瞳孔位置比较的定位框位置。sit为模块判定人眼移动的方向,作为输出信号用于控制外设舵机、云台等。

        模块269行之前都为准备 框的坐标,实际画框部分从269行开始。我首先解释画框部分,以((x_cnt >   rectangular_left1) && (x_cnt <   rectangular_right1) && ((y_cnt ==   rectangular_up1) || (y_cnt ==   rectangular_down1)) && rectangular_flag1)这段用于画上下边框的或条件之一为例,当四个条件同时满足时,输出纯红像素作为上下框边界。rectangular_flag1——框坐标是否有效;x_cnt >   rectangular_left1——当前像素在左边界右;x_cnt >   rectangular_right1——当前像素在右边界左;(y_cnt ==   rectangular_up1) || (y_cnt ==   rectangular_down1)——当前像素坐标等于上/下边界。其余或条件同理。

        114行开始,解释了如何重置定位框——外界输入的flag_rst信号(可自由设定触发源,本项目用眨眼和外界按钮,会在后续功能模块说明),当其拉高时,将当前瞳孔坐标作为新的定位框坐标。之后,将定位框x/y方向坐标和rec_x/y_sum分别与瞳孔当前x/y方向坐标和x/y_sum相减取绝对值和正负号,即可进行眼动八角定位 :

        如图黑实线为可判定的八个方向,灰虚线为方向区域范围边界,通过比较 x差值与二分之一 y(移位实现)差值,y 差值与二分之一 x 差值,以及差值正负关系,即可划分八个区域。同时使用sit将眼动方向传递至模块外以控制外设;并且根据方向的不同,控制视线追踪框rec_eye的边界定速增加或减少,以实现框随视线移动的直观效果。

  1. module VIP_Video_add_rectangular (
  2. input clk,
  3. input rst_n,
  4. input per_frame_vsync,
  5. input per_frame_hs,
  6. input per_frame_clken,
  7. input [7:0] per_img_red,
  8. input [7:0] per_img_green,
  9. input [7:0] per_img_blue,
  10. input [42:0] target_pos_out1,// {Flag,ymax[41:32],xmax[31:21],ymin[20:11],xmin[10:0]}
  11. input [42:0] target_pos_out2,
  12. input [3:0] rx_person, //person
  13. output reg post_frame_vsync,
  14. output reg post_frame_hs,
  15. output reg post_frame_clken,
  16. output reg [7:0] post_img_red,
  17. output reg [7:0] post_img_green,
  18. output reg [7:0] post_img_blue,
  19. input flag_rst, //rst the locater flag
  20. output [3:0] sit //eye direction
  21. );
  22. //1280*720 resolution
  23. parameter [10:0] IMG_HDISP = 11'd1280;
  24. parameter [9:0] IMG_VDISP = 10'd720;
  25. wire [9:0] rectangular_up1;
  26. wire [9:0] rectangular_up2;
  27. wire [9:0] rectangular_down1;
  28. wire [9:0] rectangular_down2;
  29. wire [10:0] rectangular_left1;
  30. wire [10:0] rectangular_left2;
  31. wire [10:0] rectangular_right1;
  32. wire [10:0] rectangular_right2;
  33. wire rectangular_flag1; //flag whether there is a moving target
  34. wire rectangular_flag2;
  35. assign rectangular_flag1 = target_pos_out1[42];
  36. assign rectangular_down1 = target_pos_out1[41:32];
  37. assign rectangular_right1 = target_pos_out1[31:21];
  38. assign rectangular_up1 = target_pos_out1[20:11];
  39. assign rectangular_left1 = target_pos_out1[10:0];
  40. assign rectangular_flag2 = target_pos_out2[42];
  41. assign rectangular_down2 = target_pos_out2[41:32];
  42. assign rectangular_right2 = target_pos_out2[31:21];
  43. assign rectangular_up2 = target_pos_out2[20:11];
  44. assign rectangular_left2 = target_pos_out2[10:0];
  45. reg [10:0] x_cnt;
  46. reg [9:0] y_cnt;
  47. always @(posedge clk or negedge rst_n) begin
  48. if(!rst_n)begin
  49. x_cnt <= 11'd0;
  50. y_cnt <= 10'd0;
  51. end
  52. else begin
  53. if(!per_frame_vsync)begin
  54. x_cnt <= 11'd0;
  55. y_cnt <= 10'd0;
  56. end
  57. else if(per_frame_clken) begin
  58. if(x_cnt < IMG_HDISP - 1) begin
  59. x_cnt <= x_cnt + 1'b1;
  60. y_cnt <= y_cnt;
  61. end
  62. else begin
  63. x_cnt <= 11'd0;
  64. y_cnt <= y_cnt + 1'b1;
  65. end
  66. end
  67. else begin
  68. x_cnt <= 11'd0;
  69. y_cnt <= y_cnt;
  70. end
  71. end
  72. end
  73. //sum of x/y
  74. wire [12:0]x_sum;
  75. wire [11:0]y_sum;
  76. assign x_sum = (rectangular_flag1 && rectangular_flag2) ? (rectangular_right1 + rectangular_right2 + rectangular_left1 + rectangular_left2):1'b0;
  77. assign y_sum = (rectangular_flag1 && rectangular_flag2) ? (rectangular_up1 + rectangular_up2 + rectangular_down1 + rectangular_down2):1'b0;
  78. //locater
  79. wire [9:0] rectangular_up3;
  80. wire [9:0] rectangular_up4;
  81. wire [9:0] rectangular_down3;
  82. wire [9:0] rectangular_down4;
  83. wire [10:0] rectangular_left3;
  84. wire [10:0] rectangular_left4;
  85. wire [10:0] rectangular_right3;
  86. wire [10:0] rectangular_right4;
  87. reg [41:0]rec_left = 42'b0101000000_00111011011_0100110000_00111000110;// {Flag,ymax[41:32],xmax[31:21],ymin[20:11],xmin[10:0]}
  88. reg [41:0]rec_right = 42'b0101000000_01011011011_0100110000_01011000110;// {Flag,ymax[41:32],xmax[31:21],ymin[20:11],xmin[10:0]}
  89. //rst locater
  90. always @(posedge clk or negedge rst_n)
  91. if(!rst_n)begin
  92. rec_left = 42'b0101000000_00111011011_0100110000_00111000110;
  93. rec_right = 42'b0101000000_01011011011_0100110000_01011000110;
  94. end
  95. else if(flag_rst && rectangular_flag1 && rectangular_flag2)begin
  96. rec_left = target_pos_out1[41:0];
  97. rec_right = target_pos_out2[41:0];
  98. end
  99. wire [11:0]rec_y_sum;
  100. wire [12:0]rec_x_sum;
  101. assign rec_x_sum = (rectangular_flag1 && rectangular_flag2) ? (rectangular_right3 + rectangular_right4 + rectangular_left3 + rectangular_left4):1'b0;
  102. assign rec_y_sum = (rectangular_flag1 && rectangular_flag2) ? (rectangular_up3 + rectangular_up4 + rectangular_down3 + rectangular_down4):1'b0;
  103. assign rectangular_down3 = rec_left[41:32];
  104. assign rectangular_right3 = rec_left[31:21];
  105. assign rectangular_up3 = rec_left[20:11];
  106. assign rectangular_left3 = rec_left[10:0];
  107. assign rectangular_down4 = rec_right[41:32];
  108. assign rectangular_right4 = rec_right[31:21];
  109. assign rectangular_up4 = rec_right[20:11];
  110. assign rectangular_left4 = rec_right[10:0];
  111. //output eye direction
  112. reg right;
  113. reg left;
  114. reg down;
  115. reg up;
  116. wire [3:0] sit;
  117. //judge sight direction(now-locater)
  118. wire sign_x;
  119. wire sign_y;
  120. wire [10:0]sub_x;
  121. wire [9:0]sub_y;
  122. assign sub_x = (x_sum > rec_x_sum) ? (x_sum - rec_x_sum) : (rec_x_sum - x_sum);
  123. assign sub_y = (y_sum > rec_y_sum) ? (y_sum - rec_y_sum) : (rec_y_sum - y_sum);
  124. assign sign_x = (x_sum > rec_x_sum) ? 1'b1 : 1'b0;
  125. assign sign_y = (y_sum > rec_y_sum) ? 1'b1 : 1'b0;
  126. wire h;
  127. wire v;
  128. wire h_v;
  129. assign h = ((sub_x >> 1) >= sub_y) ? 1'b1 : 1'b0;
  130. assign v = ((sub_y >> 1) >= sub_x) ? 1'b1 : 1'b0;
  131. assign h_v = (((sub_y >> 1) < sub_x) && ((sub_x >> 1) < sub_y)) ? 1'b1 : 1'b0;
  132. //sight frame
  133. reg [41:0]rec_eye = 41'b0101100000_01011011011_0100010000_00111000110;
  134. always @(posedge clk or negedge rst_n)
  135. if(!rst_n)
  136. rec_eye = 41'b0101100000_01011011011_0100010000_00111000110;
  137. else if((x_cnt == IMG_HDISP - 2'b10) && (y_cnt == IMG_VDISP - 2'b10) && rectangular_flag1 && rectangular_flag2)begin
  138. if(h && sign_x && rec_eye[31:21] < IMG_HDISP)begin //right
  139. rec_eye[31:21] <= rec_eye[31:21] + 2'b10;
  140. rec_eye[10:0] <= rec_eye[10:0] + 2'b10;
  141. right <= 1'b1;
  142. left <= 1'b0;
  143. down <= 1'b0;
  144. up <= 1'b0;
  145. end
  146. else if(h && !sign_x && rec_eye[10:0] >= 1'b1)begin //left
  147. rec_eye[31:21] <= rec_eye[31:21] - 2'b10;
  148. rec_eye[10:0] <= rec_eye[10:0] - 2'b10;
  149. right <= 1'b0;
  150. left <= 1'b1;
  151. down <= 1'b0;
  152. up <= 1'b0;
  153. end
  154. else if(v && sign_y && rec_eye[41:32] < IMG_VDISP)begin //down
  155. rec_eye[41:32] <= rec_eye[41:32] + 2'b10;
  156. rec_eye[20:11] <= rec_eye[20:11] + 2'b10;
  157. right <= 1'b0;
  158. left <= 1'b0;
  159. down <= 1'b1;
  160. up <= 1'b0;
  161. end
  162. else if(v && !sign_y && rec_eye[20:11] >= 1'b1)begin //up
  163. rec_eye[41:32] <= rec_eye[41:32] - 2'b10;
  164. rec_eye[20:11] <= rec_eye[20:11] - 2'b10;
  165. right <= 1'b0;
  166. left <= 1'b0;
  167. down <= 1'b0;
  168. up <= 1'b1;
  169. end
  170. else if(h_v && sign_x && sign_y && rec_eye[31:21] < IMG_HDISP && rec_eye[41:32] < IMG_VDISP)begin //right_down
  171. rec_eye[31:21] <= rec_eye[31:21] + 2'b10;
  172. rec_eye[10:0] <= rec_eye[10:0] + 2'b10;
  173. rec_eye[41:32] <= rec_eye[41:32] + 2'b10;
  174. rec_eye[20:11] <= rec_eye[20:11] + 2'b10;
  175. right <= 1'b1;
  176. left <= 1'b0;
  177. down <= 1'b1;
  178. up <= 1'b0;
  179. end
  180. else if(h_v && !sign_x && sign_y && rec_eye[10:0] >= 1'b1 && rec_eye[41:32] < IMG_VDISP)begin //left_down
  181. rec_eye[31:21] <= rec_eye[31:21] - 2'b10;
  182. rec_eye[10:0] <= rec_eye[10:0] - 2'b10;
  183. rec_eye[41:32] <= rec_eye[41:32] + 2'b10;
  184. rec_eye[20:11] <= rec_eye[20:11] + 2'b10;
  185. right <= 1'b0;
  186. left <= 1'b1;
  187. down <= 1'b1;
  188. up <= 1'b0;
  189. end
  190. else if(h_v && !sign_y && sign_x && rec_eye[31:21] < IMG_HDISP && rec_eye[20:11] >= 1'b1)begin //right_up
  191. rec_eye[31:21] <= rec_eye[31:21] + 2'b10;
  192. rec_eye[10:0] <= rec_eye[10:0] + 2'b10;
  193. rec_eye[41:32] <= rec_eye[41:32] - 2'b10;
  194. rec_eye[20:11] <= rec_eye[20:11] - 2'b10;
  195. right <= 1'b1;
  196. left <= 1'b0;
  197. down <= 1'b0;
  198. up <= 1'b1;
  199. end
  200. else if(h_v && !sign_y && !sign_x && rec_eye[10:0] >= 1'b1 && rec_eye[20:11] >= 1'b1)begin //left_up
  201. rec_eye[31:21] <= rec_eye[31:21] - 2'b10;
  202. rec_eye[10:0] <= rec_eye[10:0] - 2'b10;
  203. rec_eye[41:32] <= rec_eye[41:32] - 2'b10;
  204. rec_eye[20:11] <= rec_eye[20:11] - 2'b10;
  205. right <= 1'b0;
  206. left <= 1'b1;
  207. down <= 1'b0;
  208. up <= 1'b1;
  209. end
  210. end
  211. assign sit = {up, down, left, right};
  212. wire [9:0] rectangular_up5;
  213. wire [9:0] rectangular_down5;
  214. wire [10:0] rectangular_left5;
  215. wire [10:0] rectangular_right5;
  216. assign rectangular_down5 = rec_eye[41:32];
  217. assign rectangular_right5 = rec_eye[31:21];
  218. assign rectangular_up5 = rec_eye[20:11];
  219. assign rectangular_left5 = rec_eye[10:0];
  220. //draw
  221. always @(posedge clk or negedge rst_n) begin
  222. if(!rst_n) begin
  223. post_frame_vsync <= 1'd0;
  224. post_frame_hs <= 1'd0;
  225. post_frame_clken <= 1'd0;
  226. post_img_red <= 8'd0;
  227. post_img_green <= 8'd0;
  228. post_img_blue <= 8'd0;
  229. end
  230. else begin
  231. post_frame_vsync <= per_frame_vsync;
  232. post_frame_hs <= per_frame_hs;
  233. post_frame_clken <= per_frame_clken;
  234. if(post_frame_clken) begin
  235. if(((x_cnt > rectangular_left1) && (x_cnt < rectangular_right1) && ((y_cnt == rectangular_up1) || (y_cnt == rectangular_down1)) && rectangular_flag1) ||
  236. ((x_cnt > rectangular_left2) && (x_cnt < rectangular_right2) && ((y_cnt == rectangular_up2) || (y_cnt == rectangular_down2)) && rectangular_flag2) ||
  237. ((x_cnt > rectangular_left3) && (x_cnt < rectangular_right3) && ((y_cnt == rectangular_up3) || (y_cnt == rectangular_down3))) ||
  238. ((x_cnt > rectangular_left4) && (x_cnt < rectangular_right4) && ((y_cnt == rectangular_up4) || (y_cnt == rectangular_down4))) ||
  239. ((x_cnt > rectangular_left5) && (x_cnt < rectangular_right5) && ((y_cnt == rectangular_up5) || (y_cnt == rectangular_down5))) ||
  240. ((x_cnt > rectangular_left3) && (x_cnt < rectangular_left3 + 4'd10) && ((y_cnt == rectangular_up3 - 4'd4) || (y_cnt == rectangular_up3 - 5'd8) || (y_cnt == rectangular_up3 - 5'd1))
  241. && rx_person == 4'b0010)||
  242. ((x_cnt > rectangular_left3) && (x_cnt < rectangular_left3 + 4'd10) && ((y_cnt == rectangular_up3 - 4'd4) || (y_cnt == rectangular_up3 - 5'd8))
  243. && rx_person == 4'b0100)||
  244. ((x_cnt > rectangular_left3) && (x_cnt < rectangular_left3 + 4'd10) && ((y_cnt == rectangular_up3 - 4'd4))
  245. && rx_person == 4'b1000))begin
  246. //draw up and down
  247. post_img_red <= 8'd255;
  248. post_img_green <= 8'd0;
  249. post_img_blue <= 8'd0;
  250. end
  251. else if(((y_cnt > rectangular_up1) && (y_cnt < rectangular_down1) && ((x_cnt == rectangular_left1) || (x_cnt == rectangular_right1)) && rectangular_flag1)||
  252. ((y_cnt > rectangular_up2) && (y_cnt < rectangular_down2) && ((x_cnt == rectangular_left2) || (x_cnt == rectangular_right2)) && rectangular_flag2) ||
  253. ((y_cnt > rectangular_up3) && (y_cnt < rectangular_down3) && ((x_cnt == rectangular_left3) || (x_cnt == rectangular_right3)))||
  254. ((y_cnt > rectangular_up4) && (y_cnt < rectangular_down4) && ((x_cnt == rectangular_left4) || (x_cnt == rectangular_right4)))||
  255. ((y_cnt > rectangular_up5) && (y_cnt < rectangular_down5) && ((x_cnt == rectangular_left5) || (x_cnt == rectangular_right5))))begin
  256. //draw left and right
  257. post_img_red <= 8'd255;
  258. post_img_green <= 8'd0;
  259. post_img_blue <= 8'd0;
  260. end
  261. else begin
  262. post_img_red <= per_img_red ;
  263. post_img_green <= per_img_green;
  264. post_img_blue <= per_img_blue ;
  265. end
  266. end
  267. else begin
  268. post_img_red <= per_img_red ;
  269. post_img_green <= per_img_green;
  270. post_img_blue <= per_img_blue ;
  271. end
  272. end
  273. end
  274. endmodule

四、功能模块说明

1、rec_rst

        该模块利用双目信号的捕获情况,判定是否刻意眨眼,以控制flag_rst信号重置定位框,flag12为模块VIP_multi_target_detect_black输出的target_pos_out[42],若flag1&&flag2 == 1,代表两眼信号都稳定。此处我编写了状态机以判断人是否刻意眨眼。概括来说就是稳定睁眼——稳定闭眼——稳定睁眼的过程,具体时间修改参数可调。

        IDLE:初始态,用于flag_rst信号的置零。

        S1:    双目都捕获时每帧对cnt_neg_p计数。视频是60Hz,也就是说当双目都捕获持续大约两秒后,若双目都丢失,则跳转至S2,同时cnt_neg_p清零。这是一个从睁眼到闭眼的过程。

        S2:    注意,该状态使用cnt_neg_n在双目都丢失时计数。为避免信号波动或自然眨眼影响,设定为双目都丢失1秒左右后,若再次捕获到任意信号,判定为睁眼,跳转到S3。大于半秒,小于1秒的时间内捕获信号,则判定为干扰,返回S1态。

        S3:    当双目再次被持续捕捉一秒左右,跳转至S4转态。

        S4:    回归IDLE,同时拉高flag_rst代表刻意眨眼过程完成,重置定位框。

  1. module rec_rst
  2. (
  3. input clk,
  4. input rst_n,
  5. input flag1,
  6. input flag2,
  7. input v_neg, //falling edge
  8. output reg flag_rst
  9. );
  10. parameter IDLE = 5'b00001;
  11. parameter S1 = 5'b00010; //visibility
  12. parameter S2 = 5'b00100; //lost
  13. parameter S3 = 5'b01000; //appear
  14. parameter S4 = 5'b10000; //output rst
  15. reg [4:0] curr_st ;
  16. reg [4:0] next_st ;
  17. //Excitation signal generation
  18. reg [7:0] cnt_neg_p;
  19. reg [7:0] cnt_neg_n;
  20. always @(posedge clk or negedge rst_n)
  21. if(!rst_n || curr_st == IDLE || curr_st == S2 || (curr_st == S3 && !flag1 && !flag2))
  22. cnt_neg_p <= 1'b0;
  23. else if(cnt_neg_p >= 8'd128)
  24. cnt_neg_p <= cnt_neg_p;
  25. else if(flag1 && flag2 && v_neg)
  26. cnt_neg_p <= cnt_neg_p + 1'b1;
  27. always @(posedge clk or negedge rst_n)
  28. if(!rst_n || curr_st == IDLE || curr_st == S1 || flag1 || flag2)
  29. cnt_neg_n <= 1'b0;
  30. else if(cnt_neg_n >= 8'd128)
  31. cnt_neg_n <= cnt_neg_n;
  32. else if(!flag1 && !flag2 && v_neg && curr_st == S2)
  33. cnt_neg_n <= cnt_neg_n + 1'b1;
  34. always @(posedge clk or negedge rst_n)
  35. if(!rst_n)
  36. curr_st <= IDLE;
  37. else
  38. curr_st <= next_st;
  39. always @(curr_st)begin
  40. case(curr_st)
  41. IDLE: begin
  42. next_st <= S1;
  43. flag_rst <= 1'b0;
  44. end
  45. S1 : if(cnt_neg_p >= 8'd128 && !flag1 && !flag2)
  46. next_st <= S2;
  47. else
  48. next_st <= S1;
  49. S2 : if(cnt_neg_n >= 8'd64 && (flag1 || flag2))
  50. next_st <= S3;
  51. else if((flag1 || flag2) && cnt_neg_n >= 8'd32)
  52. next_st <= S1;
  53. else
  54. next_st <= S2;
  55. S3 : if(cnt_neg_p >= 7'd64 && flag1 && flag2)
  56. next_st <= S4;
  57. else
  58. next_st <= S3;
  59. S4 : if(flag1 && flag2)begin
  60. next_st <= IDLE;
  61. flag_rst <= 1'b1;
  62. end
  63. else
  64. next_st <= S4;
  65. default : next_st <= IDLE;
  66. endcase
  67. end
  68. endmodule

2、cnt_all

        此模块通过对一帧内所有有效像素的灰度值求和,与设定阈值对比,判断需要调高还是调低亮度,通过FPGA调控输出PWM波至外部硬件电路控制红外灯的亮度。模块相当简单,但是效果很不错,解决了很多问题。

        luminance为八位灰度图像数据,cmp为比较结果,再输入PWM模块调节PWM占空比。请根据实际环境调节代码末尾阈值上下限。

  1. module cnt_all
  2. (
  3. input clk ,
  4. input rst_n ,
  5. input per_frame_vsync ,
  6. input ycbcr_de , // en
  7. input [7:0] luminance ,
  8. output reg [1:0] cmp // 00 IDLE;10 exceed;01 below;11 ok
  9. );
  10. reg per_frame_vsync_r;
  11. wire vsync_pos_flag;//rising edge
  12. wire vsync_neg_flag;//falling edge
  13. assign vsync_pos_flag = per_frame_vsync & (!per_frame_vsync_r);
  14. assign vsync_neg_flag = !(per_frame_vsync) & per_frame_vsync_r;
  15. reg [27:0] cnt_fin;
  16. reg [27:0] cnt;
  17. always @(posedge clk or negedge rst_n) begin
  18. if(!rst_n)
  19. cnt <= 1'b0;
  20. else if(ycbcr_de)begin
  21. cnt <= cnt + luminance;
  22. end
  23. else if(vsync_pos_flag)
  24. cnt <= 1'b0;
  25. end
  26. //delay
  27. always@(posedge clk or negedge rst_n) begin
  28. if(!rst_n) begin
  29. per_frame_vsync_r <= 1'd0;
  30. end
  31. else begin
  32. per_frame_vsync_r <= per_frame_vsync;
  33. end
  34. end
  35. always @(posedge clk or negedge rst_n)
  36. if(!rst_n) begin
  37. cnt_fin <= 1'b0;
  38. end
  39. else if(vsync_neg_flag) begin //reg cnt after a frame
  40. cnt_fin <= cnt;
  41. end
  42. always @(posedge clk or negedge rst_n)
  43. if(!rst_n) begin
  44. cmp <= 2'b0;
  45. end
  46. else if(cnt_fin > 28'h2f00000) begin
  47. cmp <= 2'b10;
  48. end
  49. else if(cnt_fin < 28'h2800000) begin
  50. cmp <= 2'b01;
  51. end
  52. else if(cnt_fin > 28'h2800000 && cnt_fin < 28'h2f00000) begin
  53. cmp <= 2'b11;
  54. end
  55. endmodule

总结(与项目无关)

 下文与本项目无关,是博主的个人感叹(碎碎念),可跳过。

        博客主体写完,心情还是比较不平静的,有很多话想说,但是又因为各种原因删了。

        大一刚入南邮时,班主任(一位非常负责的导师)在班级群问:“大家有什么问题”,

        我:“没有啥问题”,

        班主任:“没问题就是最大的问题”。

        现在想起来当时自己真是愣头青,哈哈。大学不同于高中,我认为大学首要需要学会的能力是信息获取的能力。回首过往三年,虽然不后悔,多少有些遗憾,遗憾在于大多数时间都忙于学习,追求各种指标,而没有时间停下来,用心体会学习的过程。

        对于我来说,成绩更多像是我的保护伞,保护我免受父母的唠叨,保护我作为南邮第一届创新班学委有底气跟学校交流争取自己班的利益,保护我能保留很大一部分的个人自由······现在面对保研选择,才略微有些窥探到自己的追求:心无旁骛的深入追求知识。但是这又牵扯到很多,也很难实现。嗯,不过我依然会追逐理想,我依然问心无愧。

        祝愿各位都有远大前程。

注:本文转载自blog.csdn.net的forever_00_的文章"https://blog.csdn.net/forever_00_/article/details/145709587"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2491) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

123
硬件开发
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top