9点标定+Halcon+C#:产线视觉引导链路实战拆解
简介本资源面向工业机器人与机器视觉方向的开发者提供一套完整的9点标定Halcon程序及C#联合Halcon的源码实现可用于机器人手眼标定、相机坐标与机器人坐标映射等实际项目场景。包内共577个文件以385个ico图标、73个ssk皮肤资源、29个cs源码文件为主另含hdev脚本、dll动态库、exe可执行程序及bmp、tif等标定图片素材压缩包约50.25MB目录结构完整便于直接编译运行与二次开发。功能上涵盖9点标定算法、串口通讯向机器人发送数据、用户权限登录以及皮肤美化界面兼顾算法逻辑与交互体验。已有137人学习下载适合希望快速搭建标定与通讯框架、参考界面美化方案的中高级开发者可从中获取标定流程实现、串口数据交互思路与完整工程组织方式。1. 9点标定 Halcon C#一条产线级视觉引导链路的真实拆解产线上机械手抓件偏了 0.3 mm吸盘就吸不住偏了 0.5 mm直接撞夹具。这类问题最后都会落到同一个动作上9点标定。它的本质是求一个仿射变换矩阵把相机像素坐标映射到机器人末端或工件坐标系。Halcon 负责图像处理与标定算子C# 负责上位机逻辑、串口通讯和界面。三者拼起来才是一条能上产线的视觉引导链路。这套方案适合做 3C、锂电、汽车零部件上下料的工程师也适合手里有 Halcon license、想用 C# 上位机把标定流程固化下来的开发者。下面按「标定原理 → Halcon 算子 → C# 串口 → 界面落地 → 避坑」的顺序讲透。2. 9点标定的数学底子与 Halcon 算子选型2.1 为什么是 9 个点而不是 3 个或 12 个仿射变换有 6 个自由度x 方向缩放、y 方向缩放、旋转、剪切、x 平移、y 平移。理论上 3 个不共线的点就能解出 6 个未知数。但实际产线上相机畸变、机械手重复定位误差、标定板贴合误差都会让 3 点解出来的矩阵在边缘区域偏差很大。9 点标定的价值在于用最小二乘拟合把噪声摊掉。Halcon 里对应的是vector_to_hom_mat2d输入是像素坐标点和物理坐标点两组数组输出一个 3×3 的齐次矩阵。常见做法是 3×3 网格布点覆盖相机视野的四个角和中心。点间距根据视野大小定一般让 9 个点均匀铺满视野的 70%80%边缘留一点余量避免镜头边缘畸变最大的区域。提示9 点不是硬性规定。视野大、畸变明显的场景可以加到 16 点甚至 25 点Halcon 的vector_to_hom_mat2d对点数没有上限点越多拟合越稳但标定耗时线性增加。2.2 Halcon 标定算子的最小调用链下面这段 Halcon 代码是标定环节的核心作用是读入 9 组像素坐标和对应的物理坐标求解仿射矩阵并把矩阵保存成文件供 C# 调用。* 像素坐标9个标定点在图像中的行列 Px : [100, 300, 500, 100, 300, 500, 100, 300, 500] Py : [100, 100, 100, 300, 300, 300, 500, 500, 500] * 物理坐标对应机械手末端在工件坐标系下的 mm 值 Qx : [0.0, 50.0, 100.0, 0.0, 50.0, 100.0, 0.0, 50.0, 100.0] Qy : [0.0, 0.0, 0.0, 50.0, 50.0, 50.0, 100.0, 100.0, 100.0] * 求解仿射变换矩阵 vector_to_hom_mat2d (Px, Py, Qx, Qy, HomMat2D) * 保存矩阵C# 侧用 read_tuple 或直接读文件解析 write_tuple (HomMat2D, calib_matrix.tup)逻辑说明vector_to_hom_mat2d内部做的是最小二乘输入两组点必须一一对应顺序不能乱。参数Px/Py是图像坐标Qx/Qy是物理坐标单位统一用 mm。输出HomMat2D是一个 6 元组顺序是[a, b, c, d, e, f]对应矩阵| a b c | | d e f | | 0 0 1 |C# 侧拿到这个矩阵后对任意像素点(px, py)做变换qx a * px b * py c qy d * px e * py f这就是视觉引导的全部数学。标定做完后面每次拍照得到像素坐标套这个公式就能算出机械手该走的位置。2.3 标定精度怎么验证重投影误差标定完不能直接上产线先做重投影验证。把 9 个标定点的像素坐标代回矩阵算出物理坐标和实际记录的物理坐标比看最大偏差。* 重投影验证 affine_trans_point_2d (HomMat2D, Px, Py, QxCheck, QyCheck) * 计算每个点的偏差 ErrX : QxCheck - Qx ErrY : QyCheck - Qy * 最大偏差 MaxErr : max([abs(ErrX), abs(ErrY)])一般要求最大偏差小于 0.05 mm如果超过 0.1 mm说明标定点采集有问题常见原因是机械手到位后没停稳就拍照或者标定板没贴平。这个验证步骤我一般会写进 C# 界面里点一下按钮就出误差表省得每次手动跑 Halcon。3. C# 上位机串口通讯与 Halcon 结果对接3.1 串口通讯的配置与收发框架工业机器人串口通讯最常见的参数是 9600 或 115200 波特率、8 数据位、1 停止位、无校验。C# 里用System.IO.Ports.SerialPort类但直接裸用容易翻车因为串口数据到达是异步的界面线程不能阻塞。下面是一个可复用的串口收发封装核心是独立接收线程 委托回调避免跨线程操作控件。using System; using System.IO.Ports; using System.Threading; public class RobotSerialPort { private SerialPort _port; private Thread _readThread; private bool _running; // 收到完整帧后触发的事件 public event Actionstring OnFrameReceived; public bool Open(string portName, int baudRate 115200) { try { _port new SerialPort(portName, baudRate, Parity.None, 8, StopBits.One); _port.ReadTimeout 500; _port.Open(); _running true; _readThread new Thread(ReadLoop) { IsBackground true }; _readThread.Start(); return true; } catch (Exception ex) { // 记录日志不要弹窗阻塞 Console.WriteLine(串口打开失败: ex.Message); return false; } } private void ReadLoop() { while (_running) { try { string frame _port.ReadLine(); // 按换行符分帧 OnFrameReceived?.Invoke(frame.Trim()); } catch (TimeoutException) { /* 正常继续循环 */ } catch (Exception ex) { Console.WriteLine(串口读取异常: ex.Message); break; } } } public void Send(string cmd) { if (_port ! null _port.IsOpen) { _port.WriteLine(cmd); // 自动追加换行符 } } public void Close() { _running false; _readThread?.Join(500); _port?.Close(); } }逻辑说明ReadLoop跑在后台线程ReadLine会阻塞直到收到换行符或超时。超时设 500 ms避免线程卡死。收到帧后通过OnFrameReceived事件抛给界面层界面层用Invoke更新控件。参数方面波特率必须和机器人控制柜设置一致常见的是 115200如果现场干扰大降到 9600 更稳但传输速度会慢。注意SerialPort.ReadLine在数据不完整时会一直等如果机器人发的帧没有换行符结尾需要改用ReadExisting加自定义分帧逻辑。我一般会在协议里强制要求每条指令以\r\n结尾。3.2 Halcon 结果在 C# 里的调用方式Halcon 和 C# 的对接有两种常见做法一是用 Halcon 的HDevEngine直接跑 hdev 脚本二是用halcondotnet.dll调算子。前者适合算法经常改的场景后者适合算法固定、追求性能的场景。我一般用第二种因为标定矩阵算一次就固定了没必要每次跑脚本。C# 里引用halcondotnet.dll后可以这样读标定矩阵并做坐标变换using HalconDotNet; public class CalibTransform { private HTuple _homMat; public void LoadMatrix(string path) { // 从文件读 6 元组 HOperatorSet.ReadTuple(path, out _homMat); } public (double x, double y) PixelToRobot(double px, double py) { HTuple qx, qy; HOperatorSet.AffineTransPoint2d(_homMat, px, py, out qx, out qy); return (qx.D, qy.D); } }逻辑说明ReadTuple读入之前 Halcon 保存的矩阵文件AffineTransPoint2d做单点变换。HTuple是 Halcon 的数据容器.D取 double 值。参数上_homMat必须是 6 元组顺序不能错。如果标定矩阵更新了重新LoadMatrix即可不需要重启程序。3.3 串口协议设计怎么让机器人听懂坐标视觉算出的坐标要发给机器人协议必须简单可靠。常见做法是 ASCII 字符串格式如MOV X123.45 Y67.89\r\n。机器人侧解析 X、Y 后执行移动。C# 侧发送前要做两件事一是坐标限幅超出机器人工作空间的值直接丢弃并报警二是加序号或校验和防止丢帧导致机器人收到错误坐标。public string BuildMoveCommand(double x, double y, int seq) { // 限幅假设工作空间 0~500mm if (x 0 || x 500 || y 0 || y 500) throw new ArgumentOutOfRangeException(坐标超出工作空间); // 格式MOV,序号,X,Y,校验和 string body $MOV,{seq},{x:F2},{y:F2}; byte checksum 0; foreach (char c in body) checksum ^ (byte)c; return ${body},{checksum:X2}\r\n; }逻辑说明seq是递增序号机器人侧可以判断是否丢帧。校验和用异或简单够用。坐标保留两位小数对应 0.01 mm 精度。发送频率不要超过 50 Hz否则串口缓冲区会溢出机器人侧也处理不过来。4. 界面美化与操作流程固化4.1 用 WinForms 还是 WPF产线场景的选型理由产线上位机界面第一要求是稳定第二是操作员能看懂。WinForms 开发快、控件成熟但美化费劲WPF 样式灵活但数据绑定和线程调度坑多。我一般选 WinForms 自定义绘制因为产线电脑配置通常不高WPF 的渲染开销在低端机上会拖慢响应。界面布局按操作流程分三块左侧是标定区9 个点的采集按钮、矩阵计算、误差显示中间是实时图像显示Halcon 的HWindowControl右侧是串口状态和手动发送区。操作员只需要按顺序点「采集第 1 点」到「采集第 9 点」然后点「计算标定」误差合格就点「下发矩阵」。4.2 标定流程的界面固化从手动到一键手动跑 Halcon 标定容易出错因为点顺序、坐标记录、矩阵保存都是人工操作。界面固化的核心是把这些步骤串成一个状态机。public enum CalibState { Idle, Collecting, // 正在采集 9 个点 Computing, // 计算矩阵 Verifying, // 验证误差 Ready // 标定完成可下发 } public class CalibWorkflow { private CalibState _state CalibState.Idle; private List(double px, double py, double qx, double qy) _points new(); public void StartCollect() { _points.Clear(); _state CalibState.Collecting; } public void AddPoint(double px, double py, double qx, double qy) { if (_state ! CalibState.Collecting) return; _points.Add((px, py, qx, qy)); if (_points.Count 9) _state CalibState.Computing; } public bool ComputeAndVerify(out double maxErr) { // 调用 Halcon 算子求解矩阵 // 重投影验证返回最大误差 maxErr 0.03; // 示例值 _state maxErr 0.05 ? CalibState.Ready : CalibState.Collecting; return _state CalibState.Ready; } }逻辑说明状态机保证操作顺序不能乱没采满 9 点不能计算误差不合格自动退回采集状态。参数maxErr阈值设 0.05 mm可以根据产线精度要求调整。这个类不涉及界面代码方便单元测试。4.3 界面美化的三个实用技巧第一用TableLayoutPanel做布局不要手动设坐标分辨率一变就乱。第二按钮状态和状态机绑定采集过程中禁用「计算」按钮避免误操作。第三Halcon 图像显示用HWindowControl设置KeepAspectRatio为 true防止图像拉伸变形。提示产线界面不要用花哨的渐变色和动画操作员盯着看 8 小时高对比度、大字体、明确的颜色状态绿正常红报警比好看重要。5. 避坑与排查9点标定和串口通讯的 5 个血泪教训5.1 标定误差大但重投影误差却很小现象标定完重投影误差 0.02 mm但实际抓件偏了 1 mm 以上。原因标定板和工件不在同一平面或者相机安装角度导致景深方向有偏差。解决标定板必须和工件表面等高如果工件有厚度标定板要垫到和工件上表面齐平。另外相机光轴尽量垂直于标定平面倾斜角度超过 5° 就会引入透视误差仿射变换无法补偿。5.2 串口能发不能收或者收到乱码现象C# 能发指令机器人动了但机器人回传的状态 C# 收不到或者收到一堆乱码。原因波特率不匹配、数据位/停止位设置错误或者机器人回传的帧没有换行符。解决先用串口调试助手确认机器人回传的原始字节看波特率是否正确。如果回传帧没有\r\n改用ReadExisting加超时分帧。乱码通常是波特率差一个档位比如 9600 设成了 19200。5.3 Halcon license 报错导致程序启动失败现象开发机上跑得好好的部署到产线电脑上提示 license 错误。原因Halcon 的 license 和网卡 MAC 地址或加密狗绑定换机器后 license 失效。解决部署前确认目标机器的 license 状态用 Halcon 的license_manager检查。如果用的是加密狗确保 USB 口供电正常。C# 程序里加 try-catch 捕获HOperatorExceptionlicense 失效时给明确提示不要直接崩溃。5.4 界面卡死图像不刷新现象点「采集」按钮后界面无响应Halcon 图像窗口不更新。原因Halcon 算子跑在 UI 线程耗时操作阻塞了消息循环。解决把标定计算和图像处理放到Task.Run或后台线程处理完用Invoke回 UI 线程更新显示。注意 Halcon 的HWindowControl不是线程安全的所有绘图操作必须在 UI 线程执行。5.5 机器人收到坐标但不动或者动错位置现象串口发送成功机器人也回了 OK但机械手不动或走到错误位置。原因坐标单位不一致C# 发的是 mm机器人期望的是脉冲数或者坐标系方向相反比如 C# 的 Y 轴向上机器人的 Y 轴向下。解决先手动发一个已知坐标观察机器人实际走到哪里反推单位和方向。常见做法是在 C# 侧加一个方向系数和单位换算系数配置化处理不要硬编码。6. 进阶把标定矩阵做成可配置、可追溯的工程化方案标定矩阵不是算一次就永远不变。相机松动、镜头更换、机械手维修后都需要重新标定。工程化做法是把矩阵和标定时间、操作员、误差值一起存进配置文件或数据库每次启动时加载最新版本同时保留历史版本用于追溯。我一般用 JSON 存标定记录C# 侧用System.Text.Json读写public class CalibRecord { public string Timestamp { get; set; } public string Operator { get; set; } public double[] Matrix { get; set; } // 6 元组 public double MaxError { get; set; } public string CameraSN { get; set; } } // 保存 var record new CalibRecord { Timestamp DateTime.Now.ToString(yyyy-MM-dd HH:mm:ss), Operator 张三, Matrix new double[] { 0.5, 0, 10, 0, 0.5, 20 }, MaxError 0.03, CameraSN CAM-001 }; string json JsonSerializer.Serialize(record); File.WriteAllText(calib_history.json, json);逻辑说明Matrix存 6 个 double对应 Halcon 的HomMat2D。CameraSN用于区分多相机场景。每次标定后追加一条记录而不是覆盖方便回溯。加载时取最新一条如果MaxError超过阈值就弹窗提醒重新标定。验证标定是否仍然有效的技巧在产线上放一个已知位置的标记点每天开机时拍一张用当前矩阵算物理坐标和记录值比偏差超过 0.1 mm 就触发重新标定。这个检查可以写成定时任务不需要操作员干预。最后说个我自己的习惯每次标定完除了存矩阵还会把 9 个点的原始像素坐标和物理坐标一起存下来。这样下次标定出问题可以对比两次的点数据快速判断是相机动了还是机械手动了。这个习惯帮我省过好几次排查时间。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

SpringBoot+Vue前后端分离婚庆伴娘伴郎预约系统设计与实现

SpringBoot+Vue前后端分离婚庆伴娘伴郎预约系统设计与实现

1. 项目概述:这是一个什么样的系统先把这个项目的定位说清楚。市面上提到"婚庆服务",大部分人的第一反应是婚纱摄影、婚宴酒店、司仪跟妆,但"伴娘伴郎"这一环长期处于非常尴尬的位置——新人需要找靠谱的伴娘伴郎&#x…

2026/10/9 4:13:37 阅读更多 →
RAG重排序模型bge-reranker-v2-m3本地部署实战:Docker+vLLM+ModelScope

RAG重排序模型bge-reranker-v2-m3本地部署实战:Docker+vLLM+ModelScope

简介:面向零基础开发者的 bge-reranker-v2-m3 重排序模型本地部署实战指南,以 Docker 容器化与 vLLM 推理框架为主线,系统讲解从环境准备、镜像配置到模型运行的完整流程。资源为 1 个 PDF 文件,压缩包整体约 1.12MB,内…

2026/10/9 4:13:37 阅读更多 →
ROS 2 wait_for_all_acked详解:DDS ACK机制与QoS可靠投递实战

ROS 2 wait_for_all_acked详解:DDS ACK机制与QoS可靠投递实战

做 ROS 2 开发这几年,有一类问题总能让我在深夜排查到怀疑人生:你明明 publish() 一条指令,返回值也正常,下游设备偏偏没反应。急停指令发出去、参数配置写回去、机械臂轨迹下发前要确认缓冲区已经就绪……这些“必须送达”的消息…

2026/10/10 6:20:54 阅读更多 →

最新新闻

模型蒸馏本质是知识迁移,不是简单压缩

模型蒸馏本质是知识迁移,不是简单压缩

1. 什么是模型蒸馏:不是“压缩”,而是“知识迁移”的精密工程刚接触“模型蒸馏”这个词时,我第一反应是——这不就是把大模型砍一砍、剪一剪,塞进小设备里跑起来吗?后来在某高校实验室带一个图像识别项目时&#xff0c…

2026/10/10 7:36:25 阅读更多 →
LABVIEW数据采集程序从架构到通信视觉集成的完整实战指南

LABVIEW数据采集程序从架构到通信视觉集成的完整实战指南

干LABVIEW数据采集这行快十年了,前阵子帮客户重写了一套产线终检台的采集程序,采集卡是PCIe-6321,八通道电压电流同步采,还要兼顾报表生成和数据库写入。这套系统看着常规,但真正把采集、处理、界面和通信全部串成一套…

2026/10/10 7:36:25 阅读更多 →
从Rust到GPU:Zed自研GPUI框架的性能密码

从Rust到GPU:Zed自研GPUI框架的性能密码

1. 为什么 Zed 非要自研一个 UI 框架我最早看到 Zed 的演示视频时,第一反应是“又一个吹性能的编辑器”。但真正在项目里用上之后,才理解它为什么敢把“极致性能”挂在核心产品上。很多人聊 Zed 时都会自然提到 Rust 和 GPU,却忽略了一个关键…

2026/10/10 7:36:25 阅读更多 →
SQL 慢就 force index?先问优化器为什么不听你的

SQL 慢就 force index?先问优化器为什么不听你的

force index 的正确打开方式,以及它的三个代价 遇到慢 SQL,很多人的第一反应是:用 force index 指定一个索引。写法简单,改完立刻见效,所以很受欢迎。但我的观点是:**force index 是“最后一招”&#xff0…

2026/10/10 7:36:25 阅读更多 →
TradingAgents vs AutoGen vs LangGraph:谁才是 AI 交易圈的顶配底座

TradingAgents vs AutoGen vs LangGraph:谁才是 AI 交易圈的顶配底座

TradingAgents vs AutoGen vs LangGraph:谁才是 AI 交易圈的顶配底座 【免费下载链接】TradingAgents-AI.github.io TradingAgents: Multi-Agents LLM Financial Trading Framework 项目地址: https://gitcode.com/GitHub_Trending/tr/TradingAgents-AI.github.io…

2026/10/10 7:36:25 阅读更多 →
多智能体协作实战:从提示词堆砌到团队化分工调度

多智能体协作实战:从提示词堆砌到团队化分工调度

做AI应用这些年,我越来越觉得“单智能体包打天下”这个思路在真实业务约束下并不可靠。最近我搭了一套内部代号叫agency-agents的模拟项目,核心就是让多个智能体像一个小团队一样分工协作。它解决的场景很典型:一次任务里既要做资料搜集&…

2026/10/10 7:35:25 阅读更多 →

日新闻

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

1. 从“卫星轨道分类”这个标题说起:为什么值得花时间搞懂第一次接触“卫星轨道分类”这个概念,很多人会觉得它离自己很远——不就是天上的星星怎么转吗?但如果你正在做航天任务规划、遥感数据接收、星座设计,甚至只是准备一场航天…

2026/10/10 0:00:39 阅读更多 →
Spring AOP 核心原理与实战:从概念到日志切面落地

Spring AOP 核心原理与实战:从概念到日志切面落地

1. 从一个真实痛点说起:为什么你的代码里到处都是重复逻辑刚入行那会儿,我写过一个用户管理模块,注册、登录、改密码、注销四个接口。每个接口里都塞了几乎一样的日志打印、参数校验、事务开启和提交。当时觉得没什么,能跑就行。直…

2026/10/10 0:00:40 阅读更多 →
Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

简介:这是一套面向计算机相关专业学生与项目实战学习者的Python数据采集与分析可视化完整项目,以Boss直聘岗位数据为对象,适合用作毕业设计、课程设计或期末大作业。资源包共38个文件,约246KB,以13个py源码文件为核心&…

2026/10/10 0:00:40 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

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

2026/10/8 15:26:32 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

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

2026/10/10 1:36:08 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

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

2026/10/9 10:11:06 阅读更多 →

月新闻

我发现了一个新思路:用 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/10 5:23:50 阅读更多 →
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/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练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/9 6:17:20 阅读更多 →