C#封装佳能EOS SDK:实时取景与拍照传输实战

简介:这份源码资源面向使用 C# 与 WinForms 开发相机控制程序的开发者,重点解决佳能相机拍照、实时图像传输等功能的实现问题,内含最新版 EOS SDK,可直接对接 EDSDK.Dll 完成设备通信与图像回传。资源兼容 EOS-1D Mark III、40D、5D Mark II、7D、60D、550D 等多款佳能单反机型,适合需要构建拍摄采集、远程控制或图像传输类桌面应用的中级开发者参考。压缩包共 1792 个文件,约 134.97MB,其中 1518 个 bin 与 104 个 dll 构成 SDK 运行库与依赖组件,18 个 cs 源码文件展示核心调用逻辑,另有 icc、icm 色彩配置文件及 csproj、sln 工程文件,便于直接编译调试。目前已有 1330 人学习下载。读者可从中获取完整的相机连接、拍照触发与实时传输实现思路,结合工程结构与 SDK 依赖快速搭建自己的采集程序,减少从零摸索 SDK 接口的时间成本。

1. 从一台吃灰的单反说起:这套 C# 源码到底能帮你省掉多少事

手里有台佳能单反,想用程序控制它拍照、实时取景、拍完立刻把照片传到电脑上,这事儿听起来简单,真动手就知道坑有多深。官方给的 EOS SDK 是一堆 C++ 头文件和动态库,文档写得像天书,示例代码还是 MFC 时代的风格。你要在 C# 项目里调它,得自己写 P/Invoke 封装,处理回调、内存释放、错误码映射,稍不留神就是内存泄漏或者相机卡死。

这套源码干的就是这件事:把佳能官方 EOS SDK 用 C# 完整封装了一遍,提供相机连接、实时取景、拍照、文件传输、参数设置这些核心功能的可运行示例。它适合两类人:一是做机器视觉、影楼联机拍摄、证件照采集这类需要程序控制相机的开发者;二是想学 P/Invoke 和硬件 SDK 封装套路的 C# 工程师。源码里带了最新版 EOS SDK 的引用,不用自己满世界找库文件。

2. 技术选型与工程结构:为什么是 C# 封装 EDSDK.Dll

2.1 托管代码调非托管 SDK 的必然选择

佳能 EOS SDK 本质是一组 C 风格的动态链接库,核心是EdsInitializeSDK、EdsOpenSession、EdsSendCommand这些导出函数。C# 要调它们,只有 P/Invoke 一条路。这套源码的做法是:把所有DllImport声明集中在一个静态类里,对外暴露符合 C# 习惯的托管接口。

常见做法是建一个EDSDK静态类放原生方法声明,再建一个Camera实例类包装会话生命周期。这样业务代码里看不到一个IntPtr,全是camera.TakePhoto()这种调用。

// EDSDK.cs - 原生方法声明集中管理 internal static class EDSDK { // 相机列表变化、属性变化等事件都通过这个回调进来 internal delegate uint EdsCameraEventCallback(uint event, IntPtr objectHandle, IntPtr context); [DllImport("EDSDK.dll", CallingConvention = CallingConvention.StdCall)] internal static extern uint EdsInitializeSDK(); [DllImport("EDSDK.dll", CallingConvention = CallingConvention.StdCall)] internal static extern uint EdsGetCameraList(out IntPtr cameraList); [DllImport("EDSDK.dll", CallingConvention = CallingConvention.StdCall)] internal static extern uint EdsOpenSession(IntPtr camera); [DllImport("EDSDK.dll", CallingConvention = CallingConvention.StdCall)] internal static extern uint EdsSendCommand(IntPtr camera, uint command, int param); [DllImport("EDSDK.dll", CallingConvention = CallingConvention.StdCall)] internal static extern uint EdsRelease(IntPtr obj); }

这里有几个关键点。CallingConvention.StdCall必须写对,佳能的库用的是标准调用约定,写成Cdecl会在某些机器上直接崩。返回值uint是 EDSDK 的错误码,EDS_ERR_OK等于 0,其他值都要查表翻译成可读信息。EdsRelease用来释放任何 EDSDK 返回的对象句柄,漏调就是内存泄漏。

2.2 工程目录与模块划分

源码的目录结构大致是这样的:

CanonEDSDKWrapper/ ├── EDSDK/ │ ├── EDSDK.cs # 原生方法声明 │ ├── EDSDKErrors.cs # 错误码枚举与翻译 │ ├── EDSDKStructs.cs # 结构体定义 │ └── EDSDKConstants.cs # 常量(属性ID、命令ID) ├── Camera/ │ ├── Camera.cs # 相机会话管理 │ ├── CameraEvent.cs # 事件回调处理 │ └── CameraProperty.cs # 参数读写 ├── LiveView/ │ └── LiveViewStream.cs # 实时取景流 ├── Transfer/ │ └── ImageDownloader.cs # 图片下载 └── Demo/ └── MainForm.cs # WinForms 示例界面

这个划分的逻辑是:EDSDK目录只做底层映射,不掺业务;Camera目录管会话和状态;LiveView和Transfer是两个独立的功能模块,因为实时取景和文件传输在 EDSDK 里走的是完全不同的 API 路径。示例用 WinForms 是因为实时取景需要把相机画面渲染到控件上,WinForms 的PictureBox改起来最直接。

2.3 会话生命周期:从连接相机到安全释放

相机操作的第一步是初始化 SDK、拿到相机列表、打开会话。这套流程必须严格按顺序来,顺序错了就是各种EDS_ERR_DEVICE_NOT_FOUND或者EDS_ERR_SESSION_ALREADY_OPEN。

public bool Connect() { // 1. 初始化 SDK,整个进程只需调一次 uint err = EDSDK.EdsInitializeSDK(); if (err != EDSDKErrors.EDS_ERR_OK) return false; // 2. 获取相机列表 IntPtr cameraList; err = EDSDK.EdsGetCameraList(out cameraList); if (err != EDSDKErrors.EDS_ERR_OK) return false; // 3. 取列表里第一台相机的句柄 int count; EDSDK.EdsGetChildCount(cameraList, out count); if (count == 0) return false; IntPtr camera; EDSDK.EdsGetChildAtIndex(cameraList, 0, out camera); // 4. 打开会话,之后所有操作都基于这个会话 err = EDSDK.EdsOpenSession(camera); if (err != EDSDKErrors.EDS_ERR_OK) return false; _cameraHandle = camera; // 5. 注册事件回调,相机状态变化会通知到这里 EDSDK.EdsSetCameraStateEventHandler(camera, EDSDKConstants.StateEvent_All, _eventCallback, IntPtr.Zero); return true; }

参数说明:EdsGetChildCount拿到的数量通常是 1,除非你同时插了多台佳能相机。EdsSetCameraStateEventHandler的第二个参数StateEvent_All表示订阅所有状态事件,包括快门按下、属性变化、对象添加等。回调函数里不能做耗时操作,否则会阻塞相机通信线程,常见做法是往一个队列里丢消息,主线程轮询处理。

释放的顺序和连接完全相反:先关会话EdsCloseSession,再释放相机句柄EdsRelease,最后EdsTerminateSDK。漏掉任何一步,下次连接就可能失败,这是血泪经验。

3. 核心功能拆解:实时取景、拍照与文件传输的完整链路

3.1 实时取景:从相机传感器到 PictureBox

实时取景是这套源码里最有价值的部分。EDSDK 的实时取景不是推流,而是你主动去“拉”每一帧。流程是:先设置Evf_OutputDevice属性把取景画面输出到电脑,然后设置Evf_Mode为 1 开启实时取景,之后循环调用EdsCreateEvfImageRef和EdsGetPointer拿到 JPEG 数据。

public void StartLiveView() { // 开启实时取景模式 EDSDK.EdsSetPropertyData(_cameraHandle, EDSDKConstants.PropID_Evf_Mode, 0, 4, BitConverter.GetBytes(1)); // 指定输出到电脑 EDSDK.EdsSetPropertyData(_cameraHandle, EDSDKConstants.PropID_Evf_OutputDevice, 0, 4, BitConverter.GetBytes(EDSDKConstants.EvfOutputDevice_PC)); // 定时器每 33ms 拉一帧,约 30fps _timer = new Timer(PullFrame, null, 0, 33); } private void PullFrame(object state) { IntPtr evfImage; // 从相机取当前取景帧 uint err = EDSDK.EdsCreateEvfImageRef(_cameraHandle, out evfImage); if (err != EDSDKErrors.EDS_ERR_OK) return; // 下载帧数据到本地内存 err = EDSDK.EdsDownloadEvfImage(_cameraHandle, evfImage); if (err != EDSDKErrors.EDS_ERR_OK) { EDSDK.EdsRelease(evfImage); return; } // 拿到 JPEG 数据指针和长度 IntPtr stream; EDSDK.EdsGetPointer(evfImage, out stream); // 这里把 stream 转成 Bitmap 显示到 PictureBox // 注意:用完必须 EdsRelease(evfImage) EDSDK.EdsRelease(evfImage); }

关键参数:PropID_Evf_OutputDevice的值EvfOutputDevice_PC是 2,表示输出到电脑。EdsDownloadEvfImage是阻塞调用,相机没准备好会返回EDS_ERR_OBJECT_NOTREADY,这时候直接跳过这一帧就行,不要当错误处理。帧率由定时器间隔决定,33ms 对应约 30fps,设太小相机会跟不上,设太大画面卡顿。

注意:实时取景开启时相机会发热,长时间使用建议加个散热风扇。另外不是所有佳能机型都支持实时取景,老款入门机可能没有这个功能。

3.2 拍照与文件传输:两种模式的选择

拍照有两种方式:一种是EdsSendCommand发CameraCommand_TakePicture,相机拍完存到卡里,你再通过EdsDownload下载;另一种是设置SaveTo为电脑,拍完直接回调通知你下载。源码里两种都实现了,用哪种看场景。

public void TakePicture(bool saveToHost) { if (saveToHost) { // 拍完存电脑,通过对象事件回调通知 EDSDK.EdsSetPropertyData(_cameraHandle, EDSDKConstants.PropID_SaveTo, 0, 4, BitConverter.GetBytes(EDSDKConstants.SaveTo_Host)); } // 发送拍照命令 uint err = EDSDK.EdsSendCommand(_cameraHandle, EDSDKConstants.CameraCommand_TakePicture, 0); if (err != EDSDKErrors.EDS_ERR_OK) { // 常见错误:对焦失败、卡满、相机忙 throw new Exception(EDSDKErrors.GetErrorText(err)); } }

SaveTo_Host模式下,相机会在拍照完成后触发StateEvent_ObjectAdded事件,事件参数里带着新对象的句柄。你在回调里拿到这个句柄,调EdsGetDirectoryItemInfo获取文件名和大小,再调EdsDownload下载到本地。

private uint OnCameraEvent(uint eventType, IntPtr objectHandle, IntPtr context) { if (eventType == EDSDKConstants.StateEvent_ObjectAdded) { // 新照片对象来了,取信息 EDSDK.EdsDirectoryItemInfo info; EDSDK.EdsGetDirectoryItemInfo(objectHandle, out info); // 下载到本地 IntPtr stream; EDSDK.EdsCreateFileStream( Path.Combine(_saveDir, info.szFileName), EDSDKConstants.FileCreate_ForWrite, out stream); EDSDK.EdsDownload(objectHandle, info.size, stream); EDSDK.EdsDownloadComplete(objectHandle); // 释放资源 EDSDK.EdsRelease(stream); EDSDK.EdsRelease(objectHandle); } return EDSDKErrors.EDS_ERR_OK; }

这里有个容易翻车的地方:EdsDownload之后必须调EdsDownloadComplete,否则相机会认为文件还在传输中,后续操作全部阻塞。另外回调函数运行在 EDSDK 的内部线程上,直接在里面更新 UI 会抛跨线程异常,标准做法是用Control.Invoke切回主线程。

3.3 参数读写:光圈、快门、ISO 的远程控制

相机参数通过EdsSetPropertyData和EdsGetPropertyData读写,每个参数有对应的 PropID。光圈是PropID_Av,快门是PropID_Tv,ISO 是PropID_ISOSpeed。值不是直接的 f 值或秒数,而是 EDSDK 定义的编码值,需要转换。

public void SetAperture(int avValue) { // avValue 是 EDSDK 编码值,比如 f/2.8 对应 0x50 byte[] data = BitConverter.GetBytes(avValue); uint err = EDSDK.EdsSetPropertyData(_cameraHandle, EDSDKConstants.PropID_Av, 0, 4, data); } public int GetAperture() { byte[] data = new byte[4]; uint err = EDSDK.EdsGetPropertyData(_cameraHandle, EDSDKConstants.PropID_Av, 0, 4, data); return BitConverter.ToInt32(data, 0); }

实际项目里一般会先调EdsGetPropertyDesc拿到该参数所有可用值的列表,然后在 UI 上做成下拉框让用户选,而不是硬编码。不同机型的可用光圈范围不一样,硬编码必然出问题。

4. 避坑与排查:相机连不上、取景黑屏、传输卡死的真实原因

4.1 相机连不上,错误码 0x20 或 0x81

现象是EdsOpenSession返回非零,相机列表能拿到但会话打不开。原因通常是相机被其他程序占用了,比如佳能官方 Utility 还开着,或者上一次程序异常退出没释放会话。解决办法是先关掉所有可能占用相机的软件,然后重启相机。如果还不行,在代码里加一个EdsCloseSession的兜底调用,程序退出时强制释放。

4.2 实时取景黑屏,但没报错

现象是EdsDownloadEvfImage返回 OK,但画面全黑。原因多半是相机模式不对——实时取景只在拍照模式(M/Av/Tv/P)下有效,如果相机拨盘在视频模式或者 AUTO 模式,取景数据就是黑的。解决方法是先通过EdsSetPropertyData把PropID_AEMode设成AEMode_Manual,再开实时取景。

4.3 拍照后文件传输卡死

现象是EdsDownload一直不返回,程序无响应。原因是EdsDownload是同步阻塞的,大文件(比如 RAW)传输时间很长,如果在 UI 线程调用就会卡界面。解决办法是把下载放到后台线程,并且设置EdsSetCapacity确保电脑端有足够空间。另外传输过程中不要调其他 EDSDK 函数,相机会忙不过来。

4.4 程序退出后相机假死

现象是程序关了,但相机屏幕还显示“忙碌”,重新插拔 USB 才能恢复。原因是会话没正常关闭,相机还在等待电脑的指令。解决办法是在FormClosing事件里按顺序调EdsCloseSession、EdsRelease、EdsTerminateSDK,一个都不能少。我一般还会加个try-finally确保异常时也能释放。

4.5 多台相机同时连接时句柄混乱

现象是插了两台相机,程序只认到一台,或者操作 A 相机影响了 B 相机。原因是EdsGetChildAtIndex的索引没管理好,而且事件回调里没区分是哪个相机发来的。解决办法是给每个相机句柄建一个字典映射,回调里通过context参数区分来源。这套源码的示例只处理单相机,多相机场景需要自己扩展。

5. 二次开发改造:从示例到生产项目的几个关键调整

拿到这套源码跑通之后,真正用到项目里还得做几处改造。第一处是错误处理。示例里很多地方直接return false或者抛异常,生产环境需要把 EDSDK 错误码翻译成用户能看懂的中文提示,并且记录日志。我一般会建一个EdsErrorTranslator类,把常见错误码映射成“相机未连接”“存储卡已满”“对焦失败”这类提示。

第二处是实时取景的性能优化。示例用System.Windows.Forms.Timer拉帧,在低配电脑上会掉帧。改成独立的后台线程加AutoResetEvent控制节奏,帧率能稳定不少。另外每帧创建的Bitmap对象必须及时Dispose,否则内存涨得飞快,这是最容易翻车的地方。

// 优化后的取景循环:后台线程 + 对象复用 private void LiveViewLoop() { while (_isRunning) { _frameReady.WaitOne(33); IntPtr evfImage; if (EDSDK.EdsCreateEvfImageRef(_cameraHandle, out evfImage) != EDSDKErrors.EDS_ERR_OK) continue; if (EDSDK.EdsDownloadEvfImage(_cameraHandle, evfImage) == EDSDKErrors.EDS_ERR_OK) { // 复用同一个 Bitmap 对象,避免频繁分配 UpdateFrame(evfImage); } EDSDK.EdsRelease(evfImage); } }

第三处是断线重连。USB 线松动或者相机休眠都会导致会话断开,示例里没有处理。生产项目需要监听StateEvent_WillSoonShutDown和StateEvent_ShutDown事件,在断开时自动尝试重连,并且把当前参数设置恢复到重连后的会话上。

改造点示例做法生产建议
错误处理返回 bool错误码翻译 + 日志
取景线程WinForms Timer后台线程 + 对象复用
断线处理无事件监听 + 自动重连
多相机只支持单台句柄字典 + 上下文区分
参数设置硬编码值动态获取可用值列表

最后说个我自己的习惯:每次改完 EDSDK 相关的代码,我都会先用一台相机做完整的“连接-取景-拍照-传输-断开”流程测试,确认没有资源泄漏再提交。因为 EDSDK 的问题往往不是立刻暴露的,而是跑了几十次之后才出现句柄耗尽。从那以后我每次动相机相关的代码都强制走一遍完整流程,希望帮到你。

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

相关新闻

LangChain实战:构建RAG知识库问答系统全流程

LangChain实战:构建RAG知识库问答系统全流程

让一个通用大模型老老实实只在指定资料里找答案,这件事我从一开始做就翻过车。当时还是直接把问题丢给LLM,让它基于一份内部产品手册回答客户问题,结果它发挥得很愉快,给出的答案里一半是手册里根本没有的内容,另一半年…

2026/10/20 20:15:40 阅读更多 →
播客内容自动化矩阵:OpenClaw+Whisper+Claude实战指南

播客内容自动化矩阵:OpenClaw+Whisper+Claude实战指南

做播客的朋友应该都有这种体会:一期节目录完、剪完、发出去,好像就结束了。认真一点的人会自己写shownotes,剪两条短视频,再发一篇图文笔记,配一张封面图。听起来也不复杂,但真正做起来,每期至少…

2026/10/20 20:15:40 阅读更多 →
图像变形与实时渲染:哈哈镜从反向映射到GPU Shader实现

图像变形与实时渲染:哈哈镜从反向映射到GPU Shader实现

做了这么多年图像处理相关的开发,我始终觉得“哈哈镜”是个特别有意思的功能。它不像磨皮、美白那样追求“无痕”,反而要刻意把脸弄变形,逗人一笑。但在美颜SDK里,哈哈镜又是很考验基本功的功能,因为它要同时搞定图像几…

2026/10/20 20:15:40 阅读更多 →

最新新闻

TensorFlow鞋类识别实战:Fashion MNIST图像分类全流程

TensorFlow鞋类识别实战:Fashion MNIST图像分类全流程

前段时间重新整理之前的深度学习实验笔记,翻到鞋类识别(Fashion MNIST)这个项目时,觉得特别值得拿出来单独写一篇。原因很简单:很多人入门深度学习时,都会被建议先跑通手写数字识别(MNIST&#…

2026/10/20 20:57:37 阅读更多 →
用AI辅助Kamailio路由排错:一场人机讨论实录与故障切换实践

用AI辅助Kamailio路由排错:一场人机讨论实录与故障切换实践

最近接手一套老的SIP通信平台,核心信令是Kamailio撑着。业务要新增一个较复杂的路由策略:同一个主叫号码,在不同时间段、针对不同被叫前缀,要分别走不同的网关;而其中一路网关做故障切换时,还要保证已经建立…

2026/10/20 20:57:37 阅读更多 →
LibVLC轻量级视频播放模块:嵌入C/C++工程的硬件解码方案

LibVLC轻量级视频播放模块:嵌入C/C++工程的硬件解码方案

简介:本资源是一份基于C/C与VLC开源库开发Windows视频播放器的完整工程实践包,面向具备基础C编程能力、希望掌握多媒体开发技能的中级开发者及高校课程设计学习者。资源聚焦于在Visual Studio 2022环境下集成libvlc实现视频加载、播放控制、窗口渲染等核…

2026/10/20 20:57:37 阅读更多 →
AI Agent创业团队如何配置一支高效的Harness Engineering团队

AI Agent创业团队如何配置一支高效的Harness Engineering团队

1. 为什么需要一支专门的Harness Engineering团队先从一个很现实的场景说起:你所在的创业团队花三个月做出了一个基于大模型的Agent产品Demo,演示给投资人看时效果炸裂,但真正进入客户试用阶段,问题接踵而来——模型给出正确意图却…

2026/10/20 20:57:37 阅读更多 →
视觉语言模型与RGB-D感知融合:三条进化主线与工程实践

视觉语言模型与RGB-D感知融合:三条进化主线与工程实践

1. 从 Transformer 到多模态感知的演进逻辑视觉语言模型和 RGB-D 感知这两个方向,过去几年基本是各走各的路。做 VLM 的人盯着图文对齐、指令跟随、视觉问答这些任务,做 RGB-D 的人则埋头于点云配准、深度补全、SLAM 建图。但最近一年,这两条…

2026/10/20 20:57:37 阅读更多 →
VTI介质地震波数值模拟:MATLAB实现与各向异性正演避坑指南

VTI介质地震波数值模拟:MATLAB实现与各向异性正演避坑指南

简介:这份资源面向地震勘探与地球物理方向的学习者和研究者,提供一套基于Matlab的VTI介质地震波场数值模拟程序,用于分析各向异性介质中地震波的传播规律。VTI介质在垂直与平行层理方向上纵波速度存在差异,是实际地壳中常见的各向…

2026/10/20 20:56:32 阅读更多 →

日新闻

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实现详解

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

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

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

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

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

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

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在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 阅读更多 →