news 2026/9/3 3:12:10

STM32F407+OV7670二自由度云台颜色识别与目标跟踪系统实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32F407+OV7670二自由度云台颜色识别与目标跟踪系统实战

简介:基于STM32F407与OV7670的颜色识别与目标跟踪系统完整工程,适合嵌入式学习者、电子竞赛团队及机器人爱好者,帮助掌握图像处理、云台控制与无线交互的完整流程。系统融合二自由度舵机云台PD算法、HSL颜色空间转换、阈值判定以及蓝牙通信安卓APP交互,覆盖从硬件选型、算法设计到人机界面的设计闭环。资源包共164个文件,约2.07MB,以C/H源码为主,包含摄像头驱动、颜色识别、舵机控制、蓝牙通信等核心代码;另有XML工程配置、JAVA安卓端代码、Gradle构建脚本及PNG原理图,可按模块对照学习。已有183人学习下载。工程内含完整可编译的嵌入式项目与配置,便于复现颜色追踪流程,理解PD参数整定与HSL阈值选取方法,可作为毕业设计、电子设计竞赛或智能车开发的参考模板。 前段时间我把一套“STM32F407 + OV7670摄像头 + 二自由度云台”的颜色识别与目标跟踪系统从头到尾跑通了,从摄像头出图、HSL颜色判断、云台跟随到蓝牙上报安卓APP,整条链路都调稳定了。最初我以为最难的是把摄像头调出图像,真正动手之后才发现,颜色阈值在不同光照下的稳定性、云台跟随时那套控制参数,才是真正让人掉头发的地方。这篇文章把这套系统的选型思路、核心原理、关键代码和踩坑过程完整写出来,适合正在做课程设计、准备电子设计竞赛,或者想第一次把“图像采集 + 算法 + 运动控制 + 无线交互”整条链路跑通的同学参考,也适合想从纯单片机开发往嵌入式视觉方向过渡的工程师。

1. 硬件选型逻辑:F407、OV7670与云台结构怎么搭

1.1 为什么是STM32F407而不是F103或ESP32

选型这件事,很多初学者容易拍脑袋。我在这套项目里选定STM32F407,不是因为它便宜,而是因为它的算力、外设和调试成本刚好卡在这类项目的甜点上。

STM32F407主频168MHz,带单精度FPU。做颜色识别时,如果每个像素都做浮点HSL转换,没有FPU的F103会慢到让人怀疑人生,而F407的FPU虽然不能直接让像素处理飞起来,但至少能在代码不做大规模整数化改造的情况下跑得动。除了算力,更关键的是外设:F407自带DCMI数字摄像头接口,可以接OV7670这类并行摄像头,配合DMA直接搬运数据,CPU只在关键帧中断里介入;同时它还有多个高级定时器,可以输出舵机用的PWM,多个UART也方便挂蓝牙、调试串口、显示模块,一颗芯片就把“采集、处理、控制、通信”全包了。

对比一下:F103算力弱、没有DCMI,用IO口模拟抓取像素基本只能跑低分辨率;ESP32虽然有摄像头接口,但如果你已经熟悉STM32的生态,又要输出多路PWM控制舵机,调试起来其实没有F407顺手。所以结论很直接:这类“图像处理+运动控制”项目,F407是当前综合成本最低的起步平台。

1.2 OV7670的数据通路:DCMI直连和FIFO缓存怎么选

OV7670是一颗很经典的CMOS图像传感器,支持SCCB接口配置寄存器,输出格式可以设成RGB565或YUV422,分辨率常用QVG A(320x240)或VGA(640x480)。它最麻烦的地方是时序,VSYNC、HREF、PCLK、D0-D7这些信号都要正确接入主控。

在STM32F407上接OV7670,有两种主流方案。第一种是DCMI直连,把OV7670的8位数据线和同步信号直接接DCMI引脚,配置DCMI为RGB565模式,DMA把帧数据搬到内存,这种方案效率高,但要注意F407的SRAM有限,一帧QVGA RGB565约150KB,加上系统其他变量,整帧缓存很紧张,所以实际通常会开两路DMA缓冲,边采集边用半传输中断处理上半帧数据。第二种是加FIFO芯片(典型是AL422B,256KB的异步FIFO),OV7670先把一帧图像写进FIFO,STM32再用普通IO或FSMC把数据读回来,这种方案对时序要求低很多,调起来更容易,代价是多一颗芯片和走线。

如果只做单色目标的颜色识别,我建议优先选DCMI直连方案,虽然配置麻烦,但帧率和稳定性都比FIFO好。如果你只是想快速把Demo跑通,FIFO方案能减少很多调试时间,但要提醒一句:AL422B在QVGA RGB565下可以缓存一帧,如果调高分辨率或帧率,FIFO很快就会撑爆。

1.3 舵机云台与供电设计

云台结构上,两个自由度分别由一个9g舵机控制,常见型号是SG90或MG90S,PWM频率50Hz,脉宽0.5ms到2.5ms对应0度到180度。水平舵机控制左右转动(Pan),垂直舵机控制上下俯仰(Tilt),安装时尽量让两个舵机的转动轴垂直,这样X方向偏差和Y方向偏差的调节就是解耦的。

电机选型上,SG90扭矩小、响应快,适合轻载;如果摄像头模组加支架比较重,用MG90S金属齿轮版本会更稳,但金属齿轮舵机的死区通常比塑料齿轮大一点,后面PD控制死区参数也要相应调大。

这里有一个很容易踩的坑:舵机启动瞬间电流能到几百毫安到1A级别,如果把舵机电源和STM32主控接到同一个LDO上,舵机一转,主控电压就掉到3V以下,直接复位。我实际把5V电源单独给舵机,主控用另外的3.3V LDO供电,两个电源的地线单点相连,系统才稳定下来。供电设计看起来不起眼,但排查复位问题的时候,好多次都是它背锅。

2. HSL颜色识别:从RGB565到阈值判定的完整实现

2.1 为什么RGB直接判阈值扛不住光照变化

做颜色识别,第一反应就是直接对RGB分量做范围判断,比如红色就判断R>100且G<80且B<80。这个方法在固定光源下能用,但稍微把物体挪到窗前,或者灯光颜色偏暖偏冷,同样的红色物体,RGB三个分量的值就会剧烈变化,阈值马上失效。

原因很简单:RGB三个分量把“亮度”和“颜色”混在一起,光照变强时R、G、B同时变大,光照变弱时又同时变小,单纯看某个通道的绝对范围,很容易把阴影里的目标漏掉,或者把高光白墙误判成目标。HSL空间把颜色的本质拆开了——H(色相)描述“这是什么颜色”,S(饱和度)描述“颜色有多浓”,L(亮度)描述“有多亮”。对同一类颜色,光照变化主要影响L和S,而H相对稳定,所以在HSL空间做阈值判定,鲁棒性比RGB高一个台阶。

这也是为什么标题里强调“HSL颜色空间转换”而不是直接RGB——实际项目里,人眼觉得“这是一个红色球”的时候,它的H值确实落在红色区间,而RGB三个分量可能完全不在同一量级。用阈值判定目标身份时,HSL让“语义颜色”成为可能。

2.2 嵌入式HSL转换的整数实现

OV7670在RGB565模式下,每个像素用16位表示,高5位是R,中间6位是G,低5位是B。先要拆出RGB分量并扩展到8位,再转HSL。转换公式本身不复杂,但如果在STM32上对每个像素做浮点运算,速度会非常难看。实际工程里我用整数运算和查表近似。

// RGB565转8位RGB分量 uint8_t r = (rgb565 >> 11) & 0x1F; uint8_t g = (rgb565 >> 5) & 0x3F; uint8_t b = rgb565 & 0x1F; r = (r << 3) | (r >> 2); g = (g << 2) | (g >> 4); b = (b << 3) | (b >> 2);

拿到0-255范围的RGB后,做HSL转换。标准的HSL公式里,H是0-360度的浮点数,但为了在嵌入式里快速比较和保存,我会把H压缩到0-255的uint8,阈值上下限用uint8来判断。

void rgb888_to_hsl(uint8_t r, uint8_t g, uint8_t b, uint8_t *h, uint8_t *s, uint8_t *l) { uint8_t max = MAX(r, MAX(g, b)); uint8_t min = MIN(r, MIN(g, b)); uint8_t delta = max - min; *l = (max + min) >> 1; if (delta == 0) { *h = 0; *s = 0; return; } if (*l < 128) { *s = 255 * delta / (max + min); } else { *s = 255 * delta / (511 - max - min); } uint16_t hue; if (max == r) { hue = 60 * (g - b) / delta; if (g < b) hue += 360; } else if (max == g) { hue = 60 * (b - r) / delta + 120; } else { hue = 60 * (r - g) / delta + 240; } *h = (uint8_t)(hue * 255 / 360); }

这段代码里,S和H的计算都用了一次除法、一次乘法。对单张QVGA图来说,7万多像素全过一遍,裸算会占不少CPU时间。实际项目里我加了一道预处理:先用RGB粗筛把明显不可能是目标颜色的像素直接跳过,只有通过粗筛的像素才做完整HSL转换。比如目标如果是红色,可以先快速判断R明显大于G且R明显大于B,这样大部分背景像素就被挡掉了,HSL转换的数量能降到原来的十分之一。

2.3 阈值标定:别手撸参数,用颜色学习模式

手动设阈值参数是最不靠谱的做法,因为不同环境下同一种颜色的H、S、L范围都不一样。我在系统里加了一个“颜色学习模式”:云台先对准目标物体,按下按键或通过蓝牙下发一条标定指令,MCU读取画面中心一块区域的像素,统计这块区域的H均值与标准差,然后自动生成阈值。

具体的判定逻辑并不复杂:

uint8_t target_h_low = h_mean - 15; uint8_t target_h_high = h_mean + 15; uint8_t target_s_min = s_mean - 30; uint8_t target_l_min = l_mean - 40; uint8_t target_l_max = l_mean + 40;

色相用均值加减15左右,是因为同一物体上轻微色偏导致的H漂移通常不会超过这个范围;饱和度下限定在30以上,是为了排除灰白色区域——当S太低时,颜色接近灰色,H值没有意义。这个“颜色学习+自动阈值”的思路,比对着图片调参数高效得多,而且换一个目标物体时不用改代码,重新标定一次就行。

2.4 帧间目标质心计算

阈值判定得到的是一个个像素的“是/不是目标”二值结果,要把这些像素变成云台能用的坐标,需要做连通域分析或至少是质心计算。我的做法是扫描整帧,把所有判定为目标像素的坐标累加,最后除以目标像素总数,得到目标的质心坐标。

uint32_t sum_x = 0, sum_y = 0; uint16_t count = 0; for (uint16_t y = 0; y < IMG_HEIGHT; y++) { for (uint16_t x = 0; x < IMG_WIDTH; x++) { if (is_target_pixel(x, y)) { sum_x += x; sum_y += y; count++; } } } if (count > MIN_TARGET_PIXELS) { target_cx = sum_x / count; target_cy = sum_y / count; } else { target_lost = true; }

这里有个细节:如果直接用整帧扫描,一些零散的噪声点会影响质心位置,我用了一个简单的去噪条件——目标像素总数必须大于一个最小值(比如画面面积的0.5%),否则认为目标丢失,这能有效滤掉传感器噪声和随机的环境干扰。

3. 二自由度云台PD控制:让摄像头稳稳咬住目标

3.1 图像偏差到舵机角度的映射

目标质心坐标拿到后,下一步是把坐标偏差转换成舵机角度增量。设图像中心为(cx_mid, cy_mid),目标质心为(target_cx, target_cy),水平偏差error_x = target_cx - cx_mid,垂直偏差error_y = target_cy - cy_mid。

这里有一个很多新手理解错的地方:偏差不等于舵机绝对角度,舵机要按偏差比例去“校正”当前角度。比如目标偏右了,云台就要往右转;偏差越大,转得越快,但不是一步转到最大角度,否则目标稍微移动一点,云台就会疯狂抽搐。这个“按偏差比例调整”的过程,就是PD控制的基础思想。

3.2 PD控制器的离散化实现与限幅、死区

PD控制里,P项(比例项)让舵机响应偏差,偏差大就转得多;D项(微分项)感知偏差的变化趋势,偏差快速变化时产生阻尼,抑制超调和振荡。之所以不用PID,是因为云台系统有舵机死区和机械摩擦,I项会让误差积分越积越大,导致系统周期性过冲,甚至进入极限环振荡。实际测试下来,PD已经足够稳住跟踪,加I反而画蛇添足。

离散化的PD实现非常直接,用当前误差和上一帧误差算出误差变化率:

float pd_control(float error, float kp, float kd, float dt) { static float prev_error = 0; float derivative = (error - prev_error) / dt; prev_error = error; float output = kp * error + kd * derivative; // 限幅:每帧最多调整3度,防止目标突变时猛打舵 if (output > MAX_DELTA_ANGLE) output = MAX_DELTA_ANGLE; if (output < -MAX_DELTA_ANGLE) output = -MAX_DELTA_ANGLE; // 死区:偏差小于5个像素时不动,避免舵机微抖 if (fabs(error) < 5) output = 0; return output; }

限幅是必须的。目标从画面左侧跳到右侧时,误差可能瞬间达到160像素,如果不限幅,舵机会以最大速度猛甩过去,产生严重过冲和机械冲击。限幅之后,舵机每次最多转3度,目标就算跳变,云台也只是快速但不粗暴地追过去,机械结构寿命也更好。

3.3 参数整定的实际操作顺序

PD参数整定是我在这个项目里耗时最多的一步。我推荐一套很直观的流程:

第一步,只保留P项,Kd设为0。从很小的Kp开始(比如0.1),观察云台是否跟随目标。逐步增大Kp,直到云台出现明显的小幅度振荡,记录下这个临界Kp,然后把它降到临界值的一半左右。

第二步,固定Kp后,慢慢增加Kd。观察云台在目标突然移动时是否过冲,Kd越大阻尼越强,但太大也会让舵机动作变得迟滞、发肉。调到目标快速移动后云台能平滑停下,不来回摆动,Kd就差不多了。

在我这套320x240画面、跟踪帧率约20-30fps的系统里,Kp落在0.3-0.5附近,Kd落在0.05-0.15附近。这个值不是通用答案,不同舵机、不同帧率、不同图像尺寸下都会变,但整定顺序是通用的。

实际调试中还有一个容易被忽略的问题:目标丢失时的策略。我设定的逻辑是,连续几帧找不到目标,云台停止更新角度,保持原地等待;如果目标在画面边缘连续出现又消失,就往最后一次出现的偏差方向小幅搜索,但不对超时状态做持续猛转。没有这个逻辑,目标一丢,舵机会在最后误差方向反复横跳,很快烧舵机。

4. 蓝牙通信与安卓APP交互

4.1 串口协议设计

蓝牙模块选HC-05,它支持主从一体和串口透传,配置成从机模式后,数据从USART3直接透传到手机。串口波特率设成115200,这个速率下发送一帧几十字节的状态数据毫无压力。

通信协议设计上,我建议直接用“帧头 + 命令 + 长度 + 数据 + 校验”的标准结构,不要发裸数据。没有协议的裸串口流,事后解析就是灾难。我用的是这样一帧:

字段字节数说明
帧头110xAA
帧头210x55
命令字10x01 跟踪状态;0x02 手动控制等
数据长度1后面数据的字节数
数据字段N实际业务数据,小端序
校验和1前面所有字节累加和的低8位

发送跟踪状态时,数据字段包含目标质心X、目标质心Y、水平舵机角度、垂直舵机角度、目标是否锁定五个值。接收端先等0xAA 0x55再解析,避免串口断帧导致错位。

uint8_t tx_buf[12]; tx_buf[0] = 0xAA; tx_buf[1] = 0x55; tx_buf[2] = CMD_TRACK_STATUS; tx_buf[3] = 4; tx_buf[4] = target_cx >> 8; tx_buf[5] = target_cx & 0xFF; tx_buf[6] = target_cy >> 8; tx_buf[7] = target_cy & 0xFF; tx_buf[8] = checksum(tx_buf, 8); HAL_UART_Transmit(&huart3, tx_buf, 9, 10);

上位机下发的指令也很简单,比如手动模式、标定当前颜色、启动/停止跟踪。命令集保持精简,两侧的解析代码都容易维护。

4.2 安卓端实现与权限避坑

安卓端有两种快速实现方式。如果你只是验证通信链路,用MIT App Inventor最省事,拖一个蓝牙客户端组件,选择HC-05配对,就能收发串口数据,适合快速原型。如果你要做得完整一些,用Android Studio原生BluetoothAdapter写SPP连接,关键代码核心是这几个调用:

BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); BluetoothDevice device = adapter.getRemoteDevice("XX:XX:XX:XX:XX:XX"); BluetoothSocket socket = device.createRfcommSocketToServiceRecord( UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")); socket.connect();

这个UUID是SPP服务的标准UUID,不要随便改。连接成功后,从socket.getInputStream()读取数据,并把读取放到子线程或协程里,Android不允许在主线程做阻塞IO,否则界面会ANR。

权限是安卓端最容易被忽视的大坑。Android 6.0以上扫描蓝牙需要动态申请定位权限,Android 12以上又引入了BLUETOOTH_SCAN和BLUETOOTH_CONNECT运行时权限,只写Manifest不动态申请,程序会直接崩。我在项目里把权限申请流程放在进入主界面之前,统一处理,避免后面排查半天。

APP显示内容不用做得太花哨,我设置了一个简易仪表盘:显示目标X、目标Y、云台水平角和俯仰角、目标是否锁定,再加上两个按钮控制颜色标定模式和手动云台模式,足够完成联调。数据解析时用状态机逐字节处理,每次读到0xAA 0x55就认为进入新帧,再按长度读取后续字节,这样就算蓝牙偶尔丢一个字节,也不会让整个数据流错乱。

5. 真实环境中的三个大坑与对应解法

5.1 光照突变导致丢目标

第一个大坑是光照突变。把系统从室内搬到窗边,目标瞬间丢失,这是我在调试初期遇到最频繁的情况。HSL虽然比RGB鲁棒,但光照突变时,S值和L值变化剧烈,如果S降到阈值下限以下,或者L超出范围,目标照样丢失;极端过曝时,红色物体的H值也会因为RGB分量饱和而发生几十度的漂移。

对策有两层。第一层,在OV7670配置中开启自动曝光和自动白平衡,让传感器尽量把亮度拉回中间范围,这能显著减少L值的大幅漂移。第二层,系统里加一个“阈值自适应”逻辑——当目标连续丢失超过一定时间,重新用当前环境下的亮度平均值微调L阈值范围。有次我用手机屏幕光照射目标,因为屏幕色温偏蓝,本来红色的物体拍出来H值直接漂了将近40度,固定阈值完全没法用,后来加了标定按钮,每次换环境重新标定一次,问题才根治。

5.2 帧率上不去:ROI局部扫描

第二个坑是帧率。全帧扫描320x240,每个像素先粗筛再HSL精判,加上计算质心,一帧下来CPU耗时相当可观,系统实时性明显下降。优化手段是ROI局部扫描:目标锁定后,不再扫描整帧,而是以目标当前位置为中心,只扫描一块约120x90的区域。这样既保证目标在移动时大概率还在搜索窗内,又让单帧处理像素数降到原来的七分之一。

ROI的边界处理要小心:当目标靠近画面边缘时,搜索窗会被切掉一部分,这时我做了自动扩展——如果搜索窗接触画面边界,就把搜索窗稍微向另一侧扩展,避免目标在边缘时反而搜不到。加上ROI优化后,跟踪帧率稳定到了30fps左右,云台的跟随平滑度明显提升。

5.3 舵机抖动、不回中和供电地线问题

第三个坑是舵机抖动和不回中。最典型的表现是:目标静止不动,云台却在目标附近小幅来回摆动。一开始我以为是PD参数没调好,反复调Kd都没有本质改善,最后用万用表一测,舵机供电在动作瞬间跌到4.2V,是电源带不动负载。

解决办法是给舵机一个独立的5V2A电源,主控供电和舵机供电的地线单点连接,模拟地和功率地彻底分开走线。电源问题解决后,舵机瞬间大电流不再拖垮主控,角度也稳定了。还有一档抖动是PWM占空比精度问题,TIM配置不当导致舵机角度在小数级别来回跳动,这个可以通过降低PWM分辨率的冗余——调整定时器预分频和自动重载值,让1度对应足够大的计数值,来避免最低有效位抖动脉宽。

关于不回中,还有一个经验:每次上电后,我把两个舵机先驱动到同一个已知角度(比如90度),做一次“归零校准”,再进入跟踪模式。这样机械装配误差和舵机初始位置差异就不会影响后续PD控制。


最后说点个人体会。这类项目的复杂度不在某个单点,而在把图像采集、颜色识别、运动控制、无线交互串成一条稳定链路的全过程。颜色阈值标定、PD参数整定、供电设计,每一环都会消耗大量调试时间,我踩过的这些坑对应的基本都不是复杂算法,而是工程细节。建议读者做的时候,先把工作环境固定下来——固定光源、固定背景、固定目标颜色,把链路跑通,再逐步放开环境变量。这套系统的扩展空间也很大,比如把颜色学习模式升级成“多颜色目标切换”,或者把HSL阈值判定换成更轻量的色彩直方图反向投影,都能在现有硬件上获得更好效果。希望大家照着这份笔记搭起来时,少走几趟我走过的弯路。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/3 15:33:26

VScode配置Pico SDK开发树莓派pico

前言 树莓派Pico被广泛用于各种DIY场景&#xff0c;多数情况下&#xff0c;通常使用MicroPython或者Arduino SDK开发&#xff0c;对于原生的Pico SDK方案&#xff0c;因为配置复杂&#xff0c;相关资料相对前两种方案较少&#xff0c;但在特定情况下&#xff0c;Pico SDK性能更…

作者头像 李华
网站建设 2026/9/3 0:53:17

企业级RAG知识库全链路:从文档解析到混合检索重排实战

前阵子一个做企业内部知识库的朋友跟我吐槽&#xff1a;他们用最新的大模型 API 做了一个 RAG 系统&#xff0c;上线一周就翻车了。用户问“去年第四季度的销售返利政策”&#xff0c;系统答非所问&#xff1b;问“X 项目验收需要准备哪些材料”&#xff0c;系统只召回了一份完…

作者头像 李华
网站建设 2026/9/1 7:58:46

华为解锁码获取全攻略:绕开官方通道的BL解锁实操

简介&#xff1a;需要解除华为BootLoader锁的玩家与开发者&#xff0c;可借助这份资料获取绕过官方通道申请解锁码的完整方案与自动化工具。适用机型覆盖华为Mate10/9/8/7、P10/9/8/7/6、荣耀V10/9/8、Nova系列以及畅玩、畅享、麦芒等数十款&#xff0c;除P20/P20Pro/荣耀10外&…

作者头像 李华
网站建设 2026/9/3 14:52:12

用UC3846设计36W反激式开关电源

简介&#xff1a;本资源是一套面向电源设计初学者与电子工程师的完整反激式AC-DC隔离电源开发资料&#xff0c;聚焦小功率12V/3A适配器级应用&#xff0c;解决基于UC3846芯片实现稳定、高效反激拓扑设计的核心工程问题。压缩包共30个文件&#xff0c;含原理图&#xff08;.SchD…

作者头像 李华
网站建设 2026/9/3 6:41:02

YOLOv8涨点改进 | 全网独家复现 多尺度分层锚框强化细微裂缝感知、助力路面细缝检测、密集裂缝去重、病害分类计数精准涨点

目录 一、研究背景与行业技术痛点 二、YOLOv8网络架构与路面裂缝计数适配优势 2.1 YOLOv8整体网络架构深度拆解 2.2 YOLOv8对比传统分割计数方案核心优势 2.3 原生YOLOv8路面裂缝计数核心短板 三、CRACK500数据集标准化处理与适配优化 3.1 数据集基础信息与场景覆盖 3.…

作者头像 李华
网站建设 2026/9/3 2:15:41

华为AI岗面试复盘:OD机试、大模型与昇腾技术栈全攻略

1. 先把岗位盘清楚&#xff1a;华为AI岗到底在招什么人4月15号那场面试出来&#xff0c;我在园区门口站了十分钟&#xff0c;脑子里翻来覆去就一句话&#xff1a;华为的AI岗&#xff0c;真的不是在招一个“会调库的人”。很多人一看到“AI岗”三个字&#xff0c;第一反应就是“…

作者头像 李华