Python图像批量处理实战:5张图片高效优化与自动化方案
最近在图像处理项目中经常遇到需要批量处理多张图片的场景。无论是电商平台的商品图优化还是社交媒体的内容生成高效处理5张图像的组合任务都是很常见的需求。本文将分享一套完整的图像处理实战方案从环境搭建到高级功能实现帮助开发者快速掌握多图像批处理的核心技能。1. 图像处理基础与环境准备1.1 图像处理的核心概念图像处理是指通过算法对数字图像进行分析、增强、压缩或转换的技术过程。在实际项目中我们通常需要处理以下基本操作尺寸调整、格式转换、色彩校正、滤镜应用等。对于5张图像的批量处理关键在于建立可重复的流水线操作确保每张图像都能获得一致的处理效果。1.2 环境配置与工具选择推荐使用Python作为主要开发语言配合OpenCV、PIL等成熟库来实现图像处理功能。以下是基础环境要求Python 3.8及以上版本OpenCV 4.5 用于核心图像操作Pillow 9.0 作为图像处理辅助库NumPy 1.21 用于数值计算安装依赖包的命令如下pip install opencv-python pillow numpy验证安装是否成功import cv2 import PIL print(fOpenCV版本: {cv2.__version__}) print(fPillow版本: {PIL.__version__})2. 基础图像操作实战2.1 图像读取与基本信息获取处理5张图像的第一步是正确读取文件并了解图像的基本属性。以下代码演示如何批量读取图像并获取关键信息import cv2 import os def load_images(folder_path): 批量加载文件夹中的所有图像 images [] valid_extensions (.jpg, .jpeg, .png, .bmp, .tiff) for filename in os.listdir(folder_path): if filename.lower().endswith(valid_extensions): img_path os.path.join(folder_path, filename) img cv2.imread(img_path) if img is not None: images.append({ name: filename, data: img, height: img.shape[0], width: img.shape[1], channels: img.shape[2] if len(img.shape) 2 else 1 }) print(f成功加载: {filename}, 尺寸: {img.shape}) else: print(f加载失败: {filename}) return images # 使用示例 image_folder project_images image_list load_images(image_folder)2.2 图像尺寸统一化处理当处理5张尺寸各异的图像时通常需要将它们统一到相同尺寸以便后续处理。以下是智能尺寸调整的实现def resize_images(images, target_size(800, 600), keep_aspect_ratioTrue): 批量调整图像尺寸支持保持宽高比 processed_images [] for img_info in images: original_img img_info[data] original_height, original_width original_img.shape[:2] target_width, target_height target_size if keep_aspect_ratio: # 计算保持宽高比的缩放比例 scale min(target_width/original_width, target_height/original_height) new_width int(original_width * scale) new_height int(original_height * scale) else: new_width, new_height target_size # 使用INTER_AREA插值方法适合缩小图像 resized_img cv2.resize(original_img, (new_width, new_height), interpolationcv2.INTER_AREA) # 如果需要填充到精确尺寸 if keep_aspect_ratio and (new_width ! target_width or new_height ! target_height): # 创建目标尺寸的黑色背景 final_img np.zeros((target_height, target_width, 3), dtypenp.uint8) # 计算居中位置 y_offset (target_height - new_height) // 2 x_offset (target_width - new_width) // 2 final_img[y_offset:y_offsetnew_height, x_offset:x_offsetnew_width] resized_img else: final_img resized_img img_info[resized_data] final_img processed_images.append(img_info) return processed_images # 应用尺寸调整 target_size (800, 600) resized_images resize_images(image_list, target_size)3. 图像质量增强技术3.1 自动亮度与对比度优化5张图像可能是在不同光照条件下拍摄的需要统一优化亮度和对比度def enhance_brightness_contrast(image, brightness_factor1.2, contrast_factor1.5): 增强图像亮度和对比度 # 转换到YUV色彩空间 yuv_image cv2.cvtColor(image, cv2.COLOR_BGR2YUV) # 分离Y通道亮度 y_channel yuv_image[:,:,0] # 应用亮度和对比度调整 enhanced_y cv2.convertScaleAbs(y_channel, alphacontrast_factor, betabrightness_factor*50) # 合并通道 yuv_image[:,:,0] np.clip(enhanced_y, 0, 255) enhanced_image cv2.cvtColor(yuv_image, cv2.COLOR_YUV2BGR) return enhanced_image def batch_enhancement(images): 批量增强图像质量 enhanced_images [] for img_info in images: enhanced_img enhance_brightness_contrast(img_info[resized_data]) img_info[enhanced_data] enhanced_img enhanced_images.append(img_info) return enhanced_images # 执行批量增强 enhanced_images batch_enhancement(resized_images)3.2 噪声去除与锐化处理图像噪声会影响后续处理效果以下是综合去噪和锐化方案def denoise_and_sharpen(image, denoise_strength10, sharpen_strength1.0): 去噪与锐化组合处理 # 非典噪点去除 denoised cv2.fastNlMeansDenoisingColored(image, None, denoise_strength, denoise_strength, 7, 21) # 锐化处理 kernel np.array([[-1,-1,-1], [-1, 9,-1], [-1,-1,-1]]) * sharpen_strength sharpened cv2.filter2D(denoised, -1, kernel) return sharpened # 批量应用去噪锐化 for img_info in enhanced_images: img_info[processed_data] denoise_and_sharpen(img_info[enhanced_data])4. 高级图像处理功能4.1 批量水印添加为5张图像添加统一水印是常见需求以下是可定制的水印方案def add_watermark_batch(images, watermark_textSample Watermark, position(50, 50), opacity0.6): 批量添加文字水印 watermarked_images [] for img_info in images: img img_info[processed_data].copy() height, width img.shape[:2] # 计算水印位置支持相对位置 if isinstance(position[0], str) and position[0].endswith(%): x_pos int(width * int(position[0][:-1]) / 100) y_pos int(height * int(position[1][:-1]) / 100) else: x_pos, y_pos position # 创建水印文本 font cv2.FONT_HERSHEY_SIMPLEX font_scale min(width, height) / 1000 # 自适应字体大小 thickness max(1, int(font_scale * 2)) # 获取文本尺寸以添加背景 text_size cv2.getTextSize(watermark_text, font, font_scale, thickness)[0] # 添加半透明背景 bg_top_left (x_pos - 10, y_pos - text_size[1] - 10) bg_bottom_right (x_pos text_size[0] 10, y_pos 10) overlay img.copy() cv2.rectangle(overlay, bg_top_left, bg_bottom_right, (0,0,0), -1) cv2.addWeighted(overlay, opacity, img, 1 - opacity, 0, img) # 添加文字 cv2.putText(img, watermark_text, (x_pos, y_pos), font, font_scale, (255,255,255), thickness) img_info[watermarked_data] img watermarked_images.append(img_info) return watermarked_images # 添加水印示例 final_images add_watermark_batch(enhanced_images, Confidential, (90%, 90%))4.2 格式转换与批量保存处理完成后需要将5张图像统一保存为指定格式def save_processed_images(images, output_folder, formatJPEG, quality95): 批量保存处理后的图像 if not os.path.exists(output_folder): os.makedirs(output_folder) save_results [] for img_info in images: filename img_info[name] # 修改文件扩展名 name_without_ext os.path.splitext(filename)[0] output_filename f{name_without_ext}_processed.{format.lower()} output_path os.path.join(output_folder, output_filename) # 根据格式选择保存参数 if format.upper() JPEG: cv2.imwrite(output_path, img_info[watermarked_data], [cv2.IMWRITE_JPEG_QUALITY, quality]) elif format.upper() PNG: cv2.imwrite(output_path, img_info[watermarked_data], [cv2.IMWRITE_PNG_COMPRESSION, 9]) else: cv2.imwrite(output_path, img_info[watermarked_data]) save_results.append({ original_name: filename, saved_path: output_path, file_size: os.path.getsize(output_path) }) print(f已保存: {output_path}) return save_results # 保存所有处理后的图像 output_dir processed_results save_info save_processed_images(final_images, output_dir, formatJPEG, quality85)5. 性能优化与批量处理技巧5.1 多线程并行处理当处理大量图像或高分辨率文件时使用多线程可以显著提升效率import concurrent.futures from functools import partial def process_single_image(args): 处理单张图像的完整流程 filepath, target_size, watermark_text args img cv2.imread(filepath) if img is None: return None # 执行所有处理步骤 img_resized resize_images([{data: img}], target_size)[0][resized_data] img_enhanced enhance_brightness_contrast(img_resized) img_processed denoise_and_sharpen(img_enhanced) img_watermarked add_watermark_batch([{processed_data: img_processed}], watermark_text)[0][watermarked_data] return img_watermarked def parallel_process_images(image_paths, target_size(800,600), watermark_textWatermark): 并行处理多张图像 with concurrent.futures.ThreadPoolExecutor(max_workers4) as executor: # 准备参数 process_args [(path, target_size, watermark_text) for path in image_paths] # 提交任务 future_to_path {executor.submit(process_single_image, args): args[0] for args in process_args} results {} for future in concurrent.futures.as_completed(future_to_path): path future_to_path[future] try: results[path] future.result() except Exception as e: print(f处理失败 {path}: {e}) results[path] None return results # 使用并行处理 image_paths [os.path.join(project_images, f) for f in os.listdir(project_images) if f.lower().endswith((.jpg, .png))] parallel_results parallel_process_images(image_paths[:5]) # 处理前5张5.2 内存优化与流式处理对于大尺寸图像内存管理至关重要class ImageBatchProcessor: 支持流式处理的图像批处理器 def __init__(self, max_memory_mb500): self.max_memory max_memory_mb * 1024 * 1024 # 转换为字节 self.processed_count 0 def estimate_memory_usage(self, image_paths): 预估内存使用量 total_size 0 for path in image_paths: if os.path.exists(path): total_size os.path.getsize(path) * 3 # 粗略估计解码后大小 return total_size def process_in_batches(self, image_paths, batch_size3): 分批处理图像以避免内存溢出 all_results {} for i in range(0, len(image_paths), batch_size): batch_paths image_paths[i:ibatch_size] print(f处理批次 {i//batch_size 1}: {len(batch_paths)} 张图像) # 检查内存使用 if self.estimate_memory_usage(batch_paths) self.max_memory: print(警告: 批次内存需求超过限制减小批次大小) batch_size max(1, batch_size // 2) continue batch_results parallel_process_images(batch_paths) all_results.update(batch_results) self.processed_count len(batch_paths) # 模拟内存清理 import gc gc.collect() return all_results # 使用流式处理器 processor ImageBatchProcessor(max_memory_mb200) batch_results processor.process_in_batches(image_paths, batch_size2)6. 常见问题与解决方案6.1 图像加载失败排查在处理5张图像时经常遇到文件无法读取的问题def diagnose_image_issues(filepath): 诊断图像文件问题 issues [] # 检查文件是否存在 if not os.path.exists(filepath): issues.append(文件不存在) return issues # 检查文件权限 if not os.access(filepath, os.R_OK): issues.append(文件读取权限不足) # 检查文件大小 file_size os.path.getsize(filepath) if file_size 0: issues.append(文件大小为0可能已损坏) # 尝试多种方式读取 try: img_pil PIL.Image.open(filepath) img_pil.verify() # 验证文件完整性 except Exception as e: issues.append(fPIL验证失败: {e}) try: img_cv cv2.imread(filepath) if img_cv is None: issues.append(OpenCV无法解码图像) except Exception as e: issues.append(fOpenCV读取失败: {e}) return issues # 批量诊断函数 def batch_diagnose(image_folder): 批量诊断文件夹中的图像文件 for filename in os.listdir(image_folder): if filename.lower().endswith((.jpg, .png, .jpeg)): filepath os.path.join(image_folder, filename) issues diagnose_image_issues(filepath) if issues: print(f问题文件: {filename}) for issue in issues: print(f - {issue}) else: print(f正常文件: {filename}) # 执行诊断 batch_diagnose(project_images)6.2 处理质量不一致问题5张图像处理结果不一致的常见原因和解决方案问题现象可能原因解决方案色彩差异大原始图像色温不同使用自动白平衡校正尺寸不统一原始比例差异大采用保持宽高比的缩放策略水印位置偏移图像分辨率不同使用百分比定位而非绝对坐标处理速度慢图像尺寸过大先缩放到合理尺寸再处理def auto_white_balance(image): 自动白平衡校正 # 使用灰度世界算法 result cv2.cvtColor(image, cv2.COLOR_BGR2LAB) avg_a np.average(result[:, :, 1]) avg_b np.average(result[:, :, 2]) result[:, :, 1] result[:, :, 1] - ((avg_a - 128) * (result[:, :, 0] / 255.0) * 1.1) result[:, :, 2] result[:, :, 2] - ((avg_b - 128) * (result[:, :, 0] / 255.0) * 1.1) result cv2.cvtColor(result, cv2.COLOR_LAB2BGR) return result7. 工程最佳实践7.1 配置文件管理将处理参数外部化便于调整和复用import json import yaml class ImageProcessingConfig: 图像处理配置管理 def __init__(self, config_pathNone): self.default_config { resize: { target_width: 800, target_height: 600, keep_aspect_ratio: True }, enhancement: { brightness_factor: 1.2, contrast_factor: 1.5 }, watermark: { text: Processed Image, position: [90%, 90%], opacity: 0.6 }, output: { format: JPEG, quality: 85 } } if config_path and os.path.exists(config_path): self.load_config(config_path) else: self.config self.default_config def load_config(self, config_path): 从文件加载配置 with open(config_path, r, encodingutf-8) as f: if config_path.endswith(.json): self.config json.load(f) elif config_path.endswith(.yaml) or config_path.endswith(.yml): self.config yaml.safe_load(f) def save_config(self, config_path): 保存配置到文件 with open(config_path, w, encodingutf-8) as f: if config_path.endswith(.json): json.dump(self.config, f, indent2) elif config_path.endswith(.yaml) or config_path.endswith(.yml): yaml.dump(self.config, f, default_flow_styleFalse) # 使用配置管理 config ImageProcessingConfig(image_config.yaml)7.2 日志记录与错误处理完善的日志系统对于批量处理至关重要import logging from datetime import datetime def setup_logging(log_fileimage_processing.log): 设置日志系统 logger logging.getLogger(ImageProcessor) logger.setLevel(logging.INFO) # 避免重复添加handler if not logger.handlers: # 文件handler file_handler logging.FileHandler(log_file, encodingutf-8) file_handler.setLevel(logging.INFO) # 控制台handler console_handler logging.StreamHandler() console_handler.setLevel(logging.WARNING) # 格式设置 formatter logging.Formatter( %(asctime)s - %(name)s - %(levelname)s - %(message)s ) file_handler.setFormatter(formatter) console_handler.setFormatter(formatter) logger.addHandler(file_handler) logger.addHandler(console_handler) return logger # 增强的错误处理装饰器 def error_handler(func): 统一错误处理装饰器 def wrapper(*args, **kwargs): logger setup_logging() try: start_time datetime.now() result func(*args, **kwargs) duration (datetime.now() - start_time).total_seconds() logger.info(f{func.__name__} 执行成功耗时: {duration:.2f}秒) return result except Exception as e: logger.error(f{func.__name__} 执行失败: {str(e)}, exc_infoTrue) raise return wrapper error_handler def safe_image_processing(image_path, config): 带错误保护的图像处理函数 # 处理逻辑... pass通过这套完整的图像处理方案你可以高效地处理5张或更多图像的批量任务。关键是要建立可重复的流程、完善的错误处理和性能优化机制。在实际项目中根据具体需求调整参数和流程逐步构建适合自己业务场景的图像处理流水线。

相关新闻

基于SpringBoot的B/S架构票务管理系统设计与实现

基于SpringBoot的B/S架构票务管理系统设计与实现

1. 项目背景与需求分析 在数字化浪潮席卷各行各业的今天,传统票务管理方式正面临前所未有的挑战。纸质票务系统不仅效率低下、容易出错,更难以应对现代商业场景中复杂的票务需求。基于B/S架构的线上票务管理系统应运而生,它通过浏览器即可完成…

2026/10/13 10:33:33 阅读更多 →
大模型技术全景:从Transformer到产业应用

大模型技术全景:从Transformer到产业应用

1. 大模型技术全景解析:从AI演进到产业落地最近两年,大语言模型(LLM)的爆发式发展正在重塑整个科技行业。作为从业者,我见证了从GPT-3到Claude、Llama等模型的迭代过程,也深刻体会到这项技术对开发者和普通…

2026/10/14 7:22:00 阅读更多 →
PlantUML+EA描述《分析模式》第6章存货和会计(8)

PlantUML+EA描述《分析模式》第6章存货和会计(8)

《GJJ-004 分析模式及实现》&#xff0c;umlchina.com/url/video.html 原图6.25 EA绘制 图6.29 对应账户。 PlantUML startuml skinparam nodesep 120 skinparam ranksep 100 class 账户 { -- <i>constraints</i> {传递: 对应账户s 对应账户s->closure(对…

2026/10/10 22:43:41 阅读更多 →

最新新闻

8款AI工具助力本科论文写作全流程

8款AI工具助力本科论文写作全流程

1. 项目概述作为一名经历过本科论文写作的过来人&#xff0c;我深知选题难、资料少、时间紧是困扰大多数学生的三大难题。去年帮导师做AI工具测评时&#xff0c;意外发现了一批对论文写作帮助极大的学术平台&#xff0c;经过半年实测&#xff0c;筛选出8个真正能提升效率的AI辅…

2026/10/14 7:24:38 阅读更多 →
GPU利用率从12%到80%:深度学习推理性能优化实战

GPU利用率从12%到80%:深度学习推理性能优化实战

1. 事故现场还原&#xff1a;一个让所有算法工程师血压飙升的夜晚1.1 从“训练快得飞起”到“推理慢如蜗牛”项目上线前的最后一个晚上&#xff0c;整个团队都在做最后的压测。训练阶段一切顺利&#xff0c;模型在验证集上的指标漂亮得让人想发朋友圈。但当我们把模型部署到推理…

2026/10/14 7:24:38 阅读更多 →
OpenClaw跨平台部署实战:Windows/macOS/Linux完整环境搭建指南

OpenClaw跨平台部署实战:Windows/macOS/Linux完整环境搭建指南

OpenClaw 这个框架&#xff0c;我第一次接触是在一次内部自动化脚本大重构的时候。当时手上有个活儿&#xff0c;要同时对接好几个数据源、定时跑批、失败重试&#xff0c;脚本越写越臃肿&#xff0c;日志、异常处理、参数校验全揉在一起&#xff0c;改一个环节就怕动到另一块。…

2026/10/14 7:24:38 阅读更多 →
SQLite+FTS5+BM25构建AI上下文感知能力

SQLite+FTS5+BM25构建AI上下文感知能力

1. “context-mode”到底是什么&#xff1f;别被术语唬住&#xff0c;它本质是让AI真正“听懂上下文”的工程化开关最近在好几个技术群里看到有人问&#xff1a;“context-mode开了没&#xff1f;”、“这个插件依赖context-mode&#xff0c;但文档里根本没说怎么配”&#xff…

2026/10/14 7:24:38 阅读更多 →
不写代码也能搭:AI自动化工作流拆解与实操复盘

不写代码也能搭:AI自动化工作流拆解与实操复盘

我一直觉得&#xff0c;很多非程序员朋友把“AI 自动化工作流”想成了只有工程师才能碰的东西。自己试过一轮之后我反而发现&#xff0c;搭一套能用的 AI 自动化工作流&#xff0c;最核心的难点根本不是写代码&#xff0c;而是怎么把手上那堆乱糟糟的活拆成一条清晰的任务流水线…

2026/10/14 7:24:38 阅读更多 →
游戏研发暑期实习笔试复盘:C++、图形学与算法建模全解析

游戏研发暑期实习笔试复盘:C++、图形学与算法建模全解析

2020年4月11日晚上19:00到21:30&#xff0c;我参加了某游戏大厂的游戏研发暑期实习笔试。2.5小时&#xff0c;没有休息&#xff0c;客观题加四道编程题&#xff0c;考到后面脑子确实有点发木。这不是我第一次接触游戏岗的笔试&#xff0c;但那场题覆盖面很典型&#xff0c;横跨…

2026/10/14 7:23:38 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起&#xff1a;它到底想解决什么问题第一次看到"AnyPS5"这个标题&#xff0c;我脑子里冒出来的第一个念头是&#xff1a;这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述&#xff1a;Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师&#xff0c;我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性&#xff0c;实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景&#xff0c;也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间&#xff0c;确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中&#xff0c;这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介&#xff1a;基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码&#xff0c;面向计算机相关专业课程设计与期末大作业学生&#xff0c;以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程&#xff0c;…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程&#xff1a;键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化&#xff0c;十个新手有八个栽在"往输入框里填东西"这件事上&#xff1a;要么填不进去&#xff0c;要么填了一半&#xff0c;要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程&#xff1a;阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀&#xff1a;什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面&#xff0c;跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 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/13 22:01:27 阅读更多 →
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/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练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/12 18:11:17 阅读更多 →