news 2026/10/16 1:18:06

Qt+Halcon实现工业OCR识别:从环境配置到代码实战全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Qt+Halcon实现工业OCR识别:从环境配置到代码实战全解析

先扔个结论:这个项目其实没有听起来那么神秘。核心就三块,图像处理用 Halcon 的算子,界面和业务逻辑用 Qt 来兜底,两者通过 C++ 无缝对接。真要跑通一个能识别数字和字母的简易 OCR,工作量比你想象的小,但坑也比你想象的多——尤其是环境配置和图像格式转换这两块,会浪费掉你不少时间。这篇就把我踩过的坑、常用的套路和可以直接抄的代码,一次性整理清楚。

1. 为什么用 Qt + Halcon 这套组合

1.1 这套组合适合谁

先别急着上来就研究各种高级算法,你首先得搞清楚自己为什么要选这个组合。我个人的判断是,只要你的需求是“设备端离线识别”、“固定版面字符”或者说“工业场景下的快速落地”,Qt + Halcon 几乎是最省心的一条路,没有之一。

Halcon 的价值在于它把图像处理的东西封装得非常完善,像阈值分割、连通域分析、形态学、OCR 分类器这些,本来要写几百行 OpenCV 代码的活儿,它一个算子就能搞定。Qt 的价值在于它能稳定地把界面、线程、相机采集、结果入库串起来。Halcon 只管“眼睛”,Qt 管“手脚”,两者各干各的,组合起来就是一套很标准的桌面级视觉应用架构。

而且 Halcon 在 Windows、Linux 下都有完整的 C++ API,跟 Qt 的兼容性非常好,不像某些视觉 SDK,要么只有 Windows 版本,要么接口老得像是上个时代的产物。对于做项目周期短、现场环境乱、又要稳定交付的场景,这套组合的成熟度确实很高。

1.2 为什么不选纯 OpenCV 或深度学习

很多做纯软件出身的朋友会本能地反问:OpenCV 免费,加上 Tesseract 也能做 OCR,为什么要碰商业库?

我的看法是这样的:如果你是自己玩、数据量不大、对识别率要求没那么严格,OpenCV + Tesseract 当然可以。但对项目交付来说,Tesseract 对印刷体尚且需要大量预处理,面对工业现场常见的反光、倾斜、笔画断裂,效果往往会掉得让你措手不及。Halcon 在传统视觉算子层面沉淀了很多可以直接复用的成熟方案,比如可训练的 OCR 分类器(.omc 模型),你只需要提供少量字符样本,就能训练出适配当前场景的识别模型,这个在工业件号、条码、金属刻字、PCB 丝印上非常实用。

不用一上来就上深度学习,一个简易 OCR 项目用传统 MLP 分类器完全够了。深度学习训练需要样本量、标注成本和显卡资源,对一个只想快速识别一排数字和字母的桌面工具来说,属于杀鸡用牛刀。Halcon 的 OCR 分类器本质上也属于机器学习,但它是专门为“少样本、在线快速推理”设计的,几百个字符样本就能训出一个可用的模型,完全满足大多数场景。

1.3 项目整体流程梳理

整个项目的流程可以拆成这样:

  1. 通过相机或者读图方式获取图像。
  2. 在 Halcon 里对图像做预处理(转灰度、二值化、形态学处理)。
  3. 通过 Blob 分析或文本区域定位找到字符区域。
  4. 把字符区域交给 OCR 分类器逐个识别。
  5. 坐标排序,组合成完整字符串。
  6. 把结果传回 Qt 界面显示,同时可以在图像上绘制识别框。

这个流程听起来简单,但几乎没有一步是能偷懒的。预处理做得是否到位,直接决定了后面识别率的上限;区域定位做得不好,字符一粘连,分类器再强也白搭。后面我每一层都会拆开细讲。

2. 环境搭建与工程配置

2.1 版本选择和安装注意

Halcon 的版本号更新比较频繁,但我的建议是选稳定版,不要追新版。衡量标准很简单:你周围现有的工程或者网上能查到的资料,哪个版本用的多,你就选哪个。每个版本对 C++ 接口的细节会有一点调整,尤其是命名空间、算子参数列表的兼容性,旧项目迁移到新版本偶尔会冒出莫名其妙的编译错误。

安装路径不要带空格,不要装在中文路径下,这个不是玄学,是为了后面库文件搜索和调试时少给自己找麻烦。装完后检查下安装目录里有没有include/halconcpp和lib/x64-win64这两个关键目录,缺一个后面 Qt 工程就根本配不起来。

2.2 Qt 工程里正确链接 Halcon

新建一个Qt Widgets Application,然后在.pro文件里加下面这段配置。以 Windows + 64 位 + x64 为例:

INCLUDEPATH += C:/Program Files/MVTec/HALCON-18.11/include \ C:/Program Files/MVTec/HALCON-18.11/include/halconcpp LIBS += -LC:/Program Files/MVTec/HALCON-18.11/lib/x64-win64 \ -lhalconcpp

照着上面写完,先编译一个空工程看看能不能过,能过说明路径没问题。这里有个小坑:Halcon 的 32 位库和 64 位库目录是不同的,如果你的 Qt 是 32 位编译器,链接的是lib/x86-win32,那链接的库名也不是 halconcpp,而是 halconcpp(实际文件名可能有halconcpp.lib和halconcpp.dll两种形态,链接时指向 lib 文件)。所以务必先确认你的 Qt 构建套件到底编译的是 x86 还是 x64,别在这里盲目复制网上教程。

2.3 运行时 DLL 的坑

编译能过只是第一步。程序运行起来后最常见的错误是找不到halcon.dll或halconcpp.dll。

解决办法有两个,推荐第二个:

  • 把lib/x64-win64里所有 dll 复制到 exe 所在目录。
  • 在 Qt 的构建环境中,把 Halcon 的 bin 目录加到PATH环境变量里。建议写一个 .bat 脚本设置好PATH再启动程序。

很多人喜欢用 windeployqt 打包,但 Halcon 的依赖比较多,halcon.dll还会依赖它配套的 runtime 文件,最简单的做法是直接保持PATH指向安装目录,不要试图手动分离 dll。等你最后要发布给客户时,再考虑把需要的 dll 统一收集到发布目录里。

3. 图像处理与 OCR 识别流程拆解

3.1 图像采集:文件读图还是相机回调

简易项目里,先用读图把流程跑通是最务实的做法。我建议最开始不要接相机,用一张分辨率、光照都比较理想的样例图,先把识别流程调好,再考虑相机接入。原因很简单:如果读图都识别不对,接上相机后只会更差,干扰因素太多,你很难定位问题到底出在取图、处理还是分类器。

等读图流程稳定了,再接相机。Halcon 自带的HFramegrabber可以接很多常见工业相机品牌,也可以用 Qt 的QCamera采集后再转成HImage。更保守的方案是:利用相机厂商提供的 SDK 把图像回调拿回来,转成QImage后塞给 Halcon。这样的好处是相机本身的配置、触发、曝光控制都在厂家 SDK 里搞定,Halcon 只负责纯视觉部分,工程上更清晰。

3.2 预处理:灰度、二值化、形态学

不管是从相机来的还是从文件读的,Halcon 拿到的图像一般是三通道或者多通道,第一步统一转成灰度图。这一步用算子Rgb1ToGray或者直接读取时转灰度都可以。然后做二值化。二值化的核心目的是把字符和背景彻底分开。

对于“深色字符、浅色背景”的常见场景,直接使用全局阈值Threshold就行。但要注意,光照不均时全局阈值会死得很惨,我实际项目中无数次被反光害过。这时试一下LocalThreshold或者先用均值滤波预处理,保留背景灰度趋势,再做阈值分割,效果会稳很多。

形态学处理也很关键。闭运算能把字符内部的断裂缺口补上,开运算能去掉小的孤立噪点。这里没有固定套路,你要养成一个好习惯:每做一步预处理,就在 Halcon 的窗口里看一次中间结果,别嫌麻烦。预处理做得好,后面的识别率才有保障。

3.3 字符区域定位:Blob 分析的经典路径

定位字符区域有两种思路。

第一种是传统的 Blob 分析。具体就是把二值化后的区域做连通域分析,然后根据字符的特性(高度、宽度、面积、长宽比、紧凑度)筛选出真正的字符区域,最后按从左到右、从上到下的顺序排序。

第二种是用 Halcon 高版本提供的自动文本检测工具(TextModel相关的一套)。它会把文本行区域直接找出来,省掉手工 Blob 的功夫。但这东西对版式复杂的场景更好用,对“只需要识别固定位置的几个字符”这种场景反而有点杀鸡用牛刀。简易项目我建议先把 Blob 这套基本功打扎实,后面就算遇到复杂工况,也知道底层原理是什么。

SelectShape算子筛选区域时,需要根据实际字符大小调整height、width的阈值范围。这个没有通用值,每个人场景不同都要重新看。比较实用的办法是在调试阶段把每个区域的面积打出来,看一眼分布规律,再反推合适的阈值。

3.4 加载 OCR 模型并逐个识别

Halcon 自带了很多通用 OCR 模型,比如Industrial_0-9A-Z_NoRej(工业字符、忽略拒绝)、Document_0-9A-Z_NoRej(文档字符)。这类模型对标准字体、标准质量下的字符识别效果不错,可以直接调用。

如果你的字符包含特殊符号,或者字符质量很差(刻印、压花、点阵),通用模型大概率识别率拉胯。解决办法是自己训练模型:采集一批真实图像,做同样的预处理,然后用 Halcon 的标定工具(比如create_ocr_train_data相关流程)标注字符,训练一个自己的.omc文件。这个工作量并不大,对简易项目而言,差不多一两个小时就能跑完一轮训练。但训练时要特别注意:训练样本的图像预处理必须和实际识别时完全一致,否则模型学到的特征和实际输入的特征对不上,识别率反而更差。

识别时,用DoOcrMulti一次处理多个字符区域就行,它会返回识别出的字符内容和置信度(Confidence)。置信度非常有用,你可以设置一个阈值(比如低于 0.7 就判为可疑),在界面上用不同颜色标出来,让用户能立刻发现识别结果可能不对。

4. 核心代码实现与联调

4.1 头文件与基础封装

先把基础工作做好:

#include "HalconCpp.h" using namespace HalconCpp;

在 Qt 项目中,我建议写一个独立的OcrEngine类,负责加载模型、执行识别、返回结果。不要在界面类里写一大坨 Halcon 算子,否则后面加功能、换模型的时候,光是找代码就够你烦的。OcrEngine的接口大概长这样:

class OcrEngine { public: OcrEngine(); ~OcrEngine(); bool loadOcrModel(const QString &modelPath); QString recognizeImage(const QImage &image, double *confidence = nullptr); private: HOCR ocrHandle; QString m_modelPath; };

这里我故意把HOCR这个句柄放在类成员里,让它随类对象释放,避免每次识别都重复加载模型。模型加载一次能省不少时间,尤其当你要在界面上连续识别时,这种细节优化体感非常明显。

4.2 QImage 与 HImage 互转

这一步是 Qt 和 Halcon 对接中最容易出 bug 的地方,单独拎出来讲。

QImage 转 HImage:

HImage OcrEngine::qImageToHImage(const QImage &src) { QImage gray = src.convertToFormat(QImage::Format_Grayscale8); HImage hImg; HOperatorSet::GenImage1(&hImg, "byte", gray.width(), gray.height(), (Hlong)gray.constBits()); // 注意:constBits 指向的内存必须在此调用期间保持有效 // 实际封装时建议把数据拷贝到 std::vector 里再传给 Halcon return hImg; }

这里有个非常隐蔽的坑:QImage::constBits()拿到的是 QImage 内部缓冲区的指针。QImage 一旦被修改、重新分配或生命周期结束,指针指向的内存就失效了。所以正确做法是先把像素数据拷贝到自己的缓冲区里,再传给 Halcon,或者确保gray对象生命期覆盖到GenImage1调用完成之后。

HImage 转 QImage(通常用于在界面上显示 Halcon 处理结果):

QImage OcrEngine::hImageToQImage(const HImage &hImg) { Hlong width, height; HString type; Hbyte *ptr = (Hbyte*)hImg.GetImagePointer1(&type, &width, &height); QImage::Format fmt = QImage::Format_Grayscale8; if (type.Equal("rgb")) { fmt = QImage::Format_RGB888; } QImage img(ptr, width, height, fmt); // 这里同样要注意,Halcon 可能使用内部内存对齐 // 如果显示出现斜纹,需要用 bitsPerLine 参数构造 QImage return img.copy(); }

img.copy()非常重要,它会把数据完整拷贝到 QImage 自己的缓冲区里,这样即使 Halcon 那边图像被释放,这边的QImage也依然安全。不copy()的话,你只是拿了 Halcon 内存区的映射。

4.3 识别主流程

这是一段简化但完整的识别流程:

QString OcrEngine::recognizeImage(const QImage &image, double *confidence) { HImage hImg = qImageToHImage(image); // 1. 转灰度 HImage gray; HOperatorSet::Rgb1ToGray(hImg, &gray); // 2. 二值化 HRegion reg; HOperatorSet::Threshold(gray, &reg, 0, 200); // 3. 连通域和筛选 HRegion connection; HOperatorSet::Connection(reg, &connection); HRegion selected; HOperatorSet::SelectShape(connection, &selected, "height", "and", 15, 200); HOperatorSet::SelectShape(selected, &selected, "width", "and", 5, 150); // 4. 按从左到右排序 HTuple sortIndex; HOperatorSet::SortRegion(selected, &selected, &sortIndex, "character", "true", "row"); // 5. 加载模型(如果尚未加载) // 这里假设 ocrHandle 已经在 loadOcrModel 里初始化 // 6. 识别 HTuple recognizedChars, confidenceVal; HOperatorSet::DoOcrMulti(selected, gray, ocrHandle, &recognizedChars, &confidenceVal); // 7. 拼装结果 QString result; double minConf = 1.0; for (int i = 0; i < recognizedChars.Length(); ++i) { result += QString::fromStdString( recognizedChars[i].S().ToString()); double conf = confidenceVal[i].D(); if (conf < minConf) minConf = conf; } if (confidence) *confidence = minConf; return result; }

上面这段代码不能直接原样复制到你的项目里就完事,因为阈值参数、筛选条件都是按我自己的样例图定的,但流程就是这么个流程。核心逻辑很清楚:二值化 -> 连接区域 -> 筛选 -> 排序 -> 识别 -> 拼字符串。

SortRegion里的"character"模式很有意思,它会自动感知字符的排列方向,用于读取一行字符时比单纯按row排序要可靠得多。如果你的字符是单行,传"character", "true", "row"基本都能得到正确顺序;如果是多行,还得再加行判断逻辑,这里就先不展开。

4.4 在界面上显示图像和识别框

为了让用户能看到识别目标在哪里,通常要在原图上画框。Halcon 里可以用DispObj直接显示图像,再用DispRectangle等方式画框。在 Qt 界面里,常见做法是用一个 QLabel + QPixmap 显示结果图。

我更推荐的做法是:不直接把 Halcon 窗口嵌入 Qt 界面,而是把 Halcon 的显示结果导出成 HImage,再转成 QImage 显示。好处是你可以把识别框、字符内容、置信度这些信息全部在 QImage 上绘制好,逻辑清晰,也好做界面美化。如果你追求实时性,再考虑用 Halcon 自带的窗口控件和 Qt 做窗口嵌入,但嵌入式的坐标同步、刷新机制都比较繁琐,简易项目不建议先碰。

4.5 多线程与界面卡顿

Qt 的主线程是 UI 线程,如果直接在槽函数里调用上面那段识别流程,图像大一点、区域多一点的时候界面会卡成 PPT。解决办法就是开一个QThread工作线程去跑识别,完成后通过信号把结果发回 UI 线程。

线程模型可以用 Qt 的QThread + Worker(QObject)方式:把OcrEngine放到 Worker 对象里,使用moveToThread(appWorker, &workerThread)移入子线程,然后通过信号槽触发识别。这里最大的坑是:模型加载也要放到工作线程里完成,不要在主线程加载完再把模型对象跨线程使用。Halcon 的句柄并不是所有情况都支持跨线程共享,你非要共享的话,结果就是莫名其妙崩溃或者识别结果错乱。

我项目中的习惯是:工作线程启动后,在线程入口先加载 OCR 模型,然后进入循环等待取图识别,识别完把结果发回主线程。线程内部串行处理图片,保证同时只有一张图在识别,避免 Halcon 算子并发调用出问题。

5. 常见问题与避坑记录

5.1 运行时提示找不到 halcon.dll

这个问题排第一,90% 的新手都会撞上。编译链接没问题,但运行时就弹窗说缺 DLL。原因很简单:链接时找到了.lib,但运行时系统却在 exe 所在目录和PATH里找不到.dll。

排查顺序:

  1. 确认你编译的是x64还是x86版本,Halcon 装的是哪个位数。
  2. 把lib/x64-win64下所有 DLL 复制到 exe 输出目录,快速验证。
  3. 不要在系统环境变量里乱设,容易影响其他程序,建议在 Qt 的“构建环境”里单独加PATH,或者写启动脚本。

5.2 QImage 转 HImage 后识别结果全是空

常见原因有三个:

第一,QImage 的字节序或格式不对。如果原始图像是Format_RGB888,而你直接默认它是一张灰度图去传给 Halcon,那 Halcon 把所有像素都当单通道读,结果一定会乱。要习惯先convertToFormat(QImage::Format_Grayscale8)。

第二,QImage 的每行字节数和图像宽度不对齐。QImage 为了性能,每行可能有 padding,不一定是width * channels。如果你用了不带行字节参数的构造方式,图像会出现斜纹或者错位。解决办法是检查bytesPerLine(),把它显式传给 Halcon 的接口,或者在拷贝时做逐行处理,去掉 padding。

第三,图像数据指针提前失效。前面提到过,QImage内部缓冲区生命周期必须覆盖到GenImage1调用结束。更稳妥的做法是先memcpy到std::vector<unsigned char>,再传给 Halcon。

5.3 识别准确率上不去,问题可能出在预处理

识别率低的时候,不要一上来就怪模型。先打开中间结果,仔细看二值图是否干净。字符是否粘连?有没有断笔?背景有没有干扰点?这些对识别率的影响比模型选择大得多。

几个常用的调试技巧:

  • 调整二值化阈值前,先看灰度直方图,判断前景和背景的分布。
  • 字符粘连时,尝试形态学腐蚀分离;字符断裂时,尝试膨胀或闭运算。
  • 光照不均时,优先考虑LocalThreshold或做背景校正后再全局阈值。
  • 干扰点可以用SelectShape按面积过滤,但要小心别把噪声和点类字符一起删掉。

做了预处理优化后识别率通常会有质的提升。真实工业项目里,预处理占了我一半以上的调试时间,一点都不夸张。

5.4 界面刷新频繁导致卡顿

如果你决定在界面上持续显示相机画面,每帧都做HImage -> QImage -> QPixmap -> QLabel这一套全流程,帧率必然上不去。减少卡顿的方法:

  1. 显示用的图像和识别用的图像分离:显示可以降低分辨率,识别用原始分辨率。
  2. 控制界面刷新频率,比如 15 帧/秒,识别线程和显示线程互相独立。
  3. 不要每帧都copy()给 QImage,直接在 Halcon 内存区映射 QImage,绘制完立刻释放。

这一点在联调阶段很容易被忽略,等相机一启动全部卡死再找原因就晚了。

5.5 识别结果中字符顺序错乱

字符顺序错乱通常是SortRegion排序参数不对。如果字符只有一行,检查"character", "true", "row"这三个参数。字符有倾斜时需要设置角度容忍范围。多行字符的排序要复杂一些,建议先按行分割,再在行内按列排序,否则很容易出现“先读右边再读左边”的乱序情况。

有些场景下字符区域本身不规整,排序前可以先根据中心坐标做一次聚类,再按聚类结果排序。这个逻辑自己实现也不难,尤其是在 Qt 里存一个QVector<QPoint>然后做排序,非常直观。

6. 基于这套方案,往后还能怎么扩展

6.1 从单张图到批量处理的扩展

如果你跑通的是单张图识别,下一步自然想到批量处理。用 Qt 的QFileDialog一次选择多张图片,塞进一个队列里轮流识别,结果导出成 CSV 或 Excel。这个扩展非常简单,但要注意每张图预处理参数可能需要共用一套,如果图像来源不稳定,批量识别的准确率可能起伏很大。稳妥的办法是先跑一轮全自动识别,把置信度低于阈值的图挑出来人工复核,既保证效率又不漏掉错误。

6.2 从固定位置到任意位置的扩展

简易版通常假设字符在图像某个固定区域。但实际上现场往往字符位置会有偏移、可能有旋转。想更通用,可以用模板匹配先定位一个基准标记,再根据标记位置推算字符区域的相对位置。Halcon 的FindShapeModel系列算子适合做这个,定位精度也够用。

6.3 从数字字母到小字符集的训练

如果场景里出现的字符就那么固定几种(比如只有两位数字加一个点),别费劲找通用 OCR 模型了,直接自己训练一个小模型。采集一两百样本,标注类别,用 Halcon 的 OCR 训练工具生成 .omc 文件,识别率和速度都会非常理想。训练时的关键在于样本要有代表性:光照变化、位置偏移、轻微畸变都要覆盖到,不要只用一张图上截下来的干净字符。

6.4 结合深度学习的方向

如果未来遇到严重反光、低对比度、复杂背景的字符,传统 OCR 到了瓶颈期,可以考虑 Halcon 新的深度学习和字符检测方案。但它需要有 GPU 环境,训练数据和标注成本也不低,所以做项目规划时要先把 ROI(投入产出比)算清楚。简易项目阶段,把传统方案打磨到极限,往往已经能覆盖 80% 以上的常规现场需求。

这个项目做完整之后,最明显的一个体会是:OCR 识别本身往往不是瓶颈,瓶颈都在“图像能不能被稳定地、干净地送到分类器面前”。搞定了预处理和区域定位,后面就是一个顺理成章的事,Qt 在这方面能帮你把流程管得清清楚楚。如果你正准备开新项目,建议先拿一张真实现场图跑一遍这套流程,比你先纠结选哪个模型版本重要得多。

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

裸机开发中的可配置内容与串口控制台指令系统设计

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

作者头像 李华
网站建设 2026/10/16 1:17:14

2S磷酸铁锂主动均衡系统设计:MP2642GR-Z与PIC18F85K90实战

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

作者头像 李华
网站建设 2026/10/16 1:15:37

Excel数据分析基础技能:从清洗到图表的全流程

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

作者头像 李华
网站建设 2026/10/16 1:15:02

双串锂电池主动均衡方案:MP2642+PIC18LF46K42实战设计

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

作者头像 李华
网站建设 2026/10/16 1:15:02

现场总线本质:从模拟信号到数字权力的工业通信革命

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

作者头像 李华
网站建设 2026/10/16 1:14:54

基于MP2642GR-Z与STM32F767BI的磷酸铁锂两串电池主动均衡方案

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

作者头像 李华