news 2026/9/29 7:32:11

WinForm RichTextBox 工业级文本编辑器实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
WinForm RichTextBox 工业级文本编辑器实战指南

简介:本资源是一个基于C# WinForm平台开发的富文本编辑器完整实现项目,面向C#初学者与WinForm界面开发学习者,解决基础文本格式化、排版及交互功能集成等典型桌面应用开发问题。项目采用Visual Studio 2022开发,已封装加粗、斜体、下划线、字体颜色/背景色设置、多级对齐、缩进/退格、项目符号与编号、图片插入、内容查找及打印等核心功能,代码结构清晰,含丰富UI图标(18个GIF)与可运行二进制文件(3个EXE),便于快速调试与功能验证。压缩包共66个文件,涵盖15个核心CS逻辑文件、2个RESX本地化资源、1个SQLite配置库、1个CSProj与SLN工程文件,以及调试所需的PDB、CONFIG和缓存文件,整体仅174KB,轻量易解压。目前已有642人学习下载,提供从界面设计(MainForm.cs/resx)、格式工厂(RichFormatFactory.cs)、枚举定义(Enum.cs)到插件式格式接口(IRichFormat.cs)的完整分层实现,是理解RichTextBox深度定制与WinForm事件驱动架构的优质实践范例。

1. WinForm + RichTextBox 实现文本编辑器:不是“能打字就行”,而是要撑住工业现场的实时日志、协议报文和带格式配置文件

你手头有个 WinForm 项目,需要嵌入一个能加粗、换行、高亮关键词、保存 RTF 格式、还能响应 Ctrl+Z/Ctrl+Y 的文本编辑区域——别急着拖个 TextBox 上去。RichTextBox 不是 TextBox 的“豪华版”,它是 Win32 rich edit 控件的托管封装,原生支持 RTF 解析、OLE 对象嵌入、字符级样式控制,更是工业监控系统里协议报文解析窗、PLC 日志滚动视图、设备配置模板编辑器的底层首选。它不依赖 WPF 渲染管线,不引入 .NET MAUI 跨平台抽象层,在老旧工控机(Win7/Win10 LTSC)、无 GPU 环境、低内存嵌入式 Windows 设备上启动快、内存稳、响应准。本文讲的不是“怎么让 RichTextBox 显示文字”,而是如何用它落地一个可维护、可扩展、不卡顿、不丢格式、能对接真实业务流的文本编辑器模块——从初始化陷阱到撤销栈管理,从 RTF 安全解析到跨线程日志追加,每一步都踩过坑、测过压、上线过产线。


2. 初始化与基础功能搭建:绕开默认行为,从第一行代码就锁定可控性

RichTextBox 默认行为对生产环境极不友好:自动滚动到底部、粘贴时吞掉缩进、Ctrl+A 选中全部却无法取消焦点、字体随系统 DPI 缩放失真……必须在构造函数里做三件事:禁用默认滚动、重置字体上下文、接管剪贴板交互。

2.1 创建最小可控实例:禁用自动滚动 + 固定字体 + 禁用上下文菜单

public partial class RichTextEditor : UserControl { private readonly RichTextBox _rtb; public RichTextEditor() { InitializeComponent(); _rtb = new RichTextBox { Dock = DockStyle.Fill, // 关键:禁用自动滚动,否则日志追加时疯狂跳动 AutoScroll = false, // 关键:禁用右键菜单,避免用户误触“清除格式” ContextMenuStrip = null, // 关键:显式设置字体,避免 DPI 缩放导致行高错乱 Font = new Font("Consolas", 9f, GraphicsUnit.Point), // 关键:启用多行但禁用 WordWrap,保证等宽日志对齐 WordWrap = false, // 关键:设为只读模式初始态,业务逻辑决定何时可编辑 ReadOnly = true }; _rtb.KeyDown += OnKeyDown; _rtb.TextChanged += OnTextChanged; Controls.Add(_rtb); } }

提示:AutoScroll = false是工业场景生死线。很多串口日志窗因未关闭此选项,当每秒追加 50 行日志时,RichTextBox 内部会反复计算滚动位置,CPU 占用飙升至 30%+。关闭后需手动ScrollToCaret()或Select(Length, 0); ScrollToCaret();控制滚动,但性能提升 5 倍以上。

2.2 字体与缩放适配:用 GDI+ 获取真实像素尺寸,拒绝 SystemFonts.DefaultFont

WinForm 在高 DPI 下默认缩放 RichTextBox 字体,导致行高错位、光标偏移、RTF 解析失败。正确做法是绕过系统字体映射,直接创建像素级精确字体:

private Font GetFixedFont(float sizeInPoints) { // 获取当前 DPI 缩放比例(非 Win10 1703+ 可用 GetDpiForWindow) var dpiX = GetDeviceCaps(GetDC(IntPtr.Zero), LOGPIXELSX); var scale = dpiX / 96.0f; // 96 是 100% DPI 基准 var pixelSize = sizeInPoints * scale; // 创建不随 DPI 缩放的字体(关键:使用 GraphicsUnit.Pixel) return new Font("Consolas", pixelSize, GraphicsUnit.Pixel); } // 在构造函数中调用 _rtb.Font = GetFixedFont(9f);

参数说明:GraphicsUnit.Pixel让字体大小以像素为单位固定,彻底规避 DPI 缩放干扰;Consolas是等宽字体,对协议报文十六进制对齐、JSON 缩进、XML 标签层级显示至关重要;9f是经实测在 1920×1080@125% DPI 下最清晰的字号,比10pt更节省垂直空间。

2.3 基础编辑能力注入:Ctrl+Z/Y 撤销重做、Ctrl+F 查找、Tab 缩进控制

RichTextBox 自带Undo()和Redo(),但默认撤销栈深度为 100,且不支持自定义操作粒度。工业场景需精确控制:

private void OnKeyDown(object sender, KeyEventArgs e) { if (e.Control && e.KeyCode == Keys.Z) { e.SuppressKeyPress = true; if (_rtb.CanUndo) _rtb.Undo(); } else if (e.Control && e.KeyCode == Keys.Y) { e.SuppressKeyPress = true; if (_rtb.CanRedo) _rtb.Redo(); } else if (e.Control && e.KeyCode == Keys.F) { e.SuppressKeyPress = true; ShowFindDialog(); } else if (e.KeyCode == Keys.Tab && !_rtb.ReadOnly) { e.SuppressKeyPress = true; InsertTabAtSelection(); } } private void InsertTabAtSelection() { var selStart = _rtb.SelectionStart; var selLength = _rtb.SelectionLength; // 插入 4 个空格(非 Tab 字符),避免 RTF 解析错乱 _rtb.SelectedText = " "; // 恢复光标到插入点后 _rtb.SelectionStart = selStart + 4; }

逻辑说明:SuppressKeyPress = true阻止系统默认 Tab 导航行为;插入" "而非"\t"是因 RichTextBox 对\t的 RTF 渲染不稳定,尤其在不同缩进级别混合时易崩坏格式;CanUndo/CanRedo检查避免调用异常。


3. RTF 格式安全处理:解析、生成、校验三步闭环,拒绝“打开就崩溃”

RichTextBox 的.Rtf属性是双刃剑:直接赋值 RTF 字符串可实现加粗/颜色/字体切换,但非法 RTF 会导致控件直接抛出ArgumentException并不可恢复。工业配置文件常含特殊字符(如0x00、0x01)、不完整组标记({\colortbl;}缺少结尾})、嵌套过深(超过 32 层{}),必须建立 RTF 安全校验层。

3.1 RTF 解析前置校验:用正则 + 深度计数双保险过滤非法结构

private bool IsValidRtf(string rtfContent) { if (string.IsNullOrEmpty(rtfContent)) return false; // Step 1: 必须以 "{\rtf" 开头(忽略空格和注释) var headerMatch = Regex.Match(rtfContent, @"^\s*\{\\rtf", RegexOptions.IgnoreCase); if (!headerMatch.Success) return false; // Step 2: 大括号深度平衡检测(防栈溢出) int depth = 0; foreach (char c in rtfContent) { if (c == '{') depth++; else if (c == '}') depth--; if (depth < 0) return false; // 提前闭合 if (depth > 32) return false; // 深度超限 } if (depth != 0) return false; // 未闭合 // Step 3: 检查常见危险控制字(如 \bin、\pict) if (Regex.IsMatch(rtfContent, @"\\bin|\\pict|\\object", RegexOptions.IgnoreCase)) return false; return true; }

参数说明:depth > 32是 Win32 rich edit 控件硬限制,超限直接触发 AccessViolation;\bin和\pict可能加载二进制对象引发 GDI 资源泄漏,工业系统严禁;正则^\s*\{\\rtf允许开头有 BOM 或空格,兼容 UTF-8 with BOM 的 RTF 文件。

3.2 安全 RTF 生成器:按需构建最小合法 RTF 片段,不拼接原始字符串

直接拼接"\\b " + text + "\\b0"极易出错。应使用RichTextBox自身 API 生成片段:

public string BuildStyledRtf(string plainText, Color foreColor, bool isBold, float fontSize = 9f) { // 创建临时 RTB 用于生成 RTF using var tempRtb = new RichTextBox(); tempRtb.Rtf = @"{\rtf1\ansi\ansicpg936\deff0\deflang1033{\fonttbl{\f0\fnil\fcharset0 Consolas;}}{\colortbl ;\red" + foreColor.R + @"\green" + foreColor.G + @"\blue" + foreColor.B + @";}\viewkind4\uc1\pard\f0\fs" + (int)(fontSize * 2) + @"\cf1 "; tempRtb.Text = plainText; tempRtb.SelectAll(); tempRtb.SelectionColor = foreColor; if (isBold) tempRtb.SelectionFont = new Font(tempRtb.SelectionFont, FontStyle.Bold); // 提取纯 RTF 片段(去掉头部和尾部) var fullRtf = tempRtb.Rtf; var start = fullRtf.IndexOf("\\cf1 ", StringComparison.Ordinal) + 5; var end = fullRtf.LastIndexOf("}", StringComparison.Ordinal); return fullRtf.Substring(start, end - start + 1); }

逻辑说明:tempRtb.Rtf初始化一个最小合法 RTF 头,确保{\colortbl;}存在;SelectionFont和SelectionColor由控件内部引擎生成标准 RTF,100% 兼容;Substring截取仅含内容的 RTF 片段,供后续拼接使用。

3.3 RTF 加载防护:Try-Catch + 回退机制,保证 UI 不死锁

public void LoadRtfSafely(string rtfContent) { if (!IsValidRtf(rtfContent)) { // 回退到纯文本加载,保留内容不丢失 _rtb.Text = $"[RTF 格式错误] 无法解析,已转为纯文本:\r\n{rtfContent}"; return; } try { _rtb.Rtf = rtfContent; } catch (ArgumentException ex) when (ex.Message.Contains("RTF")) { // 记录日志 Log.Error($"RTF 加载失败: {ex.Message}"); _rtb.Text = $"[RTF 解析异常] 已降级为纯文本\r\n{rtfContent}"; } catch (OutOfMemoryException) { // RTF 过大时触发,强制截断 var truncated = rtfContent.Substring(0, Math.Min(1024 * 1024, rtfContent.Length)); _rtb.Rtf = truncated; } }

避坑重点:OutOfMemoryException在加载超大 RTF(>50MB)时必然发生,此时不能throw,必须截断;ArgumentException的ex.Message.Contains("RTF")是唯一可靠判断依据,其他异常类型(如NullReferenceException)需单独捕获并记录堆栈。


4. 高频场景专项优化:日志追加、协议报文高亮、跨线程安全更新

工业现场最常卡顿的三个场景:串口接收日志实时追加、Modbus 报文 HEX/ASCII 双视图高亮、后台线程写入 UI。RichTextBox 默认线程模型是单线程 Apartment,跨线程调用.AppendText()会触发InvalidOperationException,而简单Invoke又因高频调用堆积消息队列。

4.1 日志追加零卡顿:批量缓冲 + 异步渲染 + 滚动锚定

private readonly Queue<string> _logBuffer = new(); private readonly object _bufferLock = new(); private Timer _renderTimer; public void AppendLogLine(string line) { lock (_bufferLock) { _logBuffer.Enqueue(line); } // 启动或重置渲染定时器(50ms 周期,防抖) _renderTimer?.Stop(); _renderTimer = new Timer(OnRenderLogs, null, 50, Timeout.Infinite); } private void OnRenderLogs(object state) { var linesToRender = new List<string>(); lock (_bufferLock) { while (_logBuffer.Count > 0 && linesToRender.Count < 100) // 单次最多刷 100 行 { linesToRender.Add(_logBuffer.Dequeue()); } } if (linesToRender.Count == 0) return; // 批量追加(减少重绘次数) _rtb.AppendText(string.Join("\r\n", linesToRender) + "\r\n"); // 锚定滚动到底部(关键:先选中末尾再滚动) _rtb.SelectionStart = _rtb.TextLength; _rtb.ScrollToCaret(); // 继续下一轮 if (_logBuffer.Count > 0) _renderTimer.Change(50, Timeout.Infinite); }

参数说明:linesToRender.Count < 100防止单次追加过多导致 UI 停顿;_rtb.AppendText(...)比循环AppendText快 8 倍;SelectionStart = TextLength确保光标在末尾,ScrollToCaret()才能精准滚动。

4.2 协议报文 HEX/ASCII 高亮:用字符级 SelectionColor,避开 RTF 性能黑洞

对 Modbus TCP 报文00 00 00 00 00 06 01 03 00 00 00 02,需将功能码03标蓝、寄存器地址00 00标绿、长度00 02标红。若用 RTF 生成,每字节都要包裹{\\cf2 ...},1KB 报文生成 RTF 耗时 200ms+。正确做法是直接操作 SelectionColor:

public void HighlightModbusBytes(byte[] bytes) { // 先清空原有高亮 _rtb.SelectAll(); _rtb.SelectionColor = Color.Black; _rtb.SelectionBackColor = Color.White; // 假设 bytes 已转为 HEX 字符串 "00 00 00 00 00 06 01 03 00 00 00 02" var hexStr = BitConverter.ToString(bytes).Replace("-", " "); _rtb.Text = hexStr; // 定位并高亮(示例:功能码在索引 18-19,即第 9 个字节) int funcCodePos = 18; // "03" 的起始位置 _rtb.Select(funcCodePos, 2); _rtb.SelectionColor = Color.Blue; int addrPos = 20; // "00 00" 起始 _rtb.Select(addrPos, 5); // "00 00" 占 5 字符(含空格) _rtb.SelectionColor = Color.Green; int lenPos = 30; // "00 02" 起始 _rtb.Select(lenPos, 5); _rtb.SelectionColor = Color.Red; }

逻辑说明:SelectionColor是 GDI 直接绘制,毫秒级响应;Select(start, length)定位精确到字符,比正则匹配快 10 倍;高亮前SelectAll()清空旧样式,避免残留。

4.3 跨线程安全更新:用 BeginInvoke + 委托签名固化,杜绝 InvokeRequired 误判

private delegate void UpdateTextDelegate(string text); private delegate void UpdateRtfDelegate(string rtf); public void SafeSetText(string text) { if (_rtb.InvokeRequired) { _rtb.BeginInvoke(new UpdateTextDelegate(SafeSetText), text); return; } _rtb.Text = text; } public void SafeSetRtf(string rtf) { if (_rtb.InvokeRequired) { _rtb.BeginInvoke(new UpdateRtfDelegate(SafeSetRtf), rtf); return; } LoadRtfSafely(rtf); // 复用前面的安全加载 }

避坑重点:必须用BeginInvoke而非Invoke,避免线程阻塞;委托签名UpdateTextDelegate必须与方法签名完全一致,否则BeginInvoke会静默失败;InvokeRequired判断必须在_rtb上执行,不能用this.InvokeRequired(父窗体可能不同线程)。


5. 避坑指南:5 个血泪经验总结,全是产线翻车现场还原

RichTextBox 表面简单,实则黑匣子极多。以下 5 条均来自真实产线事故,附现象、根因、解法,拒绝玄学。

5.1 现象:日志窗口运行 2 小时后 CPU 占用 95%,Process Explorer 显示 riched20.dll 模块高负载

原因:未关闭AutoScroll,且日志追加频率 > 20 行/秒,RichTextBox 内部滚动计算逻辑在 GDI 层死循环
解决:AutoScroll = false+ScrollToCaret()替代,CPU 降至 3% 以下

5.2 现象:加载含中文的 RTF 文件后,部分汉字显示为方框或乱码

原因:RTF 头部未声明ansicpg936(GBK 编码),或{\fonttbl{...}}中字体名含 Unicode 字符
解决:强制 RTF 头部写{\rtf1\ansi\ansicpg936\deff0...},字体名用SimSun而非宋体

5.3 现象:用户 Ctrl+Z 撤销 10 次后,再输入文字,光标随机跳到文档开头

原因:RichTextBox 撤销栈在ReadOnly = true时仍记录操作,但ReadOnly = false切换后状态错乱
解决:ReadOnly切换前调用_rtb.ClearUndo(),且只在明确需要编辑时才设ReadOnly = false

5.4 现象:串口接收线程调用AppendText()频繁抛出InvalidOperationException: 跨线程操作无效

原因:InvokeRequired判断在_rtb上执行,但_rtb被多次Controls.Remove/Add导致句柄失效
解决:BeginInvoke前加if (_rtb.IsHandleCreated)检查,句柄失效时改用PostMessage发送自定义 WM_COPYDATA 消息

5.5 现象:RTF 文件中含图片(\pict)时,RichTextBox 加载后内存持续增长,30 分钟后 OOM

原因:RichTextBox 加载\pict后未释放 GDI 对象,且Dispose()不清理内部缓存
解决:禁止加载含\pict的 RTF(见 3.1 校验),或加载前用正则剥离所有\pict块:rtf = Regex.Replace(rtf, @"\\pict[^}]*}", "", RegexOptions.Singleline);


6. 进阶技巧:用 RichTextBox 做协议解析器前端,实现 HEX/ASCII 双视图联动编辑

最后这个技巧,是我给某 PLC 调试工具做的核心功能:左侧 HEX 视图(RichTextBox),右侧 ASCII 视图(另一个RichTextBox),点击 HEX 字节,ASCII 视图同步高亮对应字符,反之亦然。关键不在 UI,而在字节索引与字符位置的双向映射。

6.1 HEX/ASCII 位置映射表:预计算 O(1) 查找,拒绝每次遍历

private readonly Dictionary<int, int> _hexToAsciiPos = new(); // HEX 字符索引 → ASCII 字符索引 private readonly Dictionary<int, int> _asciiToHexPos = new(); // ASCII 字符索引 → HEX 字符索引 private void BuildPositionMap(byte[] data) { _hexToAsciiPos.Clear(); _asciiToHexPos.Clear(); // HEX 视图格式:"00 01 02 ... "(每字节 2 字符 + 1 空格) // ASCII 视图格式:"................"(每字节 1 字符) for (int i = 0; i < data.Length; i++) { int hexIndex = i * 3; // 第 i 字节在 HEX 视图的起始位置(0,3,6...) int asciiIndex = i; // 第 i 字节在 ASCII 视图的位置 _hexToAsciiPos[hexIndex] = asciiIndex; _hexToAsciiPos[hexIndex + 1] = asciiIndex; // HEX 的两个字符映射到同一 ASCII 字符 _asciiToHexPos[asciiIndex] = hexIndex; } }

6.2 双视图联动高亮:鼠标点击时,根据位置查表,同步 Select

private void HexRtb_MouseClick(object sender, MouseEventArgs e) { // 获取点击处的字符索引 int charIndex = _hexRtb.GetCharIndexFromPosition(e.Location); // 查表得 ASCII 位置 if (_hexToAsciiPos.TryGetValue(charIndex, out int asciiPos)) { // 高亮 ASCII 视图 _asciiRtb.Select(asciiPos, 1); _asciiRtb.SelectionBackColor = Color.LightBlue; // 同步高亮 HEX 视图(以字节为单位) int hexBase = _asciiToHexPos[asciiPos]; _hexRtb.Select(hexBase, 2); _hexRtb.SelectionBackColor = Color.LightBlue; } } private void AsciiRtb_MouseClick(object sender, MouseEventArgs e) { int charIndex = _asciiRtb.GetCharIndexFromPosition(e.Location); if (_asciiToHexPos.TryGetValue(charIndex, out int hexPos)) { _hexRtb.Select(hexPos, 2); _hexRtb.SelectionBackColor = Color.LightBlue; _asciiRtb.Select(charIndex, 1); _asciiRtb.SelectionBackColor = Color.LightBlue; } }

参数说明:GetCharIndexFromPosition()返回鼠标点击处的字符索引,非像素坐标;_hexToAsciiPos和_asciiToHexPos是静态映射,构建一次终身有效;SelectionBackColor比SelectionColor更醒目,适合调试场景。

6.3 可编辑双视图:修改 HEX 同步更新 ASCII,反之亦然

private void HexRtb_TextChanged(object sender, EventArgs e) { // 从 HEX 文本提取字节数组 var hexText = _hexRtb.Text.Replace(" ", "").Replace("\r", "").Replace("\n", ""); if (hexText.Length % 2 != 0) return; try { var bytes = new byte[hexText.Length / 2]; for (int i = 0; i < hexText.Length; i += 2) { bytes[i / 2] = Convert.ToByte(hexText.Substring(i, 2), 16); } // 更新 ASCII 视图(不可见字符转 '.') var asciiBuilder = new StringBuilder(); foreach (byte b in bytes) { asciiBuilder.Append(char.IsControl((char)b) || b < 32 || b > 126 ? '.' : (char)b); } _asciiRtb.Text = asciiBuilder.ToString(); } catch { /* HEX 格式错误,不更新 */ } }

逻辑说明:hexText.Replace(" ", "")清除空格,适配用户手动编辑;Convert.ToByte(..., 16)直接解析 HEX,比正则快 5 倍;ASCII 视图中char.IsControl过滤不可见字符,符合协议分析惯例。

我做这个双视图功能时,客户要求“点击任意字节,立刻看到它在协议中的语义”。于是我在_hexToAsciiPos表基础上,又加了一层_byteToSemantic字典,把 Modbus 功能码03映射到"Read Holding Registers",把06映射到"Write Single Register",鼠标悬停就弹 Tooltip。后来发现,真正让客户拍桌子叫好的,不是炫酷动画,而是点击后光标精准停在字节上、撤销重做不丢状态、连续点击 100 次不卡顿——这些全靠前面那些看似枯燥的初始化、校验、避坑。WinForm 的 RichTextBox 不是古董,它是经过 20 年产线锤炼的工业级文本引擎,只是需要你亲手把它从默认配置里解放出来。希望帮到你。

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

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

STM32学习与实战:战略上不贪也不放,走好每一步

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

作者头像 李华
网站建设 2026/9/29 7:30:47

BepInEx 安装上手:把插件框架装进你的 Unity 游戏

BepInEx 安装上手&#xff1a;把插件框架装进你的 Unity 游戏 【免费下载链接】BepInEx Unity / XNA game patcher and plugin framework 项目地址: https://gitcode.com/GitHub_Trending/be/BepInEx BepInEx 是一个面向 Unity 和 .NET 游戏的插件加载框架&#xff0c;装…

作者头像 李华
网站建设 2026/9/29 7:29:50

Zephyr应用:10-Thread

摘要&#xff1a;本文是 Zephyr RTOS 多线程编程的入门实战课。你将理解线程&#xff08;Thread&#xff09;与裸机顺序执行的区别&#xff0c;掌握 K_THREAD_DEFINE() 创建静态线程、k_sleep() / k_msleep() 主动让出 CPU、线程优先级&#xff08;数值越小优先级越高&#xff…

作者头像 李华
网站建设 2026/9/29 7:29:37

AI工程从零搭建:RAG、Prompt与Agent工作流实战

很多人问我&#xff0c;“AI工程到底怎么入门&#xff1f;”我自己也是从一个个零散脚本起步&#xff0c;踩了不少坑&#xff0c;才慢慢摸出一套能复用的工程化路径。今天这篇就围绕“ai-engineering-from-scratch”这个项目主题&#xff0c;聊聊从零搭一个AI工程的全部关键环节…

作者头像 李华
网站建设 2026/9/29 7:29:00

GPEN人脸修复:基于GAN先验的盲人脸复原技术解析

1. 先聊一聊&#xff1a;为什么人脸修复到了GPEN这里才算“能用”做图像修复、老照片还原或者视频增强的朋友&#xff0c;应该都经历过这种折磨&#xff1a;拿一张百年前斑驳的人像照或一张监控里的低分辨率人脸&#xff0c;跑传统超分算法&#xff0c;出来的图是“清晰了”&am…

作者头像 李华