Qt+C++从零搭建仿真界面:模块库、画布连线与求解器实现

简介:这份资源是一个基于Qt框架开发的类Simulink仿真界面小程序,面向具备C++基础、希望学习GUI设计与控制系统仿真的开发者。项目在VS2010环境下构建,基于Qt 4.8.5,通过QGraphicsView、QGraphicsScene与QGraphicsItem搭建可视化建模界面,支持拖放模块、绘制闭环控制回路并实时更新仿真结果,同时结合C++数值计算模拟连续系统的离散化行为,是理解图形界面设计与数字仿真结合的实践案例。压缩包共122个文件,约21.78MB,包含26个cpp源文件、18个头文件、6个ui界面文件及qrc资源、ts翻译、ico图标等,另有sln、vcxproj等VS工程配置,便于直接编译运行与二次开发。目前已有3632人学习下载,适合想深入掌握Qt绘图框架、事件处理与仿真算法实现的读者参考借鉴。

1. 从零搭一个 Qt 仿真界面:为什么我不建议你直接套 Simulink 的壳

做毕设或课设时,很多人第一反应是找一个现成的仿真工具套壳,把界面改一改就交差。但真正动手后你会发现,Simulink 那套交互逻辑——拖拽模块、连线、参数配置、仿真运行、波形显示——每一层都是独立的工程问题,套壳根本解决不了。我带的几个模拟项目里,最稳妥的做法是用 Qt + C++ 从零搭一个轻量级仿真界面,把「模块库、画布、连线、求解器、波形显示」这五件事拆开做。这套方案适合两类人:一是需要交一份完整仿真类毕设的学生,二是想理解图形化建模工具底层架构的开发者。它不追求替代工业级工具,而是让你在可控代码量内跑通完整链路,答辩时每一行都能讲清楚。

2. 需求拆解与技术选型:为什么是 Qt + C++ 而不是 Python 或 Web

2.1 仿真界面的四个核心需求

先别急着写代码,把需求列清楚。一个能跑通的仿真界面,至少需要满足四件事。第一,模块库要可扩展,用户能从侧边栏拖拽模块到画布,模块类型包括信号源、传递函数、增益、示波器等。第二,画布要支持自由布局和连线,连线代表信号流向,不能只是画着玩,要能参与后续计算。第三,参数要能配置,双击模块弹出属性对话框,修改参数后模型状态同步更新。第四,仿真要能跑起来并出波形,求解器按固定步长或变步长推进,结果实时绘制。

这四件事对应到代码结构,就是模块管理、图元编辑、属性系统和求解器引擎四个模块。很多同学一上来就写画布,结果写到连线时发现模块数据结构没设计好,返工重来。我的习惯是先把模块的抽象基类定下来,再动界面。

2.2 Qt 与 C++ 的组合优势

选 Qt 而不是 Python 的 PyQt 或 Web 技术栈,主要看中三点。一是性能,仿真循环里每步都要遍历模块、计算输出、更新连线状态,C++ 的原生执行效率比解释型语言高一个量级,步长可以压到毫秒级。二是 Qt 的 Graphics View Framework 天生适合做图元编辑,QGraphicsScene 管理图元,QGraphicsView 负责渲染,QGraphicsItem 支持自定义绘制和碰撞检测,连线、拖拽、缩放这些交互不用从零造轮子。三是信号槽机制,模块参数变化后自动通知求解器重置,比手动维护回调列表干净得多。

Python 方案不是不能做,但当你需要把仿真步长做到 1ms 以下,或者模块数量超过 50 个时,GIL 和解释开销会让你在答辩现场尴尬。Web 方案则受限于浏览器渲染帧率,波形刷新容易卡顿。

2.3 模块抽象基类的设计

所有仿真模块继承同一个基类,这是整个项目的骨架。基类定义三个核心接口:compute()负责单步计算,getOutput()返回输出值,setParameter()接收参数更新。下面是一个简化版的基类定义。

// SimulationBlock.h class SimulationBlock : public QGraphicsItem { public: virtual ~SimulationBlock() = default; // 单步计算:dt 为当前步长,inputs 为输入端口的值列表 virtual double compute(double dt, const QVector<double>& inputs) = 0; // 返回当前输出值,供下游模块读取 virtual double getOutput() const = 0; // 参数配置接口,由属性对话框调用 virtual void setParameter(const QString& key, double value) = 0; // 端口定义:输入端口数和输出端口数 virtual int inputCount() const = 0; virtual int outputCount() const = 0; // QGraphicsItem 必须实现的绘制和边界 QRectF boundingRect() const override; void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override; protected: QVector<double> m_outputs; // 输出缓存 QMap<QString, double> m_params; // 参数表 };

这个基类里,compute()是纯虚函数,每个具体模块自己实现计算逻辑。比如增益模块的compute()就是把输入乘以增益系数,传递函数模块则要维护内部状态做差分方程迭代。inputCount()和outputCount()决定模块在画布上画几个端口,连线时只能从输出端口连到输入端口。

参数表用QMap<QString, double>存储,键是参数名,值是数值。属性对话框遍历这个表生成编辑框,用户改完后调用setParameter()更新。这样新增模块时,只要在构造函数里往m_params塞默认值,界面自动适配,不用改对话框代码。

注意:compute()的调用顺序必须按拓扑排序后的顺序执行,否则下游模块会读到上一帧的旧值。拓扑排序在每次连线变化后重新计算一次,缓存起来。

3. 画布与连线实现:Graphics View 框架下的拖拽、连接与拓扑排序

3.1 画布场景的初始化与模块拖拽

画布用QGraphicsScene和QGraphicsView搭建。场景负责管理所有图元,视图负责显示和交互。侧边栏的模块列表用QListWidget,每个条目携带模块类型标识。拖拽通过重写mousePressEvent和dropEvent实现。

// CanvasView.cpp 拖拽放置的核心逻辑 void CanvasView::dropEvent(QDropEvent* event) { if (event->mimeData()->hasFormat("application/x-simblock")) { QByteArray data = event->mimeData()->data("application/x-simblock"); QString blockType = QString::fromUtf8(data); // 根据类型创建对应模块实例 SimulationBlock* block = BlockFactory::create(blockType); if (block) { QPointF scenePos = mapToScene(event->pos()); block->setPos(scenePos); scene()->addItem(block); m_blocks.append(block); // 新增模块后触发拓扑重排 rebuildTopology(); } event->acceptProposedAction(); } }

BlockFactory::create()是一个简单工厂,根据字符串返回对应模块的new实例。新增模块类型时只需在工厂里注册一行映射,不用改拖拽逻辑。rebuildTopology()在每次模块增删或连线变化后调用,重新计算执行顺序。

3.2 连线图元的设计与信号流向判定

连线用自定义的QGraphicsPathItem子类实现。每条连线记录源端口和目标端口,端口本身是模块上的小圆点,用QGraphicsEllipseItem绘制。连线创建流程是:鼠标在输出端口按下,拖动时画一条临时线,松开时检测落点是否在输入端口上,是则建立连接。

// ConnectionItem.cpp 连线建立的关键判断 void ConnectionItem::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) { QPointF endPos = event->scenePos(); // 查找落点下的输入端口 QGraphicsItem* item = scene()->itemAt(endPos, QTransform()); PortItem* port = dynamic_cast<PortItem*>(item); if (port && port->isInput()) { // 检查是否重复连接同一端口 if (!m_sourcePort->isConnectedTo(port)) { m_targetPort = port; m_sourcePort->addConnection(this); port->addConnection(this); updatePath(); // 重绘连线路径 m_scene->rebuildTopology(); // 拓扑重排 } } else { // 落点无效,删除临时连线 scene()->removeItem(this); delete this; } }

连线路径用三次贝塞尔曲线绘制,控制点根据源端口和目标端口的相对位置动态计算,保证线条平滑且不穿过模块。信号流向由源端口到目标端口的方向决定,在拓扑排序时作为有向边处理。

3.3 拓扑排序与仿真执行顺序

仿真执行前必须确定模块的计算顺序。如果 A 的输出连到 B 的输入,B 必须在 A 之后计算。这是一个有向无环图(DAG)的拓扑排序问题。我用 Kahn 算法实现,每次连线变化后重新计算。

// TopologySorter.cpp 拓扑排序核心 QVector<SimulationBlock*> TopologySorter::sort(const QList<SimulationBlock*>& blocks) { QMap<SimulationBlock*, int> inDegree; QMap<SimulationBlock*, QList<SimulationBlock*>> adjList; // 初始化入度和邻接表 for (SimulationBlock* block : blocks) { inDegree[block] = 0; adjList[block] = QList<SimulationBlock*>(); } // 根据连线构建有向边 for (SimulationBlock* block : blocks) { for (ConnectionItem* conn : block->outputConnections()) { SimulationBlock* target = conn->targetBlock(); adjList[block].append(target); inDegree[target]++; } } // Kahn 算法:入度为 0 的节点入队 QQueue<SimulationBlock*> queue; for (auto it = inDegree.begin(); it != inDegree.end(); ++it) { if (it.value() == 0) queue.enqueue(it.key()); } QVector<SimulationBlock*> sorted; while (!queue.isEmpty()) { SimulationBlock* current = queue.dequeue(); sorted.append(current); for (SimulationBlock* next : adjList[current]) { inDegree[next]--; if (inDegree[next] == 0) queue.enqueue(next); } } // 如果排序结果数量小于模块总数,说明存在环 if (sorted.size() != blocks.size()) { qWarning() << "检测到环路,仿真无法执行"; return {}; } return sorted; }

排序结果缓存到m_executionOrder中,仿真循环直接按这个顺序遍历。如果检测到环路(比如用户把输出连回了上游),返回空列表并弹出警告,阻止仿真启动。这个检查必须在每次连线变化后做,不能等到点运行才报错。

提示:拓扑排序的时间复杂度是 O(V+E),模块数量在百级以内时耗时可以忽略。但如果你的画布支持嵌套子系统,排序要递归处理,这时建议加缓存,只在结构变化时重算。

4. 求解器与波形显示:固定步长引擎和实时曲线绘制

4.1 固定步长求解器的实现

求解器负责推进仿真时间,每一步按拓扑顺序调用所有模块的compute()。固定步长是最容易实现的方案,适合大多数教学和毕设场景。步长通过界面配置,默认 0.01 秒。

// Solver.cpp 固定步长求解器 void Solver::run(double endTime, double stepSize) { double t = 0.0; m_simTime = 0.0; m_isRunning = true; while (t < endTime && m_isRunning) { // 按拓扑顺序计算每个模块 for (SimulationBlock* block : m_executionOrder) { // 收集输入端口的值 QVector<double> inputs; for (ConnectionItem* conn : block->inputConnections()) { inputs.append(conn->sourceBlock()->getOutput()); } // 单步计算 block->compute(stepSize, inputs); } // 采集示波器数据 for (ScopeBlock* scope : m_scopes) { scope->record(t, scope->getOutput()); } t += stepSize; m_simTime = t; // 每步发出信号,驱动界面刷新 emit stepCompleted(t); // 处理界面事件,避免卡死 QCoreApplication::processEvents(); } m_isRunning = false; emit simulationFinished(); }

processEvents()这行很关键。仿真循环如果一直占着主线程,界面会假死,用户点停止按钮都没反应。每步调用一次processEvents()让 Qt 处理待办事件,停止按钮的点击才能被响应。但要注意,processEvents()不能滥用,否则仿真速度会明显下降。我的做法是每 10 步调用一次,兼顾响应和性能。

4.2 示波器模块与波形绘制

示波器模块继承SimulationBlock,额外维护两个QVector<double>分别存时间和数值。绘制用QCustomPlot或 QtCharts,前者性能更好,适合实时刷新。每次stepCompleted信号触发时,示波器把新数据追加到曲线。

// ScopeBlock.cpp 数据记录与曲线更新 void ScopeBlock::record(double time, double value) { m_timeData.append(time); m_valueData.append(value); // 限制缓存长度,防止内存无限增长 if (m_timeData.size() > MAX_POINTS) { m_timeData.removeFirst(); m_valueData.removeFirst(); } // 每 5 个点更新一次曲线,降低绘制开销 if (m_timeData.size() % 5 == 0) { m_curve->setData(m_timeData, m_valueData); m_plot->replot(QCustomPlot::rpQueuedReplot); } }

rpQueuedReplot是 QCustomPlot 的排队重绘模式,避免每次setData都立即触发重绘导致界面卡顿。缓存长度限制在 10000 个点以内,超出后移除最早的数据,保证长时间仿真不会耗尽内存。

4.3 仿真线程与界面响应

当步长很小或模块很多时,主线程跑仿真仍然会卡。更稳妥的做法是把求解器放到独立线程,通过信号槽与界面通信。Solver继承QThread,在run()里执行循环,每步发出stepCompleted信号,界面线程接收后更新波形。

// 主窗口启动仿真的代码 void MainWindow::onRunClicked() { if (m_solver->isRunning()) { m_solver->stop(); return; } // 收集当前画布上的模块和连线 QList<SimulationBlock*> blocks = m_canvas->blocks(); QVector<SimulationBlock*> order = TopologySorter::sort(blocks); if (order.isEmpty()) { QMessageBox::warning(this, "错误", "模型存在环路,无法仿真"); return; } m_solver->setExecutionOrder(order); m_solver->setEndTime(m_endTimeEdit->value()); m_solver->setStepSize(m_stepSizeEdit->value()); m_solver->start(); // 启动线程 }

线程方案下,processEvents()就不需要了,因为界面线程本身是空闲的。但要注意跨线程访问数据的安全性,示波器的数据写入必须在界面线程完成,所以stepCompleted信号用Qt::QueuedConnection连接,确保槽函数在界面线程执行。

5. 避坑与常见问题:连线、线程和内存里最容易翻车的五个点

5.1 连线后仿真结果不变

现象:画布上连好了线,点运行后下游模块的输出始终是零。原因通常是拓扑排序没有在连线变化后重新计算,求解器还在用旧的执行顺序。解决:在ConnectionItem的创建和删除逻辑里都调用rebuildTopology(),确保执行顺序始终与当前图结构一致。我一般会在rebuildTopology()里加一行日志,输出排序后的模块名列表,调试时一眼就能看出顺序对不对。

5.2 仿真跑着跑着界面卡死

现象:点运行后界面无响应,停止按钮点不动。原因有两个可能:一是求解器跑在主线程且没有调用processEvents();二是波形刷新太频繁,绘制开销压垮了事件循环。解决:优先把求解器移到独立线程;如果暂时不想改线程,至少在循环里每 10 步调用一次processEvents(),并降低波形刷新频率,比如每 5 个数据点才重绘一次。

5.3 删除模块后连线残留

现象:删掉一个模块,连在它上面的线还留在画布上,点运行时报空指针。原因:模块的析构函数没有清理关联的连线图元。解决:在SimulationBlock的析构函数里遍历所有输入输出连线,逐个从场景中移除并删除。更稳妥的做法是重写QGraphicsItem::itemChange(),在ItemRemoved时触发清理。我一般会在模块删除前先弹确认框,确认后统一清理连线和拓扑缓存。

5.4 参数修改后仿真结果不更新

现象:双击模块改了参数,重新运行结果和改之前一样。原因:属性对话框修改的是临时变量,没有调用setParameter()同步到模块实例。解决:对话框的确定按钮槽函数里,遍历所有编辑框,逐个调用模块的setParameter()。如果参数影响内部状态(比如传递函数的系数),还要在setParameter()里重置内部状态变量,否则上一次仿真的残留状态会污染下一次。

5.5 长时间仿真内存持续增长

现象:仿真运行几分钟后内存占用飙升,最终崩溃。原因:示波器的数据缓存没有上限,每个步长都追加数据,时间越长占用越大。解决:给示波器设置最大缓存点数,比如 10000 个,超出后移除最早的数据。如果用户需要完整数据,提供导出 CSV 的功能,把历史数据写到文件后从内存释放。这个坑我在早期版本里踩过,仿真跑了半小时直接吃掉了 2GB 内存,后来加了缓存上限才稳住。

6. 验收与答辩:怎么证明这套仿真界面真的能跑

验收的核心不是界面多好看,而是仿真结果对不对。我一般会准备三个测试用例,覆盖从简单到复杂的场景。第一个用例是增益模块串联:信号源接一个增益为 2 的模块,再接示波器,输入正弦波,输出应该是幅值翻倍的正弦波。第二个用例是传递函数阶跃响应:给一个一阶传递函数输入阶跃信号,输出应该按指数规律上升,稳态值为增益系数。第三个用例是闭环控制:信号源接求和点,求和点接控制器,控制器接被控对象,被控对象输出反馈回求和点,观察闭环响应是否稳定。

答辩时,评委最常问的三个问题是:拓扑排序怎么处理环路、仿真步长怎么选、波形数据怎么保证不丢。第一个问题按第 3 章的 Kahn 算法回答,重点说检测到环时返回空列表并阻止仿真。第二个问题说清楚固定步长的选择依据:步长要小于系统最小时间常数的十分之一,否则数值积分会发散。第三个问题说示波器的缓存策略和导出机制,强调数据完整性靠 CSV 导出保证,内存里只保留最近窗口。

验收表格可以这样列:

测试项输入预期输出实际输出结论
增益串联正弦波,增益 2幅值翻倍正弦波幅值翻倍正弦波通过
一阶阶跃阶跃信号,K=1,T=0.5指数上升至 1指数上升至 1通过
闭环稳定阶跃信号,PID 参数整定无发散,稳态误差趋零无发散,稳态误差趋零通过
环路检测输出接回上游弹出警告,拒绝运行弹出警告,拒绝运行通过

答辩前我会把这三个用例各跑一遍,截图保存波形,打印出来夹在报告附录里。评委看到实际波形和理论波形对得上,基本就不会再追问实现细节了。从那以后我每次带仿真类项目,都强制要求先跑通这三个用例再写报告,顺序反了的话,报告写得再漂亮,现场跑不出来也是白搭。希望帮到你。

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

相关新闻

MATLAB枝切法相位解包裹:从奇点检测到单连通解包的完整实现

MATLAB枝切法相位解包裹:从奇点检测到单连通解包的完整实现

简介&#xff1a;本资源是一套基于MATLAB实现的经典枝切法相位解包裹完整代码包&#xff0c;面向光学测量、干涉图像处理、数字全息等领域的初学者与工程实践者&#xff0c;解决干涉图中因2π模糊导致的相位不连续问题。压缩包共14个文件&#xff0c;含9幅BMP格式的典型干涉图样…

2026/10/20 3:19:01 阅读更多 →
OpenCvSharp 4.1 C#调用OpenCV底层原理与工程实践指南

OpenCvSharp 4.1 C#调用OpenCV底层原理与工程实践指南

简介&#xff1a;本资源是OpenCvSharp 4.1官方配套例程集合&#xff0c;专为C#开发者设计&#xff0c;用于快速上手基于OpenCV 4.1的计算机视觉开发&#xff0c;适用于图像处理、目标检测、特征提取等典型应用场景&#xff0c;适合具备基础C#编程能力并初涉OpenCV的中初级开发者…

2026/10/20 3:19:01 阅读更多 →
零代码调教大模型:5步提示词工程,把AI从“人工智障”变成专属助手

零代码调教大模型:5步提示词工程,把AI从“人工智障”变成专属助手

我最早用大模型的时候&#xff0c;心里只有一个想法&#xff1a;这玩意儿是不是被人吹过头了。让它帮忙写封邮件&#xff0c;它洋洋洒洒给你整出八百字&#xff1b;让它总结一段会议记录&#xff0c;关键数字全给漏了&#xff1b;更绝的是&#xff0c;你问它一个冷门问题&#…

2026/10/20 3:17:59 阅读更多 →

最新新闻

从技能清单到能力体系:可迁移能力的拆解与验证方法

从技能清单到能力体系:可迁移能力的拆解与验证方法

大概是五六年前&#xff0c;我在一家节奏很快的公司做开发&#xff0c;每天被各种新技术名词裹着往前走。那时候我的“skills”&#xff0c;说白了就是一份越抄越长的备忘录&#xff0c;里面堆满了框架名、工具名、各种公开课的完成记录&#xff0c;看起来特别有安全感。但真到…

2026/10/20 13:35:45 阅读更多 →
Windows 下 VSCode 配置 yaml-cpp 全攻略:vcpkg 与源码编译双方案

Windows 下 VSCode 配置 yaml-cpp 全攻略:vcpkg 与源码编译双方案

1. 为什么在 Windows 上折腾 yaml-cpp 值得单独写一篇如果你平时写 C&#xff0c;尤其是做机器人、仿真、游戏配置或者工业软件方向&#xff0c;大概率绕不开 YAML 这种配置文件格式。它比 INI 表达力强&#xff0c;比 JSON 好写注释&#xff0c;比 XML 顺眼太多。而 yaml-cpp …

2026/10/20 13:35:45 阅读更多 →
REA模型实战:用事件驱动重构订单与账务系统

REA模型实战:用事件驱动重构订单与账务系统

1. "REA" 这个词&#xff0c;我前后摸索了好几天才真正接上话我在某次内部架构评审会上第一次听到"REA"这个词&#xff0c;当时差点没接住话头。同事说"下单模块要继续加状态的话&#xff0c;后面退款和风控都很难做&#xff0c;不如按 REA 重建"…

2026/10/20 13:35:45 阅读更多 →
技能学习的底层逻辑:从收藏到实战,打造个人能力系统

技能学习的底层逻辑:从收藏到实战,打造个人能力系统

"skills"这个词这几年在热搜里的出现频率越来越高。今天网络热词是AI、提示词、搞钱、上岸&#xff0c;明天是内卷、复盘、长期主义、个人IP&#xff0c;但你把每一轮热度拆开看&#xff0c;背后其实都是同一个问题&#xff1a;怎么让自己变得更有价值。技能学习说穿…

2026/10/20 13:35:45 阅读更多 →
SVN服务器IP变更实战:relocate命令原理与批量迁移避坑指南

SVN服务器IP变更实战:relocate命令原理与批量迁移避坑指南

能在一天之内把整个开发部的SVN地址从旧IP迁到新IP&#xff0c;其实不是个复杂活&#xff0c;但你如果没搞清楚SVN地址到底存在哪里、用什么命令改、改了之后哪些人还要跟着动&#xff0c;那这件事可能从五分钟变成一下午。最近我又处理了一轮SVN服务迁移&#xff0c;把所有要改…

2026/10/20 13:35:45 阅读更多 →
基于Win32 API与GDI+的捕鱼达人游戏开发实战:C++课程设计完整方案

基于Win32 API与GDI+的捕鱼达人游戏开发实战:C++课程设计完整方案

简介&#xff1a;这是一份面向高校C课程设计与Windows游戏开发初学者的实践项目源码&#xff0c;主题为经典小游戏“捕鱼达人”。项目以C语言为基础&#xff0c;结合Windows API完成窗口创建、消息循环与事件响应&#xff0c;并借助GDI绘制游戏背景、鱼群、炮台与子弹等元素&am…

2026/10/20 13:34:41 阅读更多 →

日新闻

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 阅读更多 →