iOS图表开发:用贝塞尔曲线从零绘制折线图、柱状图与饼图

简介:本资源面向iOS开发初学者与需要自定义图表效果的中级开发者,聚焦Swift中UIBezierPath绘制贝塞尔曲线的核心技巧。通过一个完整的Xcode工程示例,演示如何重写draw(_:)方法、构建三次与二次贝塞尔曲线路径,并将折线数据平滑为美观的数据线,解决系统图表库难以满足个性化视觉需求的问题。压缩包共25个文件,约156KB,包含6个Swift源码文件、3个Objective-C头文件、3个plist配置、2个storyboard与1个xib界面文件,以及工程配置、测试目录和README说明,结构完整可直接运行。目前已有700余人学习下载。读者可从中掌握move(to:)、addCurve与addQuadCurve等关键API的实际用法,理解控制点对曲线形态的影响,并参考示例代码将理论知识落地为可复用的自定义图表视图,适合作为贝塞尔曲线入门的练手项目。

1. 用贝塞尔曲线画图表,为什么比直接堆 View 更值得学

如果你写过 iOS 图表,大概率经历过这种纠结:用第三方库吧,定制化一改就崩;自己用UIStackView堆柱子吧,圆角、渐变、动画全得靠嵌套,性能还拉胯。这个项目走的是另一条路——直接用UIBezierPath和CAShapeLayer从零画折线图、柱状图和饼图。它适合两类人:一是想搞懂图表底层渲染原理的 Swift 开发者,二是接私活时需要轻量级、无依赖图表方案的人。整个项目不依赖任何第三方库,纯 UIKit + Core Graphics,代码量可控,改起来心里有底。下面我从目录结构开始,把这套东西拆开讲清楚。

2. 项目结构与渲染管线:从数据到屏幕像素的完整链路

拿到源码先别急着跑,花五分钟把目录翻一遍,后面改代码能省一半时间。这个项目的组织方式很典型,按职责分层,没有花哨的架构,但每一层都卡在关键位置上。

2.1 目录分层与文件职责

常见的目录结构是这样的:

BezierChart/ ├── App/ │ ├── AppDelegate.swift │ └── SceneDelegate.swift ├── Chart/ │ ├── Base/ │ │ ├── ChartView.swift // 图表基类,持有数据源和样式配置 │ │ └── ChartConfig.swift // 颜色、线宽、动画时长等参数 │ ├── Line/ │ │ ├── LineChartView.swift // 折线图主视图 │ │ └── LineChartRenderer.swift // 贝塞尔路径构建与绘制 │ ├── Bar/ │ │ ├── BarChartView.swift │ │ └── BarChartRenderer.swift │ └── Pie/ │ ├── PieChartView.swift │ └── PieChartRenderer.swift ├── Model/ │ └── ChartDataPoint.swift // 数据模型:value、label、color ├── Extension/ │ ├── CGFloat+Extension.swift // 角度转弧度、数值映射 │ └── UIColor+Extension.swift // 渐变色生成 └── Resource/ └── Assets.xcassets

ChartView是基类,定义了dataPoints、config和reloadData()三个核心接口。子类只负责实现drawChart(in rect: CGRect)方法,把具体的路径构建逻辑交给对应的 Renderer。这种拆分的好处是:样式配置和数据源在 View 层,路径计算在 Renderer 层,两边互不干扰。你想换一套配色,只动ChartConfig;想改折线平滑算法,只动LineChartRenderer。

2.2 渲染管线:CAShapeLayer 才是真正干活的

很多人以为图表是draw(_ rect:)画出来的,其实这个项目用的是CAShapeLayer方案。draw(_:)在主线程同步执行,数据量大时直接卡 UI;CAShapeLayer把路径提交给 GPU 合成,还能挂动画。核心链路是这样的:

// ChartView.swift 基类中的关键初始化 override init(frame: CGRect) { super.init(frame: frame) // 关闭隐式动画,避免布局时闪烁 layer.masksToBounds = true // 每个图表持有一个 shapeLayer 用于路径绘制 shapeLayer.fillColor = UIColor.clear.cgColor shapeLayer.lineCap = .round shapeLayer.lineJoin = .round layer.addSublayer(shapeLayer) } // 子类重写此方法,由 Renderer 返回构建好的路径 func drawChart(in rect: CGRect) { fatalError("子类必须实现") } override func layoutSubviews() { super.layoutSubviews() shapeLayer.frame = bounds // 布局变化时重新计算路径 drawChart(in: bounds) }

这里有几个参数值得注意:lineCap设为.round让折线端点圆润,lineJoin设为.round避免尖角。masksToBounds开启后,超出边界的路径会被裁掉,防止图表溢出。layoutSubviews里重新调用drawChart是必须的,否则旋转屏幕后路径不会更新。

2.3 数据映射:把业务数值翻译成坐标点

图表的核心就一件事:把[100, 200, 150]这样的数值,映射到屏幕上的CGPoint。这个项目在CGFloat+Extension.swift里封装了映射函数:

extension CGFloat { /// 将数值从数据区间映射到绘图区间 /// - Parameters: /// - dataMin: 数据最小值 /// - dataMax: 数据最大值 /// - drawMin: 绘图区最小值(通常是 0) /// - drawMax: 绘图区最大值(通常是视图高度) func map(from dataMin: CGFloat, _ dataMax: CGFloat, to drawMin: CGFloat, _ drawMax: CGFloat) -> CGFloat { // 防止除以零导致 NaN guard dataMax - dataMin != 0 else { return drawMin } let ratio = (self - dataMin) / (dataMax - dataMin) return drawMin + ratio * (drawMax - drawMin) } }

调用时,Y 轴需要翻转,因为 UIKit 坐标系原点在左上角:

let y = value.map(from: minValue, maxValue, to: chartHeight, 0)

minValue和maxValue通常取数据集的极值,再留 10% 的 padding,避免折线贴顶或贴底。这个 padding 系数在ChartConfig里叫yAxisPaddingRatio,默认 0.1,改大改小直接影响图表的视觉呼吸感。

3. 折线图核心实现:贝塞尔路径构建与动画落地

折线图是三个图表里最复杂的,因为它涉及路径平滑、渐变填充和入场动画。把这部分吃透,柱状图和饼图基本就是降维打击。

3.1 构建折线路径:从 move 到 addLine

LineChartRenderer的核心方法是buildLinePath(with points: [CGPoint]):

func buildLinePath(with points: [CGPoint]) -> UIBezierPath { let path = UIBezierPath() guard points.count > 1 else { return path } // 起点:移动到第一个数据点 path.move(to: points[0]) // 依次连接后续点 for i in 1..<points.count { path.addLine(to: points[i]) } return path }

这段代码本身不复杂,但有两个细节容易翻车。第一,points.count必须大于 1,否则move(to:)之后没有线段,路径为空。第二,如果数据点少于 2 个,应该直接返回空路径而不是崩溃。我见过有人在points[0]处直接取下标,数据源为空时闪退,血泪经验。

3.2 平滑曲线:用三次贝塞尔替代直线

直线连接看起来太硬,很多图表需要平滑曲线。项目里提供了smoothFactor参数,开启后用三次贝塞尔曲线连接:

func buildSmoothPath(with points: [CGPoint], smoothness: CGFloat = 0.3) -> UIBezierPath { let path = UIBezierPath() guard points.count > 1 else { return path } path.move(to: points[0]) for i in 1..<points.count { let prev = points[i - 1] let curr = points[i] // 计算控制点偏移量 let dx = (curr.x - prev.x) * smoothness let dy = (curr.y - prev.y) * smoothness // 两个控制点分别位于前后点的水平偏移位置 let cp1 = CGPoint(x: prev.x + dx, y: prev.y + dy) let cp2 = CGPoint(x: curr.x - dx, y: curr.y - dy) path.addCurve(to: curr, controlPoint1: cp1, controlPoint2: cp2) } return path }

smoothness控制曲线弯曲程度,0 就是直线,0.3 左右比较自然,超过 0.5 会出现明显的过冲。控制点的计算逻辑是:第一个控制点从前一个点沿切线方向偏移,第二个控制点从当前点反向偏移。这样能保证曲线在数据点处平滑过渡,不会出现尖角。

3.3 渐变填充:CAGradientLayer 与路径的配合

折线下方通常需要渐变填充,做法是把折线路径闭合到 X 轴,然后交给CAGradientLayer做遮罩:

func applyGradientFill(to path: UIBezierPath, in rect: CGRect) { // 复制路径并闭合到底部 let fillPath = path.copy() as! UIBezierPath fillPath.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY)) fillPath.addLine(to: CGPoint(x: rect.minX, y: rect.maxY)) fillPath.close() // 创建渐变层 let gradientLayer = CAGradientLayer() gradientLayer.frame = rect gradientLayer.colors = [ config.lineColor.withAlphaComponent(0.4).cgColor, config.lineColor.withAlphaComponent(0.0).cgColor ] gradientLayer.startPoint = CGPoint(x: 0.5, y: 0) gradientLayer.endPoint = CGPoint(x: 0.5, y: 1) // 用路径做遮罩 let maskLayer = CAShapeLayer() maskLayer.path = fillPath.cgPath gradientLayer.mask = maskLayer layer.insertSublayer(gradientLayer, at: 0) }

startPoint和endPoint用单位坐标,(0.5, 0) 到 (0.5, 1) 表示从上到下渐变。withAlphaComponent控制透明度,顶部 0.4、底部 0 是最常见的配置。注意insertSublayer(at: 0)把渐变层插到最底层,否则会盖住折线。

3.4 入场动画:strokeEnd 从 0 到 1

折线图的入场动画用strokeEnd实现,这是CAShapeLayer的专属属性:

func animateLineDrawing() { // 先设置路径,再执行动画 shapeLayer.path = linePath.cgPath let animation = CABasicAnimation(keyPath: "strokeEnd") animation.fromValue = 0 animation.toValue = 1 animation.duration = config.animationDuration animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) shapeLayer.add(animation, forKey: "lineDraw") }

animationDuration默认 0.8 秒,在ChartConfig里可调。timingFunction用.easeInEaseOut比线性更自然。有个坑:如果shapeLayer.path没提前设置,动画会从空路径开始,看起来像闪一下。所以顺序必须是先设 path,再加动画。

4. 柱状图与饼图:复用路径思路解决不同几何问题

折线图搞定后,柱状图和饼图的实现思路是相通的——都是构建UIBezierPath,交给CAShapeLayer渲染。区别在于几何计算。

4.1 柱状图:圆角矩形与分组布局

柱状图的核心是计算每根柱子的 frame,然后用圆角矩形路径绘制:

func buildBarPath(at index: Int, value: CGFloat, in rect: CGRect) -> UIBezierPath { let barCount = CGFloat(dataPoints.count) let spacing: CGFloat = config.barSpacing // 柱间距 let totalSpacing = spacing * (barCount + 1) let barWidth = (rect.width - totalSpacing) / barCount let x = spacing + (barWidth + spacing) * CGFloat(index) let barHeight = value.map(from: 0, maxValue, to: 0, rect.height) let barRect = CGRect(x: x, y: rect.height - barHeight, width: barWidth, height: barHeight) // 圆角只加在顶部两个角 let path = UIBezierPath(roundedRect: barRect, byRoundingCorners: [.topLeft, .topRight], cornerRadii: CGSize(width: config.barCornerRadius, height: config.barCornerRadius)) return path }

barSpacing默认 8pt,barCornerRadius默认 4pt。byRoundingCorners只让顶部圆角,底部保持直角,这是柱状图的标准视觉。如果数据量大,barWidth会变得很窄,需要设置最小宽度,否则柱子细成一条线。

4.2 饼图:扇形路径与角度计算

饼图用addArc构建扇形,关键是起始角和结束角的计算:

func buildPieSector(startAngle: CGFloat, endAngle: CGFloat, radius: CGFloat, center: CGPoint) -> UIBezierPath { let path = UIBezierPath() // 从圆心移动到起始角边缘 path.move(to: center) // 画弧线到结束角 path.addArc(withCenter: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true) // 闭合回圆心 path.close() return path }

角度用弧度制,CGFloat.pi * 2是整圆。每个扇形的角度 =(value / totalValue) * 2 * pi。注意clockwise参数在 UIKit 坐标系里是反的,传true实际是逆时针方向,这个玄学坑我踩过不止一次。解决办法是统一用true,然后通过调整startAngle和endAngle的顺序来控制方向。

4.3 点击选中:hitTest 与路径判断

饼图需要响应点击,判断点是否在扇形内。项目里用UIBezierPath.contains(_:)做命中检测:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { guard let point = touches.first?.location(in: self) else { return } for (index, path) in sectorPaths.enumerated() { if path.contains(point) { // 选中该扇形,放大 1.05 倍 highlightSector(at: index) delegate?.chartView(self, didSelectItemAt: index) break } } }

sectorPaths是渲染时缓存的路径数组,避免每次点击重新计算。highlightSector通过transform缩放实现放大效果,配合CABasicAnimation做过渡。注意path.contains对闭合路径有效,如果路径没close(),判断会不准。

5. 避坑与常见问题:那些文档不会告诉你的细节

这一章记录几个实际开发中高频翻车的点,每个都按「现象 → 原因 → 解决」来说。

5.1 图表不显示或只显示一部分

现象:reloadData()调了,数据也传了,但屏幕上什么都没有,或者只画了前几个点。

原因:shapeLayer.frame没有跟随bounds更新。CAShapeLayer的frame默认是 zero,路径坐标是相对于 layer 自身的,如果 frame 不对,路径就画到屏幕外了。

解决:在layoutSubviews里显式设置shapeLayer.frame = bounds,并且确保drawChart在 frame 设置之后调用。如果用了 Auto Layout,layoutSubviews可能被多次调用,加个if oldBounds != bounds的判断避免重复计算。

5.2 动画结束后路径消失

现象:入场动画播完,折线不见了。

原因:CABasicAnimation默认在动画结束后移除,shapeLayer的strokeEnd回到初始值 0。

解决:设置animation.isRemovedOnCompletion = false和animation.fillMode = .forwards,让动画停留在最终状态。或者更稳妥的做法是,动画结束后手动设置shapeLayer.strokeEnd = 1。

5.3 数据为空时崩溃

现象:接口返回空数组,App 直接闪退。

原因:points[0]或dataPoints.max()在空数组上取下标或求极值。

解决:所有路径构建方法开头加guard !points.isEmpty else { return },maxValue计算时用dataPoints.map { $0.value }.max() ?? 1,给个默认值兜底。

5.4 渐变填充颜色不对

现象:设置了渐变色,但显示出来是纯色或者颜色错位。

原因:CAGradientLayer的frame和mask路径坐标系不一致,或者colors数组里的 CGColor 没有正确转换。

解决:确保gradientLayer.frame和maskLayer.path基于同一个坐标系。UIColor.cgColor在暗黑模式下会动态变化,如果需要固定颜色,用UIColor(red:green:blue:alpha:)显式指定。

5.5 大量数据时滚动卡顿

现象:数据点超过 500 个,图表滑动明显掉帧。

原因:每次layoutSubviews都重新构建完整路径,CPU 计算量大。

解决:缓存路径,只在数据源变化时重建。可以用dataVersion标记,数据没变就直接复用shapeLayer.path。另外,折线图数据点超过 1000 时,考虑采样降频,每 N 个点取一个,视觉上几乎看不出差别。

6. 二次开发:把这套图表改造成你要的样子

拿到源码后,最常改的三个地方是:样式配置、数据源适配和新增图表类型。我一般会先跑通 Demo,然后按下面的顺序动手。

6.1 样式配置:集中管理而不是散落各处

ChartConfig是个 struct,所有可调参数都在里面:

struct ChartConfig { var lineColor: UIColor = .systemBlue var lineWidth: CGFloat = 2.0 var animationDuration: TimeInterval = 0.8 var yAxisPaddingRatio: CGFloat = 0.1 var barSpacing: CGFloat = 8.0 var barCornerRadius: CGFloat = 4.0 var showGrid: Bool = true var gridColor: UIColor = .systemGray5 }

改配色只动lineColor,改动画时长只动animationDuration。如果项目需要多套主题,可以扩展一个ChartTheme协议,让ChartConfig从主题初始化。这样切换暗黑模式或品牌色时,不用翻遍每个 Renderer。

6.2 数据源适配:从 JSON 到 ChartDataPoint

实际项目里数据通常来自网络,需要一层转换:

struct ChartDataPoint { let value: CGFloat let label: String? let color: UIColor? } // 从接口响应映射 func mapResponse(_ items: [[String: Any]]) -> [ChartDataPoint] { return items.compactMap { dict in guard let value = dict["value"] as? CGFloat else { return nil } return ChartDataPoint(value: value, label: dict["label"] as? String, color: nil) } }

compactMap自动过滤掉解析失败的项,避免脏数据导致崩溃。如果接口返回的是字符串数值,记得用CGFloat(string)转换并处理 nil。

6.3 新增图表类型:以雷达图为例

新增图表遵循同样的模式:继承ChartView,实现drawChart,把路径构建逻辑放到独立的 Renderer。以雷达图为例,核心是计算多边形顶点:

func buildRadarPath(values: [CGFloat], center: CGPoint, radius: CGFloat) -> UIBezierPath { let path = UIBezierPath() let angleStep = CGFloat.pi * 2 / CGFloat(values.count) for (index, value) in values.enumerated() { let angle = angleStep * CGFloat(index) - CGFloat.pi / 2 let r = radius * (value / maxValue) let point = CGPoint(x: center.x + cos(angle) * r, y: center.y + sin(angle) * r) index == 0 ? path.move(to: point) : path.addLine(to: point) } path.close() return path }

角度减pi/2是为了让第一个顶点朝上。maxValue取所有维度中的最大值,保证图形比例协调。雷达图的网格线是同心多边形,用同样的角度计算,只是半径按比例缩小。

6.4 验证方法:用预览和单元测试兜底

改完代码后,我习惯用 SwiftUI Preview 快速看效果:

struct LineChartView_Previews: PreviewProvider { static var previews: some View { LineChartView(dataPoints: [ ChartDataPoint(value: 100, label: "一月", color: nil), ChartDataPoint(value: 200, label: "二月", color: nil), ChartDataPoint(value: 150, label: "三月", color: nil) ]) .frame(height: 300) .previewLayout(.fixed(width: 375, height: 300)) } }

对于路径计算这种纯逻辑,写单元测试更靠谱:

func testMapFunction() { let result = CGFloat(50).map(from: 0, 100, to: 0, 200) XCTAssertEqual(result, 100, accuracy: 0.001) }

accuracy参数处理浮点误差,别用XCTAssertEqual直接比浮点数,否则偶尔会挂。

6.5 一个我踩过的坑

有次改折线图,把smoothness调到 0.8,想做个夸张的曲线效果。结果数据点稀疏时,曲线过冲严重,直接画到了图表外面。排查了半天才发现是控制点偏移量太大,导致贝塞尔曲线超出了数据点的凸包范围。从那以后我每次调平滑系数,都强制在0.1到0.4之间试三档,并且用极值数据(全相等、递增、递减)各跑一遍,确认没有溢出才提交。希望帮到你。

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

相关新闻

KylinOS国产系统实操指南:从安装到部署的踩坑经验

KylinOS国产系统实操指南:从安装到部署的踩坑经验

我在一线折腾 Linux 也有好多个年头了&#xff0c;平时用主流发行版比较多。前段时间因为工作调整&#xff0c;需要把一部分开发和运维场景迁移到 KylinOS 这套国产系统上&#xff0c;于是认认真真做了一段实操学习。从下载 ISO、装虚拟机开始&#xff0c;到后来在真实物理机上…

2026/10/20 3:15:58 阅读更多 →
Basler相机C++ SDK环境配置避坑指南:版本对齐与USB驱动调试

Basler相机C++ SDK环境配置避坑指南:版本对齐与USB驱动调试

简介&#xff1a;本资源是一套面向C工业视觉开发者的Basler相机SDK轻量级封装类库&#xff0c;专为快速集成Basler工业相机而设计&#xff0c;适用于机器视觉、自动化检测等需高稳定性图像采集的中高级开发场景。压缩包仅2KB&#xff0c;含2个核心文件&#xff1a;头文件Basler…

2026/10/20 3:15:58 阅读更多 →
Trimmomatic双端测序全攻略:从FASTQ质控到参数调优实战

Trimmomatic双端测序全攻略:从FASTQ质控到参数调优实战

做生信的朋友&#xff0c;对 Trimmomatic 这个名字应该都熟。它是二代测序数据预处理阶段最常被提到的工具之一&#xff0c;专门处理下机后最原始的 FASTQ 文件&#xff1a;去接头、剪低质量碱基、过滤掉太短的序列。如果说测序是原料采购&#xff0c;那 Trimmomatic 就是洗菜、…

2026/10/20 3:15:58 阅读更多 →

最新新闻

MSA存储入门指南:从SAN协议到选型场景的全面解析

MSA存储入门指南:从SAN协议到选型场景的全面解析

简介&#xff1a;这份文档面向备考H3C认证工程师-MSA存储&#xff08;H3CNE-MSA&#xff09;的技术人员与H3C代理服务工程师&#xff0c;聚焦MSA存储设备基础配置与维护知识&#xff0c;帮助读者在有限时间内系统梳理考点、检验掌握程度。资源为1个docx文件&#xff0c;压缩包约…

2026/10/20 19:39:14 阅读更多 →
C语言大作业高分实践:工程结构、内存安全与评分对齐

C语言大作业高分实践:工程结构、内存安全与评分对齐

简介&#xff1a;这是一份面向高校C语言初学者与课程设计学生的高分期末大作业实战项目&#xff0c;完整实现了一个具备学生端与管理员双角色的学生成绩管理系统。资源包含可直接编译运行的C/Qt源码&#xff08;含18个.h头文件与18个.cpp实现文件&#xff09;、图形界面.ui文件…

2026/10/20 19:39:14 阅读更多 →
Redis Java客户端选型与实战:Jedis、Lettuce、Redisson对比

Redis Java客户端选型与实战:Jedis、Lettuce、Redisson对比

1. 先搞清楚需求&#xff0c;再选客户端写Java应用连Redis&#xff0c;第一步不是去翻依赖、复制代码&#xff0c;而是先想清楚&#xff1a;你的业务到底需要Redis做什么&#xff0c;以及你的应用是什么形态。这话可能听起来像废话&#xff0c;但我在实际项目里见过太多人一上来…

2026/10/20 19:39:14 阅读更多 →
轻量级AI助手ZeroClaw实战:有限算力下的部署与Agent落地

轻量级AI助手ZeroClaw实战:有限算力下的部署与Agent落地

1. 项目定位&#xff1a;为什么轻量级AI助手反而更实用去年年底我拿到一台配置平平的旧工作站&#xff0c;8GB内存、无独显、CPU还是几年前的型号&#xff0c;跑几个容器就喘。当时手头正好有个工具自动化需求&#xff1a;定时抓取内部系统数据&#xff0c;做简单清洗&#xff…

2026/10/20 19:39:14 阅读更多 →
单相桥式半控整流电路原理与Matlab/Simulink仿真建模分析

单相桥式半控整流电路原理与Matlab/Simulink仿真建模分析

单相桥式半控整流电路&#xff0c;这个名字在电力电子课程里出现的频率极高。很多电气工程专业的朋友第一次接触晶闸管整流&#xff0c;就是从它开始的。最近正好有人在问Matlab仿真的思路&#xff0c;我把自己做这个仿真报告的全过程梳理了一遍&#xff0c;从拓扑结构分析到模…

2026/10/20 19:39:14 阅读更多 →
Java动漫链接生成器:批量整理番剧资源链接的轻量工具

Java动漫链接生成器:批量整理番剧资源链接的轻量工具

简介&#xff1a;这是一款面向动漫资源采集与二次元工具开发者的轻量级命令行程序&#xff0c;基于Java实现&#xff0c;可根据指定标签批量生成动漫图片链接&#xff0c;适合需要批量获取Danbooru图源、做数据采集或练手Java命令行项目的开发者。资源包共17个文件&#xff0c;…

2026/10/20 19:38:12 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:45 阅读更多 →