视觉图像信息处理与FPGA实现第九次作业——直方图均衡

RAM的B站视频解析

RAM的文档

一、65536x8位的单端口RAM

`timescale 1ns / 1ps
//SPRF Single Port Read/Write Function
//65535 是RAM中总的字数,也就是存储深度,X8表示每个字是8位的
module SPRF65536X8(
  Q,
  CLK,
  CEN,
  WEN,
  A,
  D
);

  //输出寄存器Q
  output [7:0]	Q;
  input 		CLK;
  //chip enable 片上使能
  input			CEN;
  //write enable 写使能
  input			WEN;
  //16位地址输入
  input [15:0]	A;
  //8位数据输入
  input [7:0]	D;
 
  reg [7:0] 	mem[0:65535];
  reg [7:0] 	Q;
 
 //控制存储体men[]
 //低电平触发CEN和WEN
  always @ (posedge CLK)
	if(!CEN & !WEN)
	  mem[A] <= D;
  

  always @ (posedge CLK)
  //片上使能OK,写使能不OK的时候
	if(!CEN & WEN)
	  Q <= mem[A];
	else if(!CEN & !WEN)
	  Q <= D;
	else
	  Q <= Q;
	  
endmodule

二、256*11的双端口RAM

双端口RAM相比单端口RAM,允许同时进行读写操作,读写过程不会相互影响。这使得它更适用于需要并行读写访问的应用场景,如FIFO(先进先出队列)、缓存等。但相应的,双端口RAM的资源消耗也更高。

`timescale 1ns / 1ps

// Two Port Read/Write Function
// 256 deep , 11bit  
// AA: 8位读地址输入端口A
// AB: 8位写地址输入端口B
// DB: 11位数据输入端口B
// CENA: 读使能端口A
// CENB: 写使能端口B
// CLKA: 读时钟输入端口A
// CLKB: 写时钟输入端口B
// QA: 11位数据输出端口A
module TPRF256X11 (
  AA,
  AB,
  DB,
  CENA,
  CENB,
  CLKA,
  CLKB,
  QA
);

  input [7:0]	AA;
  input [7:0]	AB;
  input			CENA;
  input			CENB;
  input 		CLKA;
  input 		CLKB;
  input [10:0]	DB;
  output [10:0]	QA;
 
  reg [10:0] 	mem[0:255];
  reg [10:0] 	QA;
 
 //当B写使能OK时,(DB: 11位数据输入端口B)把值给到
 //存储器的men(AB) ,(AB: 8位写地址输入端口B)
  always @ (posedge CLKB)
	if(!CENB)
	  mem[AB] <= DB;
  

 //当A读使能OK时,(AA: 8位读地址输入端口A)把值给到
 //QA (QA: 11位数据输出端口A)
  always @ (posedge CLKA)
	if(!CENA)
	  QA <= mem[AA];
	else
	  QA <= QA;

endmodule
//最终实现在CLKA和CLKB两个时钟域里面完成写和读的同时进行

三、直方图均衡代码

代码中有四处需要修改的地方,可以在vscode搜索“修改”即可找到需要修改的参数,需要根据灰度图的分辨率,调整相应的参数。以下是200*200灰度图直方图均衡的代码。

`timescale 1ns / 1ps
//*****************************************************************************	
//
// Project           	: FPGA image process class
// Module	    		: histogram_equalization    
// Description			: This module is the histogram equalization of image.
//    
// FileName	     		: histogram_equalization.v   
// Call Modules      	: -
// Called by Modules 	: histogram_equalization_tb.v 
//
// --------------------------------------------------------------------------
//
// Created Date      	: 2022.06.27
// Author            	:     Tester:        Supervisor:
// Revision History  	: V1.0 2022.06.27
//
//****************************************************************************

module histogram_equalization
//======================<port>=====================
(
//======================<input>====================
  input					clk,
  input					rst_n,
  input					valid_in,
  input					image_write_done,
  input					bmp_write_done,
  input			[7:0]	point_data_in,  
//======================<output>===================
  output		[7:0]	point_data_out,
  output	reg			init_done,
  output	reg			data_read_start,
  output	reg			data_read_done

);

  parameter				ST_IDLE = 3'd0;
  parameter				ST_INIT = 3'd1;
  parameter				ST_SAMPLE = 3'd2;
  parameter				ST_COUNT = 3'd3;
  parameter				ST_UPDATE = 3'd4;
  parameter				ST_WRITE_BMP = 3'd5;
  
//======================<signal>===================
  reg 			[2:0]	state;
  reg 			[2:0]	count;
  reg 					count_done;
  reg 			[15:0]	count_sum;
  reg 					update_done;
  
  wire 					image_process_done;
  wire 			[27:0]	update_conut_temp;
  wire 			[7:0]	update_conut;

  // sample memory
  //采样使能信号
  reg 					sample_cen;  
  reg 					sample_wen;  
  reg 			[15:0]	sample_addr;
  wire 			[7:0]	sample_wr_data;
  wire 			[7:0]	sample_rd_data;
  reg 			[7:0]	sample_rd_data_d1;
  reg 			[7:0]	sample_rd_data_d2;
  
  // count memory
  reg 					count_wr_cen;   
  reg 			[7:0]	count_wr_addr;
  reg 			[10:0]	count_wr_data;
  
  reg 					count_rd_cen;   
  reg 			[7:0]	count_rd_addr;
  wire 			[10:0]	count_rd_data;
  
  reg 			[7:0]	count_rd_addr_d1;

  assign sample_wr_data = point_data_in;

  always @ (posedge clk or negedge rst_n) begin
	if(!rst_n) begin
		state <= ST_IDLE;
	end
	else
		case(state)
			ST_IDLE: begin
				if(valid_in)
					state <= ST_INIT;
				else
					state <= ST_IDLE;
			end
			ST_INIT: begin
				if(init_done)
					state <= ST_SAMPLE;
				else
					state <= ST_INIT;
			end
			ST_SAMPLE: begin
				if(bmp_write_done)
					state <= ST_COUNT;
				else
					state <= ST_SAMPLE;
			end
			ST_COUNT: begin
				if(count_done)
					state <= ST_UPDATE;
				else
					state <= ST_COUNT;
			end
			ST_UPDATE: begin
				if(update_done)
					state <= ST_WRITE_BMP;
				else	
					state <= ST_UPDATE;
			end
			ST_WRITE_BMP: begin
				if(data_read_done)
					state <= ST_IDLE;
				else	
					state <= ST_WRITE_BMP;
			end
		endcase
  end

  always @ (posedge clk or negedge rst_n) begin
	if(!rst_n) begin
		count_wr_cen <= 1'b1;
		count_wr_addr <= 8'b0;
		count_wr_data <= 11'b0;
		count_rd_cen <= 1'b1;
		count_rd_addr <= 8'b0;
		init_done <= 1'b0;
		sample_cen <= 1'b1;
		sample_wen <= 1'b0;
		sample_addr <= 16'b0;
		//读地址计数器,d1应该是delay 1个周期
		count_rd_addr_d1 <= 8'b0;
		count_done <= 1'b0;
		count <= 3'b0;
		count_sum <= 16'b0;
		update_done <= 1'b0;
		sample_rd_data_d1 <= 8'b0;
		sample_rd_data_d2 <= 8'b0;
		data_read_start <= 1'b0;
		data_read_done <= 1'b0;
	end
	else 
		case(state)
			ST_IDLE: begin
				count_wr_cen <= 1'b0;
			end	
			ST_INIT: begin
				// start to init
				count_wr_addr <= count_wr_addr + 1'b1;
				if(count_wr_addr == 255) begin
					// init done
					init_done <= 1'b1;
					// finish init
					count_wr_cen <= 1'b1;
					count_wr_addr <= 0;
					// start to sample
					sample_cen <= 1'b0;
				end
			end	
			ST_SAMPLE: begin
				// sample 

				
				sample_addr <= sample_addr + 1'b1;
				if(bmp_write_done) begin
					//需要修改
					sample_addr <= 1078;  // 数据起始索引地址data_start_index,由BMP图片格式规定
					sample_wen <= 1'b1;
					// start to count
					count <= count + 1'b1;
				end
				
			end		
			ST_COUNT: begin
				// count
				count <= count + 1'b1;
				if(count == 5)
					count <= 0;
				
				case(count)
					// read the sample data
					0: begin
						sample_cen <= 1'b0;
						sample_addr <= sample_addr + 1'b1;
					end
					// read the count 
					1: begin
						sample_cen <= 1'b1;
					end
					// count the data
					2: begin
						count_rd_cen <= 1'b0;
						count_rd_addr <= sample_rd_data;
					end
					3: begin
						count_rd_cen <= 1'b1;
					end
					// write back
					4: begin
						count_wr_cen <= 1'b0;
						count_wr_data <= count_rd_data + 1'b1;
						count_wr_addr <= count_rd_addr;
						//需要修改
						if(sample_addr == 41078) begin //读入的BMP图片的所有数据地址 file_read = $fread(bmp_data,bmp_file_read)
							sample_addr <= 0;
							count_done <= 1'b1;
							count_wr_addr <= 0;
							count_rd_cen <= 1'b0;
							count_rd_addr <= 0;
							count_wr_cen <= 1'b1;
						end
					end
					5: begin
						count_wr_cen <= 1'b1;
					end				
				endcase
			end		
			ST_UPDATE: begin
				// read the count data
				count_rd_addr <= count_rd_addr + 1'b1;
				count_sum <= count_sum + count_rd_data;
				// update and write
				count_wr_cen <= count_rd_cen;
				count_rd_addr_d1 <= count_rd_addr;
				count_wr_addr <= count_rd_addr_d1;
				count_wr_data <= update_conut;
				if(count_wr_addr == 255) begin
					update_done <= 1'b1;
					count_rd_cen <= 1'b1;
					count_wr_cen <= 1'b1;
					// start to write bmp
					sample_cen <= 1'b0;
					sample_wen <= 1'b1;
					sample_addr <= 0;
				end

			end	
			ST_WRITE_BMP: begin
				//需要修改
				if(sample_addr < 1078) begin // 地址小于1078时为BMP的头文件数据
					sample_addr <= sample_addr + 1;
					sample_rd_data_d1 <= sample_rd_data;
					sample_rd_data_d2 <= sample_rd_data_d1;
					if(sample_addr == 1)
						data_read_start <= 1'b1;
				end
				else begin
					sample_addr <= sample_addr + 1;
					sample_rd_data_d1 <= sample_rd_data;
					sample_rd_data_d2 <= sample_rd_data_d1;
					
					count_rd_cen <= 1'b0;
					count_rd_addr <= sample_rd_data;
					//修改为400*400的结束数据,这个是200*200的
					if(sample_addr == 41079) begin // 除BMP的头文件数据的其余数据
						data_read_done <= 1'b1;
						data_read_start <= 1'b0;
						sample_cen <= 1'b1;
						count_rd_cen <= 1'b1;
					end
				end
			end	
		endcase
  end

  // 255/40000 * count_sum
  // 20bit fixed,这里应该是为了保证灰度的值在255之内,同时又要避免除法
  assign update_conut_temp = 6684 * count_sum; //20240327:255/4000x1024x1024=6684
  assign update_conut = update_conut_temp[27:20]; 
  //需要修改 1081
  //  
  assign point_data_out = ~update_done ? 0 : (sample_addr < 1081)? sample_rd_data_d2 : count_rd_data;


  SPRF65536X8 u_sample_mem(
	.Q		(sample_rd_data),
	.CLK	(clk),
	.CEN	(sample_cen),
	.WEN	(sample_wen),
	.A		(sample_addr),
	.D		(sample_wr_data)
  );


  TPRF256X11 u_count_mem(
	.AA		(count_rd_addr),
	.AB		(count_wr_addr),
	.DB		(count_wr_data),
	.CENA	(count_rd_cen),
	.CENB	(count_wr_cen),
	.CLKA	(clk),
	.CLKB	(clk),
	.QA		(count_rd_data)
  );


endmodule

四、tb文件

因为vivado的testbench使用$open函数的路径问题,详情见我的CSDN博客:https://blog.csdn.net/weixin_44357071/article/details/137203642

,我的操作如下:

  • 代码正常放进来,然后点一下vivado里面的"run simulation"仿真(产生sim1这个文件夹)

  • 然后把图片放在正确的地方,我的路径如图image-20240507131542692

`timescale 1ns / 1ps
//*****************************************************************************	
//
// Project           	: FPGA image process class
// Module	    		: histogram_equalization_tb     
// Description			: This module is the tb module of histogram equalization.
//    
// FileName	     		: histogram_equalization_tb.v   
// Call Modules      	: histogram_equalization.v
// Called by Modules 	: -
//
// --------------------------------------------------------------------------
//
// Created Date      	: 2022.06.19
// Author            	:     Tester:        Supervisor:
// Revision History  	: V1.0 2022.06.19
//
//****************************************************************************
`define Clock 20

module histogram_equalization_tb;

//======================<port>=================================
  reg						clk;
  reg						rst_n;
  reg		[7:0]			point_data_in;
  reg						image_write_done;
  reg						bmp_write_done;
  reg						valid_in;
  reg 		[7:0] 			bmp_data[0:50000];
  reg						data_read_start_d1;
  
  wire		[7:0]			point_data_out;
  wire		[7:0]			bmp_data_out;
  wire						init_done;
  wire						data_read_done;
  wire						data_read_start;

//======================<clock and reset>======================
  initial begin
	clk = 1;
	forever
		#(`Clock/2) clk = ~clk;
  end

  initial begin
	rst_n = 0;
	#(`Clock*20 + 1);
	rst_n = 1;
end

// read the bmp data
  integer bmp_file_read;
  integer file_read;
  integer data_start_index;
  integer bmp_size;

  initial begin
	bmp_file_read = $fopen(".\\picture_copy.bmp","rb");
	file_read = $fread(bmp_data,bmp_file_read);
	// get the data start index
	data_start_index = {bmp_data[13], bmp_data[12], bmp_data[11], bmp_data[10]};
	// get the bmp size
	bmp_size = {bmp_data[5], bmp_data[4], bmp_data[3], bmp_data[2]};
  end


//======================<input signal>=========================
  initial begin
  end  

  integer index;
  always @ (posedge clk or negedge rst_n) begin
	if(!rst_n) begin
		index <= 0;
		image_write_done <= 0;
		bmp_write_done <= 0;
		point_data_in <= 0;
		valid_in <= 0;
	end
	// when index=data_start_index, it starts to process image
	else begin
		valid_in <= 1;
		if(init_done) begin
			if (index == data_start_index) begin
				image_write_done <= 1;
				index <= index + 1;
				point_data_in <= bmp_data[index];
			end
			else if(index == bmp_size)
				bmp_write_done <= 1;
			else begin
				index <= index + 1;
				point_data_in <= bmp_data[index];
			end
		end
	end
  end

// if not process image, select the point_data_out, else select point_data_in
//  assign bmp_data_out = image_write_done ? point_data_out : point_data_in;
  assign bmp_data_out = point_data_out;
  
  always @ (posedge clk or negedge rst_n) begin
	if(!rst_n) begin
		data_read_start_d1 <= 1'b0;
	end
	else 
		data_read_start_d1 <= data_read_start;
  end

  
// write the bmp data
  integer bmp_file_write;
  initial begin
	bmp_file_write = $fopen(".\\picture_histogram_equalization.bmp","wb");
  end
  // write the data every clock
  always @ (posedge clk) begin
	if(rst_n) begin
		// when index=0, not write
		if(data_read_start_d1)
			$fwrite(bmp_file_write, "%c", bmp_data_out);
		else if(data_read_done) begin
			$fclose(bmp_file_write);
			$fclose(bmp_file_read);
			$display("Write bmp file complete, Close the file");	
			$finish;
		end
	end
  end
 
//======================<Module Instance>======================
histogram_equalization u_histogram_equalization
  (
    .clk					(clk),
    .rst_n					(rst_n),
    .valid_in				(valid_in),
    .image_write_done		(image_write_done),
    .bmp_write_done			(bmp_write_done),
    .point_data_in			(point_data_in),
    .point_data_out	    	(point_data_out),
    .init_done	  			(init_done),
    .data_read_start	    (data_read_start),
    .data_read_done	    	(data_read_done)
  );


endmodule


五、产生合适的灰度图片

将图片用软件“画图打开”,点击"属性",按照如下设置。

image-20240507132255978

然后将图片另存为bmp图像,256色图,在代码里的图像数据开始位置是由这个参数决定的。

image-20240507132509631

五、直方图均衡效果

均衡前

picture_copy

均衡后

picture_histogram_equalization

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

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

相关文章

如何在已经安装好的PostgreSQL14中安装uuid 扩展

当前环境 PG14.8 LINUX 8.8 存在问题&#xff1a; 开发人员问&#xff0c;PG中&#xff0c;支持 生成UUID吗&#xff0c;具体是什么&#xff0c;答&#xff0c;类似这个函数 uuid_generate_v4() 看了一下&#xff0c; select uuid_generate_v4();会报错&#xff0…

2024-05-07 商业分析-如何在社会层面做一个更好的工具人-记录

摘要: 2024-05-07 商业分析-如何成为一个靠谱的工具人 如何在社会层面做一个更好的工具人 那么今天讲的这个主题呢&#xff0c;对吧&#xff1f;你们一看啊&#xff0c;就觉得这个就不应该我讲是吧啊&#xff0c;但是呢这个逻辑呢我还得跟你们讲一下啊&#xff0c;就是如何成为…

2009-2022年上市公司华证ESG评级评分数据(含细分项)

2009-2022年上市公司华证ESG评级评分数据&#xff08;含细分项&#xff09; 1、时间&#xff1a;2009-2022年 2、来源&#xff1a;华证ESG 3、指标&#xff1a;证券代码、证券简称、综合评级、年度、综合得分、E评级、E得分、S评级、S得分、G评级、G得分 4、范围&#xff1…

AI伦理和安全风险管理终极指南

人工智能&#xff08;AI&#xff09;正在迅速改变各个领域的软件开发和部署。驱动这一转变的两个关键群体为人工智能开发者和人工智能集成商。开发人员处于创建基础人工智能技术的最前沿&#xff0c;包括生成式人工智能&#xff08;GenAI&#xff09;模型、自然语言处理&#x…

Python读取ASC文件并转换成Excel文件(坐标)

import pandas as pd# 读取asc文件&#xff0c;指定空格为分隔符 df pd.read_csv(out_view2.asc, sep , headerNone)# 去掉空列 df df.dropna(howall, axis1)# 将数据保存到Excel文件 df.to_excel(out_view2.xlsx, indexFalse, headerFalse)效果图

Day1| Java基础 | 1 面向对象特性

Day1 | Java基础 | 1 面向对象特性 基础补充版Java中的开闭原则面向对象继承实现继承this和super关键字修饰符Object类和转型子父类初始化顺序 多态一个简单应用在构造方法中调用多态方法多态与向下转型 问题回答版面向对象面向对象的三大特性是什么&#xff1f;多态特性你是怎…

unity华为sdk接入指路指南

目前比较靠谱的几个方案&#xff1a;试过几个仅供参考 温馨提示&#xff1a;最高目前可支持方案到unity2021版本以下&#xff0c;以上请联系华为官方寻求技术支持 Unity集成华为游戏服务SDK方式&#xff08;一&#xff09;&#xff1a;集成Unity官方游戏SDK&#xff1a; 华为…

代码随想录算法训练营第十八天:二叉树的层序遍历(中间放假)

代码随想录算法训练营第十八天&#xff1a;二叉树的层序遍历&#xff08;中间放假&#xff09; ‍ ​​ 102.二叉树的层序遍历 力扣题目链接(opens new window) 给你一个二叉树&#xff0c;请你返回其按 层序遍历 得到的节点值。 &#xff08;即逐层地&#xff0c;从左到右…

速览Coinbase 2024Q1 财报重点:业务全面开花,净利润达11.8亿美元

作者&#xff1a;范佳宝&#xff0c;Odaily 星球日报 近期&#xff0c;Coinbase 发布了其 2024 年第一季度财报。 报告显示&#xff0c;Coinbase 第一季度营收为 16.4 亿美元&#xff0c;高于分析师平均预期的 13.4 亿美元&#xff1b;净利润为 11.8 亿美元&#xff0c;合每股…

关于Centos 7/8 网络设置 与工具连接

网络三步曲的配置 1、首先更改虚拟机的网络配置 查看子网地址以及网关 如果有要求需要更改IP地址&#xff0c;规定第三位是指定数值&#xff0c;那么需要全部更改 例如&#xff0c;IP地址为192.168.200.30 其中200为重点&#xff0c;更改时为以下步骤 1、点击DHCP设置&#x…

贪吃蛇大作战(C语言--实战项目)

朋友们&#xff01;好久不见。经过一段时间的沉淀&#xff0c;我这篇文章来和大家分享贪吃蛇大作战这个游戏是怎么实现的。 &#xff08;一&#xff09;.贪吃蛇背景了解及效果展示 首先相信贪吃蛇游戏绝对称的上是我们00后的童年&#xff0c;不仅是贪吃蛇还有俄罗斯⽅块&…

找不到模块“vue-router”。你的意思是要将 moduleResolution 选项设置为 node,还是要将别名添加到 paths 选项中?

在tsconfig.app.json中添加&#xff0c;记得一定是 tsconfig.app.json 中&#xff0c;如添加到 tsconfig.node.json 还是会报错的 哈哈哈哈&#xff0c;不瞒你们&#xff0c;我就添加错了&#xff0c;哈哈哈。所以这也算写一个demo提醒自己 "compilerOptions": {&qu…

【牛客】排列计算

原题链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 目录 1. 题目描述 2. 思路分析 3. 代码实现 1. 题目描述 2. 思路分析 如果直接涂色来计算单点权重&#xff0c;2e5*2e5必然超时。 所以用差分进行优化。 3. 代码实现 #include<bits/stdc.h> using name…

Go语言fmt包深度探索:格式化输入输出的利器

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 &#x1f3ad; 引言一、基础输出函数fmt.Print与fmt.Println&#x1f4cc; fmt.Print&#xff1a;纯粹输出&#xff0c;不带换行&#x1f4cc; fmt.Println&#xff1a;输出后自动添加换行符 二、格式化输出fmt.Printf&…

鸿蒙开发接口Ability框架:【@ohos.application.missionManager (missionManager)】

missionManager missionManager模块提供系统任务管理能力&#xff0c;包括对系统任务执行锁定、解锁、清理、切换到前台等操作。 说明&#xff1a; 本模块首批接口从API version 8开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。 导入模块 impo…

“Postman 中文版使用教程:如何切换到中文界面?”

Postman 的很好用的接口测试软件。但是&#xff0c;Postman 默认是英文版的&#xff0c;也不支持在软件内切换为中文版。很多同学的英语并不是很好&#xff0c;看到一堆的英文很是头痛。 今天我们来介绍下&#xff1a;切换到 Postman 中文版的方法。想要学习更多的关于 Postma…

Type-C转音频(USB2.0数据传输)+PD充电芯片乐得瑞LDR6500/LDR6023

LDR6500 USB-C DRP 接口 USB PD 通信芯片概述 Type-C转音频(USB2.0数据传输)PD充电芯片乐得瑞LDR6500LDR6500是乐得瑞科技针对USB Type-C标准中的Bridge设备而开发的USB-C DRP&#xff08;Dual Role Port&#xff0c;双角色端口&#xff09;接口USB PD&#xff08;Power Deliv…

Qt---day2-信号与槽

1、思维导图 2、 拖拽式 源文件 #include "mywidget.h" #include "ui_mywidget.h" MyWidget::MyWidget(QWidget *parent) : QWidget(parent) , ui(new Ui::MyWidget) { ui->setupUi(this); //按钮2 this->btn2new QPushButton("按钮2",th…

LeetCode 面试经典150题 252.会议室

题目&#xff1a;给定一个会议时间安排的数组 intervals &#xff0c;每个会议时间都会包括开始和结束的时间 intervals[i] [starti, endi] &#xff0c;请你判断一个人是否能够参加这里面的全部会议。 思路&#xff1a;因为一个人在同一时刻只能参加一个会议&#xff0c;因此…

选择适用的无尘棉签:保障洁净生产环境下的高效擦拭

随着洁净生产条件的日益普及和无尘级别要求的提高&#xff0c;无尘擦拭用品成为广大用户追捧的必备工具。在这个领域&#xff0c;无尘棉签作为一种高效的擦拭工具&#xff0c;扮演着重要的角色。然而&#xff0c;面对市场上种类繁多的无尘棉签&#xff0c;如何选择最合适的产品…
最新文章