亚博microros小车-原生ubuntu支持系列:8-脸部检测与人脸特效
前面的都是使用了mediapipe框架。后面的这两节采用了opencv\dlib的框架。
一 脸部检测
核心:opencv
detectMultiScale函数
detectMultiScale(image, scaleFactor, minNeighbors, flags, minSize, maxSize)
image--待检测图片,一般为灰度图像加快检测速度;
scaleFactor参数控制每个图像序列的缩放比例。该参数决定了在每个图像序列中检测窗口的大小。默认值为1.1,表示每次图像被缩小10%。较小的值可以捕捉更多的细节,但也会增加计算量。较大的值可以加快检测速度,但可能会错过一些目标。
minNeighbors参数定义了每个目标至少应该有多少个邻居,才能被认为是一个目标。该参数用于过滤检测到的目标。
flags参数用于定义检测模式。它可以是以下几个值的组合:
- CASCADE_SCALE_IMAGE:使用缩放图像进行检测(默认值)。
- CASCADE_FIND_BIGGEST_OBJECT:只检测最大的目标。
- CASCADE_DO_ROUGH_SEARCH:快速搜索模式。
minSize、maxSize参数用于指定检测目标的最小、最大尺寸。
src/yahboom_esp32_mediapipe/yahboom_esp32_mediapipe/目录下新建05_FaceEyeDetection.py,代码如下:
#!/usr/bin/env python2
# encoding: utf-8
#import ros lib
import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Point
import mediapipe as mp
from cv_bridge import CvBridge
from sensor_msgs.msg import CompressedImage,Image
#import define msg
from yahboomcar_msgs.msg import PointArray
#import commom lib
import cv2 as cv
import numpy as np
import time
from cv_bridge import CvBridge
from sensor_msgs.msg import Image, CompressedImagefrom rclpy.time import Time
import datetimeprint("import done")class FaceEyeDetection(Node):def __init__(self,name):super().__init__(name)self.bridge = CvBridge()#加载分类器self.eyeDetect = cv.CascadeClassifier( "/home/bohu/yahboomcar/yahboomcar_ws/src/yahboom_esp32_mediapipe/resource/haarcascade_eye.xml")self.faceDetect = cv.CascadeClassifier("/home/bohu/yahboomcar/yahboomcar_ws/src/yahboom_esp32_mediapipe/resource/haarcascade_frontalface_default.xml")self.pub_rgb = self.create_publisher(Image,"/FaceEyeDetection/image", 500)def cancel(self):self.pub_rgb.unregister()def face(self, frame):gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)#灰度faces = self.faceDetect.detectMultiScale(gray, 1.3)#检测for face in faces: frame = self.faceDraw(frame, face)return framedef eye(self, frame):gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)#灰度eyes = self.eyeDetect.detectMultiScale(gray, 1.3)#检测for eye in eyes:cv.circle(frame, (int(eye[0] + eye[2] / 2), int(eye[1] + eye[3] / 2)), (int(eye[3] / 2)), (0, 0, 255), 2)return frame#画框显示def faceDraw(self, frame, bbox, l=30, t=10):x, y, w, h = bboxx1, y1 = x + w, y + hcv.rectangle(frame, (x, y), (x + w, y + h), (255, 0, 255), 2)# Top left x,ycv.line(frame, (x, y), (x + l, y), (255, 0, 255), t)cv.line(frame, (x, y), (x, y + l), (255, 0, 255), t)# Top right x1,ycv.line(frame, (x1, y), (x1 - l, y), (255, 0, 255), t)cv.line(frame, (x1, y), (x1, y + l), (255, 0, 255), t)# Bottom left x1,y1cv.line(frame, (x, y1), (x + l, y1), (255, 0, 255), t)cv.line(frame, (x, y1), (x, y1 - l), (255, 0, 255), t)# Bottom right x1,y1cv.line(frame, (x1, y1), (x1 - l, y1), (255, 0, 255), t)cv.line(frame, (x1, y1), (x1, y1 - l), (255, 0, 255), t)return framedef pub_img(self, frame):self.pub_rgb.publish(self.bridge.cv2_to_imgmsg(frame, "bgr8"))class MY_Picture(Node):def __init__(self, name):super().__init__(name)self.bridge = CvBridge()self.sub_img = self.create_subscription(CompressedImage, '/espRos/esp32camera', self.handleTopic, 1) #获取esp32传来的图像self.last_stamp = Noneself.new_seconds = 0self.fps_seconds = 1self.face_eye_detection = FaceEyeDetection('face_eye_detection')self.content = ["face", "eye", "face_eye"]self.content_index = 0#回调函数def handleTopic(self, msg):self.last_stamp = msg.header.stamp if self.last_stamp:total_secs = Time(nanoseconds=self.last_stamp.nanosec, seconds=self.last_stamp.sec).nanosecondsdelta = datetime.timedelta(seconds=total_secs * 1e-9)seconds = delta.total_seconds()*100if self.new_seconds != 0:self.fps_seconds = seconds - self.new_secondsself.new_seconds = seconds#保留这次的值start = time.time()frame = self.bridge.compressed_imgmsg_to_cv2(msg)frame = cv.resize(frame, (640, 480))cv.waitKey(10)action = cv.waitKey(1) & 0xFFif action == ord("f") or action == ord("F"):self.content_index += 1if self.content_index >= len(self.content): self.content_index = 0if self.content[self.content_index] == "face": frame = self.face_eye_detection.face(frame)elif self.content[self.content_index] == "eye": frame = self.face_eye_detection.eye(frame)else: frame = self.face_eye_detection.eye(self.face_eye_detection.face(frame))end = time.time()fps = 1 / ((end - start)+self.fps_seconds)text = "FPS : " + str(int(fps))cv.putText(frame, text, (20, 30), cv.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 1)cv.imshow('frame', frame)self.face_eye_detection.pub_img(frame)# print(frame)cv.waitKey(10)def main():print("start it")rclpy.init()esp_img = MY_Picture("My_Picture")try:rclpy.spin(esp_img)except KeyboardInterrupt:passfinally:esp_img.destroy_node()rclpy.shutdown()
主要流程跟之前类似,这个不如mediapipe框架好,尤其是画框比较麻烦。
这个识别效果对比之前的face_recognition,我觉得不如那个好,参见:
ros2-4.2 用python实现人脸识别_ros2使用人脸检测-CSDN博客
构建后运行:
ros2 run yahboom_esp32_mediapipe FaceEyeDetection
效果如下
二 人脸特效
人脸检测
使用了dlib库
.get_frontal_face_detector()
功能:人脸检测画框
参数:无
返回值:默认的人脸检测器
shape_predictor()
功能:标记人脸关键点
参数:shape_predictor_68_face_landmarks.dat:68个关键点模型地址
返回值:人脸关键点预测器
import cv2
import mediapipe as mp
import dlibdetector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor("/home/bohu/yahboomcar/yahboomcar_ws/src/yahboom_esp32_mediapipe/resource/shape_predictor_68_face_landmarks.dat")cap = cv2.VideoCapture(0)#打开默认摄像头
while True:ret,frame = cap.read()#读取一帧图像#图像格式转换frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)# 因为摄像头是镜像的,所以将摄像头水平翻转# 不是镜像的可以不翻转frame= cv2.flip(frame,1)#输出结果gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)faces = detector(gray)print(f'faces:{len(faces)}')for face in faces:# 利用预测器预测shape = predictor(gray, face)# print(shape)# 标出68个点的位置for i in range(68):cv2.circle(frame, (shape.part(i).x, shape.part(i).y), 2, (0, 255, 0), -1, 1)cv2.putText(frame, str(i), (shape.part(i).x, shape.part(i).y), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255))cv2.imshow('opencv detectMultiScale', frame)if cv2.waitKey(1) & 0xFF == 27:break
cap.release()

人脸特效
基本思路就是在dlib人脸检测点上,在额外使用opencv画线\和fillConvexPoly填充多边形函数。
这种就是比较麻烦,没有官方的函数之间调用好。
src/yahboom_esp32_mediapipe/yahboom_esp32_mediapipe/目录下新建文件06_FaceLandmarks.py,代码如下:
#!/usr/bin/env python3
# encoding: utf-8
import rclpy
from rclpy.node import Node
import time
import dlib
import cv2 as cv
import numpy as np
from cv_bridge import CvBridge
from sensor_msgs.msg import Image, CompressedImagefrom rclpy.time import Time
import datetimeclass FaceLandmarks:def __init__(self, dat_file):self.hog_face_detector = dlib.get_frontal_face_detector()self.dlib_facelandmark = dlib.shape_predictor(dat_file)def get_face(self, frame, draw=True):gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)self.faces = self.hog_face_detector(gray)for face in self.faces:self.face_landmarks = self.dlib_facelandmark(gray, face)if draw:for n in range(68):x = self.face_landmarks.part(n).xy = self.face_landmarks.part(n).ycv.circle(frame, (x, y), 2, (0, 255, 255), 2)cv.putText(frame, str(n), (x, y), cv.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 255), 2)return framedef get_lmList(self, frame, p1, p2, draw=True):lmList = []if len(self.faces) != 0:for n in range(p1, p2):x = self.face_landmarks.part(n).xy = self.face_landmarks.part(n).ylmList.append([x, y])if draw:next_point = n + 1if n == p2 - 1: next_point = p1x2 = self.face_landmarks.part(next_point).xy2 = self.face_landmarks.part(next_point).ycv.line(frame, (x, y), (x2, y2), (0, 255, 0), 1)return lmListdef get_lipList(self, frame, lipIndexlist, draw=True):lmList = []if len(self.faces) != 0:for n in range(len(lipIndexlist)):x = self.face_landmarks.part(lipIndexlist[n]).xy = self.face_landmarks.part(lipIndexlist[n]).ylmList.append([x, y])if draw:next_point = n + 1if n == len(lipIndexlist) - 1: next_point = 0x2 = self.face_landmarks.part(lipIndexlist[next_point]).xy2 = self.face_landmarks.part(lipIndexlist[next_point]).ycv.line(frame, (x, y), (x2, y2), (0, 255, 0), 1)return lmListdef prettify_face(self, frame, eye=True, lips=True, eyebrow=True, draw=True):if eye:leftEye = landmarks.get_lmList(frame, 36, 42)rightEye = landmarks.get_lmList(frame, 42, 48)if draw:if len(leftEye) != 0: frame = cv.fillConvexPoly(frame, np.mat(leftEye), (0, 0, 0))if len(rightEye) != 0: frame = cv.fillConvexPoly(frame, np.mat(rightEye), (0, 0, 0))if lips:lipIndexlistA = [51, 52, 53, 54, 64, 63, 62]lipIndexlistB = [48, 49, 50, 51, 62, 61, 60]lipsUpA = landmarks.get_lipList(frame, lipIndexlistA, draw=True)lipsUpB = landmarks.get_lipList(frame, lipIndexlistB, draw=True)lipIndexlistA = [57, 58, 59, 48, 67, 66]lipIndexlistB = [54, 55, 56, 57, 66, 65, 64]lipsDownA = landmarks.get_lipList(frame, lipIndexlistA, draw=True)lipsDownB = landmarks.get_lipList(frame, lipIndexlistB, draw=True)if draw:if len(lipsUpA) != 0: frame = cv.fillConvexPoly(frame, np.mat(lipsUpA), (249, 0, 226))if len(lipsUpB) != 0: frame = cv.fillConvexPoly(frame, np.mat(lipsUpB), (249, 0, 226))if len(lipsDownA) != 0: frame = cv.fillConvexPoly(frame, np.mat(lipsDownA), (249, 0, 226))if len(lipsDownB) != 0: frame = cv.fillConvexPoly(frame, np.mat(lipsDownB), (249, 0, 226))if eyebrow:lefteyebrow = landmarks.get_lmList(frame, 17, 22)righteyebrow = landmarks.get_lmList(frame, 22, 27)if draw:if len(lefteyebrow) != 0: frame = cv.fillConvexPoly(frame, np.mat(lefteyebrow), (255, 255, 255))if len(righteyebrow) != 0: frame = cv.fillConvexPoly(frame, np.mat(righteyebrow), (255, 255, 255))return frameclass MY_Picture(Node):def __init__(self, name,landmarkss):super().__init__(name)self.bridge = CvBridge()self.sub_img = self.create_subscription(CompressedImage, '/espRos/esp32camera', self.handleTopic, 1) #获取esp32传来的图像self.landmarksros = landmarkssself.last_stamp = Noneself.new_seconds = 0self.fps_seconds = 1def handleTopic(self, msg):self.last_stamp = msg.header.stamp if self.last_stamp:total_secs = Time(nanoseconds=self.last_stamp.nanosec, seconds=self.last_stamp.sec).nanosecondsdelta = datetime.timedelta(seconds=total_secs * 1e-9)seconds = delta.total_seconds()*100if self.new_seconds != 0:self.fps_seconds = seconds - self.new_secondsself.new_seconds = seconds#保留这次的值start = time.time()frame = self.bridge.compressed_imgmsg_to_cv2(msg)frame = cv.resize(frame, (640, 480))cv.waitKey(10)frame = self.landmarksros.get_face(frame, draw=False)frame = self.landmarksros.prettify_face(frame, eye=True, lips=True, eyebrow=True, draw=True)end = time.time()fps = 1 / ((end - start)+self.fps_seconds)text = "FPS : " + str(int(fps))cv.putText(frame, text, (20, 30), cv.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 1)cv.imshow('frame', frame)landmarks = None
def main():global landmarksprint("start it")#使用官方训练好的dlib 68点模型dat_file = "/home/bohu/yahboomcar/yahboomcar_ws/src/yahboom_esp32_mediapipe/resource/shape_predictor_68_face_landmarks.dat"landmarks = FaceLandmarks(dat_file)rclpy.init()esp_img = MY_Picture("My_Picture",landmarks)try:rclpy.spin(esp_img)except KeyboardInterrupt:passfinally:esp_img.destroy_node()rclpy.shutdown()
构建后运行:
ros2 run yahboom_esp32_mediapipe FaceLandmarks
效果如下:

相关文章:
亚博microros小车-原生ubuntu支持系列:8-脸部检测与人脸特效
前面的都是使用了mediapipe框架。后面的这两节采用了opencv\dlib的框架。 一 脸部检测 核心:opencv detectMultiScale函数 detectMultiScale(image, scaleFactor, minNeighbors, flags, minSize, maxSize) image--待检测图片,一般为灰度图像加快检测…...
代码随想录算法训练营day32
代码随想录算法训练营 —day32 文章目录 代码随想录算法训练营前言一、动态规划理论基础二、509. 斐波那契数动态规划动态规划优化空间版递归法 三、70. 爬楼梯动态规划动态规划空间优化 746. 使用最小花费爬楼梯动态规划空间优化 总结 前言 今天是算法营的第32天,…...
缓存之美:万文详解 Caffeine 实现原理(下)
上篇文章:缓存之美:万文详解 Caffeine 实现原理(上) getIfPresent 现在我们对 put 方法有了基本了解,现在我们继续深入 getIfPresent 方法: public class TestReadSourceCode {Testpublic void doRead() …...
中企出海:从国际投资建厂:投前投中投后重点事项
1. 投前重点事项 1.1 市场调研与分析 在国际投资建厂的投前阶段,市场调研与分析是至关重要的基础工作,它能够帮助企业全面了解目标市场,为后续决策提供有力依据。 市场规模与潜力:通过收集和分析目标国家或地区的经济数据、行业…...
github登录用的TOTP和恢复码都丢失了怎么办
从22年左右开始github的登录就需要用TOTP的一个6位秘钥做二次认证登录,如果在用的TOTP软件失效了,可以用github开启二次认证时下载的恢复码重置认证,但是如果你和我一样这两个东西都没了就只能用邮箱重置了,过程给大家分享一下 一…...
最长递增子序列问题(Longest Increasing Subsequence),动态规划法解决,贪心算法 + 二分查找优化
问题描述:在一个大小乱序的数列中,找到一个最大长度的递增子序列,子序列中的数据在原始数列中的相对位置保持不变,可以不连续,但必须递增。 输入描述: 第一行输入数列的长度 n。(1 < n < 200) 第二…...
Python中采用.add_subplot绘制子图的方法简要举例介绍
Python中采用.add_subplot绘制子图的方法简要举例介绍 目录 Python中采用.add_subplot绘制子图的方法简要举例介绍一、Python中绘制子图的方法1.1 add_subplot函数1.2 基本语法(1)add_subplot的核心语法(2)add_subplot在中编程中的…...
纯 Python、Django、FastAPI、Flask、Pyramid、Jupyter、dbt 解析和差异分析
一、纯 Python 1.1 基础概念 Python 是一种高级、通用、解释型的编程语言,以其简洁易读的语法和丰富的标准库而闻名。“纯 Python” 在这里指的是不依赖特定的 Web 框架或数据分析工具,仅使用 Python 原生的功能和标准库来开发应用程序或执行任务。 1.…...
C++实现有限元二维杆单元计算 Bar2D2Node类(纯自研 非套壳)
本系列文章致力于实现“手搓有限元,干翻Ansys的目标”,基本框架为前端显示使用QT实现交互,后端计算采用Visual Studio C。 QT软件界面 具体软件操作可查看下方视频哦。也可以点击这里直接跳转。 直接干翻Ansys?小伙自研有限元 1、…...
wx036基于springboot+vue+uniapp的校园快递平台小程序
开发语言:Java框架:springbootuniappJDK版本:JDK1.8服务器:tomcat7数据库:mysql 5.7(一定要5.7版本)数据库工具:Navicat11开发软件:eclipse/myeclipse/ideaMaven包&#…...
Unity中两个UGUI物体的锚点和中心点设置成不一样的,然后怎么使两个物体的位置一样?
一、问题复现 需求:go1物体和我想把go1的位置跟go2的位置一样,但是我通过物体的anchoredPosition以及position还有localposiiton都没有解决问题,使用上面的这三个属性的效果如下: 运行之后,可以看出,go1的…...
兼职全职招聘系统架构与功能分析
2015工作至今,10年资深全栈工程师,CTO,擅长带团队、攻克各种技术难题、研发各类软件产品,我的代码态度:代码虐我千百遍,我待代码如初恋,我的工作态度:极致,责任ÿ…...
HTML5 History API
在 HTML5 的 History API 中,pushState 和 replaceState 方法也可以接受一个 state 对象作为参数。这些方法允许你在改变浏览器路由时不重新加载页面,并且可以附加一些自定义数据。 state 返回在 history 栈顶的 任意 值的拷贝。 let currentState h…...
2025_1_22打卡
402. 移掉 K 位数字 - 力扣(LeetCode) 279. 完全平方数 - 力扣(LeetCode)...
Formality:不可读(unread)的概念
相关阅读 Formalityhttps://blog.csdn.net/weixin_45791458/category_12841971.html?spm1001.2014.3001.5482https://blog.csdn.net/weixin_45791458/category_12841971.html?spm1001.2014.3001.5482 在Formality中有时会遇到不可读(unread)这个概念,本文就将对此…...
stm32f103C8T6和AT24C256链接
模拟IIC总线 myiic.c #ifndef __24CXX_H #define __24CXX_H #include "myiic.h" #define AT24C01 127 //1kbit1*1024/8128byte地址寻址范围为0-127 #define AT24C02 255 #define AT24C04 511 #define AT24C08 1023 #define AT24C16 2047 #define AT24C32 …...
5.SQLAlchemy对两张有关联关系表查询
问题 例如,一个用户可以有多个收获地址。 定义表如下: 用户表 地址表 一般情况,我们会先查询用户表,拿到用户id后,再到地址表中查询关联的地址数据。这样就要执行两次查询。 仅仅为了方便查询,需要一些属…...
2.2.1 语句结构
ST(Structured Text)语言是一种基于IEC 61131-3标准的高级文本编程语言,其语法规则严格且清晰。以下是ST语言中关于分号、注释和代码块的详细语法规则说明: 分号(;)作用:分号用于表示语句的结束。语法规则: 每个独立的语句必须以分号结尾。分号是语句的终止符,用于分隔…...
安当二代TDE透明加密技术与SMS凭据管理系统相结合的数据库安全解决方案
安当二代TDE透明加密技术与安当SMS凭据管理系统的结合,为企业提供了一套完整的数据库安全解决方案,涵盖字段级加密脱敏和动态凭据管理两大核心功能。以下是其实现方式和技术特点的详细说明: 一、安当二代TDE透明加密技术:字段级加…...
es的date类型字段按照原生格式进行分组聚合
PUT student2 { "mappings": {"properties": {"name": {"type": "text","analyzer": "standard" // 使用标准分析器,适合姓名字段},"birthday": {"type": "date&…...
OpenClaw硬件适配:Qwen3-32B镜像在不同显卡的性能对比
OpenClaw硬件适配:Qwen3-32B镜像在不同显卡的性能对比 1. 测试背景与动机 最近在本地部署OpenClaw时遇到一个实际问题:当对接Qwen3-32B这类大模型时,不同显卡的表现差异巨大。我的开发机配置是RTX3060 12GB,而同事的机器是RTX40…...
幻境·流金惊艳生成:从织梦令到流金光影汇聚的全过程效果对比
幻境流金惊艳生成:从织梦令到流金光影汇聚的全过程效果对比 1. 开篇:当技术遇见艺术的美妙邂逅 想象一下,你脑海中浮现出一个绝美的画面:赛博朋克都市中霓虹流淌的街道,或是水墨意境中的玄金山水。传统方式需要数小时…...
bun执行nodejs
使用 Bun 执行 Node.js 代码非常简单,因为 Bun 设计为与 Node.js 高度兼容。以下是完整的使用指南:1. 安装 Bunbash复制# macOS / Linux curl -fsSL https://bun.sh/install | bash# Windows (通过 WSL) curl -fsSL https://bun.sh/install | bash# 或通…...
光刻胶选型避坑指南:从正胶负胶到配套试剂的全流程解析
光刻胶选型避坑指南:从正胶负胶到配套试剂的全流程解析 在半导体制造领域,光刻工艺的质量直接决定了芯片的性能和良率。而光刻胶作为光刻工艺的核心材料,其选型往往成为工艺工程师最头疼的问题之一。我曾亲眼见过一个团队因为选错光刻胶类型&…...
【多模态表示与语言模型】3.1 自引用嵌入字符串(SELFIES)
3.1 自引用嵌入字符串(SELFIES)在分子生成式 AI 领域,表示学习(Representation Learning)的瓶颈长期以来集中在语法脆弱性问题上。传统 SMILES(Simplified Molecular-Input Line-Entry System)表…...
I2C总线原理与嵌入式系统应用实践
1. I2C总线基础解析I2C(Inter-Integrated Circuit)总线是Philips半导体(现NXP)在1982年推出的双线制串行通信协议。作为一名电子工程师,我在多个嵌入式项目中都深度使用过这种总线。它的精妙之处在于仅用两根线&#x…...
你能不能站稳脚跟,从来不是你有多厉害,而是老板刚好需要什么
你能不能站稳脚跟,从来不是你有多厉害,而是老板刚好需要什么 目录 你能不能站稳脚跟,从来不是你有多厉害,而是老板刚好需要什么 为什么老板的需求,才是你职场的核心标尺? 打工人的破局法则:别再埋头干活,学会按需创造价值 第一步:先搞清楚3个核心问题,精准锚定需求 …...
02_Neo4j知识体系之Cypher核心语法与CRUD实战
02_Neo4j知识体系之Cypher查询语言深度解析 体系 查询语言层:Cypher核心语法、CRUD操作、高级查询、路径模式、聚合分析、条件过滤、Quantified Path Patterns(QPP)关联能力:与属性图模型、索引设计、执行计划分析、图应用建模和…...
《Foundation 网格 - 大型设备》
《Foundation 网格 - 大型设备》 引言 在当今科技日新月异的时代,大型设备在各个领域都扮演着至关重要的角色。其中,Foundation 网格作为一项创新技术,正在逐渐改变着我们的生产方式和生活质量。本文将深入探讨Foundation 网格的特点、应用以及未来发展趋势。 一、Founda…...
Anthropic 收购 Oven 后,Claude Code 用运行时写了一篇护城河文章
2025 年,Anthropic 收购了 Oven——Bun 的母公司。 当时大家的解读是:「Anthropic 想拥有自己的 JavaScript 运行时。」说得通,但没有什么特别的。AI 公司投资基础设施,这在行业里是常态。 然后 Claude Code 的源码流出了。 人…...

