四、VGA项目:联合精简帧+双fifo+sobel算法 实现VGA显示

前言:该项目实际上是在很多基础的小练习上合成起来的,例如涉及到uart(rs232)的数据传输、双fifo流水线操作、VGA图像显示,本次内容在此基础上又增添了sobel算法,能实现图像的边沿监测并VGA显示。

文章目录

  • 1.项目描述
  • 2.sobel算法解析:
  • 3.模块结构示意图:
  • 4.sobel_ctrl模块设计
    • 4.1波形设计
    • 4.2代码
  • 5.VGA_shift模块设计
    • 5.1 显示效果示意图:
    • 5.2 简易波形示意图:
    • 5.3 代码
  • 6.uart_rx模块设计(直接用之前的设计 略)
  • 7.顶层设计
  • 8.最终的显示效果:

在这里插入图片描述

1.项目描述

通过串口助手把 200 行 x200 列数据传入 FPGA,对应三行三列的九个数进行 sobel算法,把边缘检测的结果(198x198的数据)通过 vga 来显示,用两种颜色来区分是否是边界点(边界用白色显示,其他用黑色)。

2.sobel算法解析:

在这里插入图片描述

  1. 把图像每三行三列的数据分别乘上算子中对应位置的值再相加。然后进行如下运算,得到相应方向(x 和 y)的 Dx 和 Dy。
    Dx=(a3-a1)+(b3-b1)*2+c3-c1;
    Dy=(a1-c1)+(a2-c2)*2+a3-c3;

  2. 对上面求得的 Dx 和 Dy 做平方和的平方根,再取近似值 Dx 和 Dy 的绝对值的和得到 Dxy:

在这里插入图片描述

  1. 如果 Dxy 的值大于一个阈值(这个阈值是多次尝试试出来的,本次项目中设置为5),表示该点为边界点,就让 VGA 显示一个白点,否则显示黑点。

  2. 把计算的结果通过 vga 显示,显示器会把是边界点的以白色像素显示,不是边界点的以黑色像素点显示,于是得到了一幅图像的轮廓

在这里插入图片描述

3.模块结构示意图:

在这里插入图片描述

其中:
uart_rx模块在:手写一个uart协议——rs232
sobel_ctrl模块的核心是双fifo的流水线操作參考(需在此基础上进行改动):双fifo流水线操作
VGA_shift模块參考(原移动框为200x200这里变成198x198,且添加了ram方便rgb图像的存储与读取):VGA接口驱动与图像显示动态移动

4.sobel_ctrl模块设计

4.1波形设计

在这里插入图片描述

4.2代码

module sobel_ctrl(
    input wire clk,
    input wire rst,
    input wire [7:0] rx_data,
    input wire pi_flag,
    output reg [7:0] rgb,
    output reg po_flag
    );
reg[9:0] cnt_col,cnt_row;
reg wr_en1_r,wr_en2_r;
wire wr_en1,wr_en2;

reg [7:0] data_in1_r;
wire [7:0] data_in1;
wire [7:0] dout1,dout2;

reg [7:0] dout1_t,dout1_tt,dout2_t,dout2_tt;
reg [7:0] rx_data_t,rx_data_tt;
reg rd_en_r;
wire rd_en;

reg shift_flag;

reg flag_d;

reg [7:0] Dx,Dy;

reg flag_abs,flag_dxy,flag_rgb;
reg [7:0] abs_dx,abs_dy;
reg [7:0] dxy;

parameter COL_MUX=199;
parameter ROW_MUX=199;
parameter VALUR=5;  //不断调试得到一个合适的阈值

// cnt_col
always @(posedge clk) begin 
    if(rst==1'b1) begin
        cnt_col <= 'd0;
    end 
    else if (pi_flag==1'b1 && cnt_col==COL_MUX) begin
         cnt_col<='d0;
    end
    else if (pi_flag==1'b1) begin
         cnt_col<=cnt_col+1'b1;
    end
end

// cnt_row
always @(posedge clk) begin 
    if(rst==1'b1) begin
        cnt_row <= 'd0;
    end
    else if (cnt_row==ROW_MUX && pi_flag==1'b1 && cnt_col==COL_MUX) begin
        cnt_row<='d0;
    end
    else if (pi_flag==1'b1 && cnt_col==COL_MUX) begin
       cnt_row<=cnt_row+1'b1;
    end
end

// wr_en1_r
assign wr_en1=wr_en1_r;
always @(posedge clk) begin 
    if(rst==1'b1) begin
        wr_en1_r <= 'd0;
    end
    else if (cnt_row=='d0) begin
        wr_en1_r<=pi_flag;
    end
    else if (cnt_row>'d1 && cnt_row<ROW_MUX) begin
        wr_en1_r<=shift_flag;
    end
end

// wr_en2_r
assign wr_en2=wr_en2_r;
always @(posedge clk) begin 
    if(rst==1'b1) begin
        wr_en2_r <= 'd0;
    end 
    else if (cnt_row>'d0 && cnt_row<ROW_MUX) begin
        wr_en2_r<=pi_flag;
    end
    else
        wr_en2_r<='d0;
end

// data_in1_r
assign data_in1=data_in1_r;
always @(posedge clk) begin 
    if(rst==1'b1) begin
         data_in1_r<= 'd0;
    end 
    else if (cnt_row=='d0) begin
        data_in1_r<=rx_data;
    end
    else if (cnt_row>'d1 && cnt_row<ROW_MUX) begin
       data_in1_r<=dout2;
    end
end

// rd_en_r
assign rd_en=rd_en_r;

always @(posedge clk) begin 
    if(rst==1'b1) begin
         rd_en_r<= 'd0;
    end 
    else if (cnt_row>'d1) begin
        rd_en_r<=pi_flag;
    end
    else 
        rd_en_r<='d0;
end

// shift_flag
always @(posedge clk) begin 
    if(rst==1'b1) begin
        shift_flag <= 'd0;
    end 
    else
        shift_flag<=rd_en_r;
end

// dout1_t,dout1_tt,dout2_t,dout2_tt,rx_data_t,rx_data_tt
always @(posedge clk) begin 
	if (shift_flag==1'b1) begin
		{dout1_tt,dout1_t}={dout1,dout1_t};
		{dout2_tt,dout2_t}={dout2,dout2_t};
		{rx_data_tt,rx_data_t}={rx_data_t,rx_data};
	end
end

always @(posedge clk) begin 
	if(rst==1'b1) begin
		flag_d <= 'd0;
	end 
	else if (cnt_row>=2 && cnt_col>2) begin
		flag_d<=rd_en_r;
	end
end

always @(posedge clk) begin 
	if(rst==1'b1) begin
		Dx <= 'd0;
		Dy <= 'd0;
	end 
	else if (flag_d==1'b1) begin
		Dx<=(dout1_tt-dout1)+(dout2_tt-dout2)<<1+(rx_data_tt-rx_data);
		Dy<=(dout1_tt-rx_data_tt)+(dout1_t-rx_data_t)<<1+(dout1-rx_data);
	end
	
end

always @(posedge clk) begin 
	if (rst==1'b1) begin
		flag_abs<='d0;
		flag_dxy<='d0;
		flag_rgb<='d0;
		po_flag<='d0;

	end
	else
		{po_flag,flag_rgb,flag_dxy,flag_abs}<={flag_rgb,flag_dxy,flag_abs,flag_d};
end

// abs_dx
always @(posedge clk) begin 
	if(rst==1'b1) begin
		 abs_dx<='d0;
	end 
	else if (flag_abs==1'b1 ) begin
		if (dx[7]==1'b1) begin
			abs_dx<=(~Dx)+1'b1;
		end
		else
			abs_dx<=Dx;
	end
end

// abs_dy
always @(posedge clk) begin 
	if(rst==1'b1) begin
		abs_dy <= 'd0;
	end 
	else if (flag_abs==1'b1) begin
		if (dy[7]==1'b1) begin
			abs_dy<=(~Dy)+1'b1;
		end
		else 
			abs_dy<=Dy;
	end
end

// dxy
always @(posedge clk) begin 
	if(rst==1'b1) begin
		 dxy<= 'd0;
	end 
	else if (flag_dxy==1'b1) begin
		dxy<=abs_dx+abs_dy;
	end
end

// rgb
always @(posedge clk) begin 
	if(rst==1'b1) begin
		rgb <= 'd0;
	end 
	else if (flag_rgb==1'b1) begin
		if (dxy>VALUR) begin
			rgb<=8'hff;
		end
		else
			rgb<=8'h00;
	end
end


sfifo_8X256 sfifo1_8X256 (
  .clk(clk),      // input wire clk
  .din(data_in1),      // input wire [7 : 0] din
  .wr_en(wr_en1),  // input wire wr_en
  .rd_en(rd_en),  // input wire rd_en
  .dout(dout1),    // output wire [7 : 0] dout
  .full(),    // output wire full
  .empty()  // output wire empty
);


sfifo_8X256 sfifo2_8X256 (
 .clk(clk),      // input wire clk
  .din(rx_data),      // input wire [7 : 0] din
  .wr_en(wr_en2),  // input wire wr_en
  .rd_en(rd_en),  // input wire rd_en_r
  .dout(dout2),    // output wire [7 : 0] dout
  .full(),    // output wire full
  .empty()  // output wire empty
);
endmodule

5.VGA_shift模块设计

5.1 显示效果示意图:

在这里插入图片描述

5.2 简易波形示意图:

在这里插入图片描述

5.3 代码

module vga_shift(
    input wire sclk,//50mhz
    input wire clk_25,
    input wire rst,
    input wire [7:0] rgb_in,
    input wire pi_flag,
    output reg hsync,
    output reg vsync,
    output reg [7:0] rgb
    );

parameter HSYNC_END=95;
parameter CNT_H_END=799;

parameter VSYNC_END=1;
parameter CNT_V_END=524;

parameter RED=8'b11100000;
parameter GREEN=8'b00011100;
parameter BLUE=8'b00000011;
parameter WHITE=8'b11111111;

parameter ADDR_MUX=16'd39203;
reg [9:0] cnt_h;
reg [9:0] cnt_v;

reg [8:0] x;
reg [8:0] y;

reg flag_x;
reg flag_y;

reg [15:0] addra,addrb;
wire [7:0] doutb;

  // cnt_h
  always @(posedge clk_25) begin 
       if(rst==1'b1) begin
            cnt_h<= 'd0;
       end 
       else if (cnt_h==CNT_H_END) begin
           cnt_h<='d0;
       end
       else 
            cnt_h<=cnt_h+1'b1;
   end 

// hsync
   always @(posedge clk_25) begin 
       if(rst==1'b1) begin
            hsync<= 'd1;
       end
       else if (cnt_h==CNT_H_END) begin
           hsync<='d1;
       end 
       else if (cnt_h==HSYNC_END) begin
           hsync<='d0;
       end
   end

// cnt_v
always @(posedge clk_25) begin 
    if(rst==1'b1) begin
        cnt_v <= 'd0;
    end 
    else if (cnt_v==CNT_V_END && cnt_h==CNT_H_END) begin
        cnt_v<='d0;
    end
    else if (cnt_h==CNT_H_END) begin
        cnt_v<=cnt_v+1'b1;
    end
end

// vsync
always @(posedge clk_25) begin
    if(rst==1'b1) begin
        vsync <= 'd1;
    end 
    else if (cnt_v==VSYNC_END && cnt_h==CNT_H_END) begin
        vsync<='d0;
    end
    else if (cnt_v==CNT_V_END && cnt_h==CNT_H_END) begin
        vsync<='d1;
    end
   
end
// x
always @(posedge clk_25) begin 
	if(rst==1'b1) begin
		x <= 'd0;
	end 
	else if (cnt_h==CNT_H_END && cnt_v==CNT_V_END && flag_x=='d0) begin
		x<=x+1'b1;
	end
	else if (cnt_h==CNT_H_END && cnt_v==CNT_V_END && flag_x=='d1) begin
		x<=x-1'b1;
	end
end

// flag_x
always @(posedge clk_25) begin 
	if(rst==1'b1) begin
		 flag_x<= 'd0;
	end 
	else if (cnt_h==CNT_H_END && cnt_v==CNT_V_END && x=='d441 && flag_x=='d0) begin
		flag_x<='d1;
	end
	else if (cnt_h==CNT_H_END && cnt_v==CNT_V_END && x=='d1 && flag_x=='d1) begin
		flag_x<='d0;
	end
end

//y
always @(posedge clk_25) begin 
	if(rst==1'b1) begin
		 y<= 'd0;
	end 
	else if (cnt_h==CNT_H_END && cnt_v==CNT_V_END && flag_y=='d0) begin
		y<=y+1'b1;
	end 
	else if (cnt_h==CNT_H_END && cnt_v==CNT_V_END && flag_y=='d1) begin
		y<=y-1'b1;
	end
end

// flag_y
always @(posedge clk_25) begin 
	if(rst==1'b1) begin
		flag_y <= 'd0;
	end 
	else if (cnt_h==CNT_H_END && cnt_v==CNT_V_END && y=='d281 && flag_y<='d0) begin
		flag_y<='d1;
	end
	else if (cnt_h==CNT_H_END && cnt_v==CNT_V_END && y=='d1 && flag_y<='d1) begin
		flag_y<='d0;
	end
end

// rgb 
always @(posedge clk_25) begin 
    if(rst=='b1) begin
         rgb<= 'd0;
    end
    //注意198x198像素时,白框范围需要改变
    else if (cnt_h>=144+x && cnt_h<=341+x && cnt_v>=35+y && cnt_v<=232+y) begin
    	rgb<=doutb;
    end
    else if (cnt_h>=144 && cnt_h<=783) begin
        if (cnt_v>=35 && cnt_v<=194) begin
            rgb<=RED;
        end
        else if (cnt_v>=195 && cnt_v<=354) begin
            rgb<=GREEN;
        end
        else if (cnt_v>=355 && cnt_v<=514) begin
            rgb<=BLUE;
        end
    end 
    else
        rgb<='d0;
    
end


// addra  这里注意写ram的时钟为50mhz,读ram的时钟为25mhz(VGA的时钟)
always @(posedge sclk) begin 
  if(rst) begin
     addra<= 'd0;
  end
  else if (pi_flag==1'b1 && addra==ADDR_MUX) begin
    addra<='d0;
  end
  else if (pi_flag==1'b1) begin
    addra<=addra+1'b1;
  end 

end

// addrb 注意ram读数据相对于读地址有一拍延迟,所以地址要早一拍给出
always @(posedge clk_25) begin 
  if(rst==1'b1) begin
    addrb <= 'd0;
  end 
  else if (cnt_h>=144+x-1 && cnt_h<=341+x-1 && cnt_v>=35+y && cnt_v<=232+y && addrb==ADDR_MUX) begin
    addrb<='d0;
  end
  else if (cnt_h>=144+x-1 && cnt_h<=341+x-1 && cnt_v>=35+y && cnt_v<=232+y) begin
    addrb<=addrb+1'b1;
  end
end


asblk_mem_8x198x198 your_instance_name (
  .clka(sclk),    // input wire clka
  .wea(pi_flag),      // input wire [0 : 0] wea
  .addra(addra),  // input wire [15 : 0] addra
  .dina(rgb_in),    // input wire [7 : 0] dina
  .clkb(clk_25),    // input wire clkb
  .addrb(addrb),  // input wire [15 : 0] addrb
  .doutb(doutb)  // output wire [7 : 0] doutb
);

endmodule

6.uart_rx模块设计(直接用之前的设计 略)

7.顶层设计

module top_sobel(
	input wire clk,//50mhz
	input wire rst,
	input wire rx,
	output wire vsync,
	output wire hsync,
	output wire [7:0]rgb
    );


wire clk_out25;
wire clk_out50;

wire rx_data;
wire pi_flag_rx_to_sobel;
wire pi_flag_sobel_to_vga;

wire [7:0] rgb_in;

 clk_wiz_gen25 instance_name
   (
    // Clock out ports
    .clk_out50(clk_out50),     // output clk_out50
    .clk_out25(clk_out25),     // output clk_out25
   // Clock in ports
    .clk_in50(clk));      // input clk_in50
	uart_rx inst_uart_rx (
			.clk     (clk_out50),
			.rst     (rst),
			.rx      (rx),
			.po_data (rx_data),
			.po_flag (pi_flag_rx_to_sobel)
		);
	sobel_ctrl  inst_sobel_ctrl (
			.clk     (clk_out50),
			.rst     (rst),
			.rx_data (rx_data),
			.pi_flag (pi_flag_rx_to_sobel),
			.rgb     (rgb_in),
			.po_flag (pi_flag_sobel_to_vga)
		);
	vga_shift inst_vga_shift (
			.sclk    (clk_out50),
			.clk_25  (clk_out25),
			.rst     (rst),
			.rgb_in  (rgb_in),
			.pi_flag (pi_flag_sobel_to_vga),
			.hsync   (hsync),
			.vsync   (vsync),
			.rgb     (rgb)
		);


endmodule

8.最终的显示效果:

上位机通过MATLAB处理,用友善助手下发原图像数据:

在这里插入图片描述
经过一系列图像处理后,最终在vga的显示效果:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/610484.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

你写的每条SQL都是全表扫描吗

你写的每条SQL都是全表扫描吗&#xff1f;如果是&#xff0c;那MySQL可太感谢你了&#xff0c;每一次SQL执行都是在给MySQL上压力、上对抗。MySQL有苦难言&#xff1a;你不知道索引吗&#xff1f;你写的SQL索引都失效了不知道吗&#xff1f;慢查询不懂啊&#xff1f;建那么多索…

Xinstall助力App地推监测,实现精准效果评估

在移动互联网时代&#xff0c;App的推广已经成为企业营销的重要手段。然而&#xff0c;如何有效地监测App地推效果&#xff0c;一直是广告主和开发者面临的难题。幸运的是&#xff0c;Xinstall作为国内专业的App全渠道统计服务商&#xff0c;为广告主和开发者提供了一站式的解决…

【C++阅览室】C++之Vector(容器)

目录 vector的介绍 vector的使用 vector的定义 vector iterator 的使用 vector 空间增长问题 vector 增删查改 vector 迭代器失效问题。&#xff08;重点&#xff09; vector的介绍 1、 vector 是表示可变大小数组的序列容器&#xff0c;可以使用连…

java.lang.NoSuchMethodException: com.ruoyi.web.controller.test.bean.HeadTeacher

软件开发过程中使用Java反射机制时遇到了下面的问题 com.ruoyi.web.controller.test.bean.HeadTeacher4b9af9a9 com.ruoyi.web.controller.test.bean.HeadTeacher4b9af9a9java.lang.NoSuchMethodException: com.ruoyi.web.controller.test.bean.HeadTeacher.<init>(java…

英飞凌TC3xx 启动逻辑梳理(1)

目录 1.启动时序总览 2.Boot Firmware干了什么&#xff1f; 2.1 BMHD梳理 2.2 HWCFG 2.3 ABM 2.4 BMHD 无效时处理方案 2.5 HSM启动如何影响SSW启动 3.小结 在调TC3xx的板子时&#xff0c;最害怕的就是刷UCB&#xff1b;稍不注意板子就上锁&#xff0c;调试器也连不上了…

MacOS java多版本安装与管理

Home - SDKMAN! the Software Development Kit Manager # 安装sdkman curl -s "https://get.sdkman.io" | bashsource "$HOME/.sdkman/bin/sdkman-init.sh"sdk version正常出现sdkman版本号就安装成功了 # 安装java # 安装java8 sdk install java 8.0…

大数据------JavaWeb------Tomcat(完整知识点汇总)

Web服务器——Tomcat Web服务器定义 它是一个应用程序&#xff08;软件&#xff09;&#xff0c;对HTTP协议的操作进行封装&#xff0c;使得程序员不必直接对协议进行操作&#xff0c;让Web开发更便捷 Web服务器主要功能 封装HTTP协议操作&#xff0c;简化开发将Web项目部署到…

浅谈如何自我实现一个消息队列服务器(7)——编写服务器部分

文章目录 一、编写服务器代码1.1、分析一个服务器应具备的功能1.1.1、成员变量1.1.2、对外提供的接口 一、编写服务器代码 再次拿出这张图&#xff0c;前面我们已经将重要概念&#xff1a;VirtualHost、exchange、msgQueue、message、binding 都实现了&#xff0c;此时就可以开…

传统行业商家转到抖音开店怎么操作?电商的本质其实都一样

我是王路飞。 现在越来越多的传统行业商家开始转型到抖音上开店了。 不仅仅是因为现在的传统电商平台的环境、玩法、规则等&#xff0c;都对中小卖家非常苛刻&#xff0c;尤其是没有团队自己单干的&#xff0c;想做起来&#xff0c;真的挺难的。 更关键的是&#xff0c;抖音…

PCIE协议-1

1. PCIe结构拓扑 一个结构由点对点的链路组成&#xff0c;这些链路将一组组件互相连接 - 图1-2展示了一个结构拓扑示例。该图展示了一个称为层级结构的单一结构实例&#xff0c;由一个根复合体&#xff08;Root Complex, RC&#xff09;、多个端点&#xff08;I/O设备&#xf…

Dependencies:查找项目中dll关联文件是否缺失。

前言 Dependencies工具作为一款优秀的DLL解析工具&#xff0c;能让你很直观地看到DLL的相关信息&#xff0c;如具备哪些功能函数、参数&#xff0c;又比如该DLL基于哪些DLL运行。判断该dll基于哪些dll运行&#xff0c;如果基于的dll丢失&#xff0c;那么就会提示。就能判断缺少…

《第一行代码》第二版学习笔记(10)——基于位置的服务

文章目录 一、使用百度定位二、获取经纬度使用百度地图移动到我的位置并让“我”显示在地图上 Android Studio中没有signingReport文件&#xff0c;解决参考文档 一、使用百度定位 下载百度LBS开放平台的SDK 在项目的app.gradle文件下添加依赖&#xff1a;implementation fil…

【北京迅为】《iTOP-3588开发板从零搭建ubuntu环境手册》-第2章 获取并安装Ubuntu操作系统

RK3588是一款低功耗、高性能的处理器&#xff0c;适用于基于arm的PC和Edge计算设备、个人移动互联网设备等数字多媒体应用&#xff0c;RK3588支持8K视频编解码&#xff0c;内置GPU可以完全兼容OpenGLES 1.1、2.0和3.2。RK3588引入了新一代完全基于硬件的最大4800万像素ISP&…

buildroot添加ssh功能

在制作了自己的buildroot生成的根文件系统之后&#xff0c;是没有ssh服务的&#xff0c;需要自行添加。 在buildroot的menuconfig里&#xff1a; Target packages -> Networking applications -> openssh 重新编译&#xff0c;还不能通过电脑连接&#xff0c;还需配置并…

101_Linux文件挂载系统相关

一、文件系统简介 传统的磁盘与文件系统应用中,一个分区就只能够被格式化成为一个文件系统,所以我们可以说一个文件系统就是一个硬盘分区。 随着新技术的出现如LMM与软件磁盘阵列software raid),这些技术可以将一个分区格式化为多个文件系统(例如LWM),也能够将多个分区合成一…

又被System.out.print给坑了一把

学过java的同学都应该知道&#xff0c;第一个程序很多人都是这样&#xff1a; public class Hello {public static void main(String[] args) { System.out.print("Hello,world&#xff01;");} } 打印结果是&#xff1a;Hello,world&#xff01; 接着可能会…

SSIM(Structural Similarity),结构相似性及MATLAB实现

参考文献 Wang, Zhou; Bovik, A.C.; Sheikh, H.R.; Simoncelli, E.P. (2004-04-01). “Image quality assessment: from error visibility to structural similarity”. IEEE Transactions on Image Processing. 13 (4): 600–612. Bibcode:2004ITIP…13…600W. CiteSeerX 10.…

ctype--数据类型转换函数——vb.net

CType 函数 语法 CType(expression, typename) 组成部分 expression 任何有效表达式。 如果 expression 的值超出 typename 所允许的范围&#xff0c;Visual Basic 将引发异常。 typenameDim 语句的 As 子句中的任何合法表达式&#xff0c;即任何数据类型、对象、结构、类或接…

【系统架构师】-选择题(十三)数据库基础

1、在某企业的营销管理系统设计阶段&#xff0c;属性"员工"在考勤管理子系统中被称为"员工"&#xff0c;而在档案管理子系统中被称为"职工"&#xff0c;这类冲突称为&#xff08; 命名冲突&#xff09;。 同一个实体在同系统中存在不同的命名&am…

2024年财富自由秘籍,创业项目大揭秘!

2024年&#xff0c;一个崭新的创业项目如日中天般迅速崛起&#xff0c;吸引了无数创业者的目光——那就是APP广告变现。这不仅是一条轻松实现财富自由的道路&#xff0c;更是一个充满无限可能的黄金领域。 在移动互联网高速发展的今天&#xff0c;智能手机已成为我们生活中不可…
最新文章