做图像增强处理Python程序这件事,最初是因为我手里有一批拍摄环境偏暗、画面发灰的照片,人脸轮廓都快被压进暗部里了。我花了一个周末写了一个命令行工具,输入一张图或一个文件夹,就能自动做对比度增强、亮度修正、细节锐化和轻度降噪,最终把“灰蒙蒙”的图片修到可用的程度。这篇文章就把这个程序的完整设计思路、核心算法选择、代码实现、参数调优和踩坑过程都摊开讲一遍,给也想做图像增强处理Python程序的人一个可以直接照抄的样本。
1. 图像增强处理Python程序:需求与方案选型
1.1 先搞清楚“增强”到底要动哪些参数
很多人拿到图像增强处理Python程序的需求后,第一反应是“用深度学习超分辨率重建模型”。但我的场景其实没那么复杂:主要是旧照片、监控截图、设备拍摄的样张,画面存在问题主要是三类,一是对比度不足,直方图集中在中间区域,黑的不够黑、白的不够白;二是整体偏暗或偏亮,缺乏合理的曝光感;三是细节边缘模糊,像蒙了一层纱。
所以我把“增强”拆成了四个互相独立又可组合的环节:
- 对比度增强:拉伸有效灰度范围,让层次更分明。
- 亮度修正:用非线性曲线调整曝光,保留高光和暗部细节。
- 细节锐化:增强边缘过渡,让轮廓更清晰。
- 噪声抑制:在提升对比度和锐化的同时,避免把噪点一并放大。
这四个环节就是整个程序的核心闭环。我没有一步到位去做“超分”或“人像美颜”,原因也很简单:那些功能虽然酷,但需要模型权重、GPU推理、调参成本,而且对于“把图修到能发出去”这种目标来说,严重超纲。图像增强处理Python程序的第一个原则,就是知道哪些需求必须接,哪些需求应该明确说“不做”。
1.2 Python + OpenCV + NumPy 的组合为什么够用
整个程序我选了Python生态,核心库只有三个:OpenCV、NumPy、argparse。这个组合几乎不用动脑子,OpenCV提供大量成熟的图像处理算子,底层是C和C++,性能完全够用;NumPy负责像素级运算,尤其在做伽马校正和查表法时很方便;argparse则是Python标准库,用于批量参数传入。
有人可能会问:处理大量图片时,Python不是会被嫌弃慢吗?其实在图像增强这类任务里,真正的计算密集部分是卷积、直方图统计、颜色空间转换,这些全部由OpenCV的底层实现完成,Python只是在外面做“调度”。我在实际处理中,一个500万像素的RGB图像完成“CLAHE + 伽马 + 锐化”全流程,单线程大约是80到150毫秒。这个速度已经足够批量处理几百张图片了,没必要为了追求极端性能去换C++。
还有一个选择是Pillow。Pillow胜在简单,但它的色彩空间管理、CLAHE支持、快速去噪能力都比OpenCV弱不少。如果你做的是“图像增强处理Python程序”,我建议直接用OpenCV为主体,Pillow最多用来做格式转换和缩略图。
1.3 我决定不做什么
在我的程序里,我明确不做以下几件事:
- 不做基于深度学习的超分辨率重建,因为依赖重、速度慢,而且容易生成过度平滑的“塑料感”纹理。
- 不做自动人脸美颜,因为处理对象是通用图像,不是只有人像。
- 不做内容生成式修复,例如给破损照片凭空补出缺失区域,那属于inpainting的另一个话题。
- 不做一键自动白平衡,因为它很可能改变原图的真实色彩氛围。旧照片的黄色调有时候是年代感,强行校正反而不自然。
有了这些边界,程序可以把精力全部集中在“可解释、可控制、可重复”的像素级增强上。图像增强处理Python程序真正的价值,不是炫技,而是能稳定处理一千张图,每一张效果都可预测。
2. 核心算法原理解析与关键选型
2.1 对比度:CLAHE 是比直方图均衡化更“听话”的方案
先看最简单的全局直方图均衡化(Histogram Equalization),它的核心思想是把图像的灰度直方图重映射到近似均匀分布,让原本集中在狭窄区间的像素分布摊开。用OpenCV一行就能做:cv2.equalizeHist(gray)。
但这个方案在真实照片上有个副作用:如果画面局部有很亮的窗口或很暗的角落,全局均衡化会把局部反差拉得过大,导致人脸出现灰白、五官发硬,整体观感非常“数码感”。原因在于它只统计全局灰度分布,没有考虑像素的空间位置。
我实际用的是CLAHE(Contrast Limited Adaptive Histogram Equalization,限制对比度自适应直方图均衡化)。它把图像分成一个个小块(默认8x8),每个小块单独做直方图均衡化,然后用双线性插值把块与块之间的边界融合,避免出现明显的块状痕迹。关键是它加了一个“对比度限制”参数clipLimit,当某个灰度等级的像素数量超过阈值时,多出来的部分会被剪掉并重新分配,从而限制对比度放大的幅度。
在实际代码中的体现是这样:
import cv2 def apply_clahe(bgr_image, clip_limit=2.0, tile_size=8): # 转LAB颜色空间,单独处理亮度通道,可以最大限度保护颜色 lab = cv2.cvtColor(bgr_image, cv2.COLOR_BGR2LAB) l_channel, a_channel, b_channel = cv2.split(lab) clahe = cv2.createCLAHE(clipLimit=clip_limit, tileGridSize=(tile_size, tile_size)) l_enhanced = clahe.apply(l_channel) lab_enhanced = cv2.merge((l_enhanced, a_channel, b_channel)) return cv2.cvtColor(lab_enhanced, cv2.COLOR_LAB2BGR)这个函数是我整个程序里最核心的模块。clipLimit=2.0是我试出来的通用默认值,偏低时增强效果不明显,偏高时对比度会很生硬,容易出现光晕感。tile_size=8表示每个局部块大小为8x8像素,块越小,局部自适应能力越强,但过小会让纹理细节像被“磨过”一样,不建议低于4。
2.2 亮度:伽马校正的曲线逻辑
亮度修正我没有直接用“乘以一个系数”,因为线性乘法是很多新手最容易踩的坑。如果直接pixel = pixel * 1.2,高光部分会瞬间曝掉,暗部也撑不起来,效果非常粗糙。
真正合适的方案是伽马校正,也就是对每个像素做O = I^(1/gamma),或者写成O = I^gamma,取决于你从哪个方向理解。为了方便,我代码里统一使用“调整系数”语义:当gamma < 1时,输出图像整体变亮;当gamma > 1时,整体变暗。
用NumPy实现非常简洁:
import numpy as np def apply_gamma(bgr_image, gamma=0.85): # 归一化到0-1区间再做幂运算,最后用clip防止溢出 table = np.linspace(0, 1, 256, dtype=np.float32) ** gamma table = np.clip(table * 255, 0, 255).astype(np.uint8) return cv2.LUT(bgr_image, table)这里我用了cv2.LUT查表法,而不是直接对每个像素做浮点运算。原因是查表法生成长度为256的映射表,一次索引替换就能处理整张图,速度比逐像素操作快几个数量级。clip是必须的,因为归一化后乘255时,某些像素可能略超过255,不处理就会出现环绕导致的颜色错乱。
为什么要用非线性曲线而不是线性乘系数?因为人眼对暗部的亮度变化更敏感,伽马曲线在暗部抬升的幅度大,在高光处保持平缓,这样能让暗部细节露出来,同时高光不刺眼。我的默认gamma=0.85是偏保守的值,能让欠曝图片明显变亮,但不会让人觉得“发灰”。
2.3 锐化:USM 要在噪声抑制之后再做
锐化我选了USM(Unsharp Mask,反锐化掩模),而不是最简单的拉普拉斯算子。拉普拉斯算子直接提取二阶导数并叠加到原图上,边缘虽然变得清楚,但噪点和压缩伪影会被一起放大,原图质量差一点就惨不忍睹。
USM的思路是先对原图做一次高斯模糊得到“低频层”,再用“原图 - 低频层”得到边缘细节层,最后把细节层乘以一个强度系数加回原图。它的好处是可以通过高斯模糊的sigma控制边缘提取范围,sigma越小提取的细节越细微,sigma越大提取的轮廓越宏观。
核心代码:
def apply_usm(bgr_image, amount=1.2, sigma=1.0): blurred = cv2.GaussianBlur(bgr_image, (0, 0), sigma) detail = cv2.subtract(bgr_image, blurred) sharpened = cv2.addWeighted(bgr_image, 1.0, detail, amount, 0) return sharpened注意amount我推荐控制在0.8到1.5之间。低于0.8基本看不出效果,高于1.5边缘会出现很明显的白色“描边”,尤其在天空和皮肤边缘。另外,sigma=1.0适合大多数照片,如果图片本身分辨率很高,可以适当提高到1.5。
还有一个非常重要的执行顺序问题:先锐化还是先去噪?如果先去噪再锐化,锐化能增强真正的边缘,同时不会把残留噪点进一步放大;如果先锐化再去噪,去噪会把锐化产生的振铃和边缘过冲又磨掉,等于白做。所以我的管线严格执行“去噪 -> 对比度 -> 亮度 -> 锐化”的顺序。
2.4 颜色修正:LAB空间单独处理亮度通道
很多人在做亮度增强时直接操作BGR三通道,结果就是增强后颜色“变味”,偏蓝或偏黄,非常难看。原因很简单,BGR通道和色彩视觉的感知属性不匹配,直接乘系数会打破通道间的比例关系。
我的做法是在处理亮度和对比度之前,把图像从BGR转换到LAB颜色空间。LAB空间用一个L通道表示亮度,用A和B两个通道表示颜色对立度。这样做的好处是:对L通道做任何拉伸、压缩、伽马处理,A和B通道保持不变,颜色在理论上是稳定不偏移的。
回看2.1中的apply_clahe函数,实际上它已经内置了LAB转换。但如果你对处理后的图做更精细的颜色修正,也可以直接操作A和B通道做饱和度调整:
def adjust_saturation(bgr_image, percent=1.1): hsv = cv2.cvtColor(bgr_image, cv2.COLOR_BGR2HSV) h, s, v = cv2.split(hsv) s = np.clip(s * percent, 0, 255).astype(np.uint8) return cv2.cvtColor(cv2.merge((h, s, v)), cv2.COLOR_HSV2BGR)在增加对比度和亮度后,图像颜色通常会显得淡一些,适当调整饱和度能找回“鲜亮感”。但percent别超过1.2,否则天空、肤色、绿色植被都会变得过于浓艳,直接失去真实感。
3. 完整实现:一个可复用的批处理程序
3.1 依赖安装与项目骨架
项目结构非常简单:
image_enhancer/ ├── enhance.py ├── requirements.txt └── test_images/ ├── input/ └── output/requirements.txt只需要两个依赖:
opencv-python>=4.5.3 numpy>=1.21.0安装的时候有个小的建议:如果只是处理普通图片,安装opencv-python就够了;如果对视频流处理有需求,再安装opencv-contrib-python,因为一些扩展算法在contrib版本里才齐全。这两者不能同时装在同一环境,否则会出现符号冲突。
3.2 核心代码逐段讲解
下面是我清洗整理后的完整程序骨架,可以直接存成enhance.py运行:
import argparse import logging import time from pathlib import Path import cv2 import numpy as np logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s') logger = logging.getLogger("image_enhancer") def read_image(path: Path): # Windows下中文路径用imread经常失败,改用imdecode读取 data = np.fromfile(str(path), dtype=np.uint8) image = cv2.imdecode(data, cv2.IMREAD_COLOR) if image is None: raise ValueError(f"无法读取图像: {path}") return image def save_image(path: Path, image: np.ndarray): # 同样用imencode+tofile解决中文路径写入问题 ext = path.suffix success, buffer = cv2.imencode(ext, image) if not success: raise ValueError(f"无法编码图像: {path}") buffer.tofile(str(path)) def apply_clahe(bgr_image, clip_limit=2.0, tile_size=8): lab = cv2.cvtColor(bgr_image, cv2.COLOR_BGR2LAB) l, a, b = cv2.split(lab) clahe = cv2.createCLAHE(clipLimit=clip_limit, tileGridSize=(tile_size, tile_size)) l = clahe.apply(l) lab_merged = cv2.merge((l, a, b)) return cv2.cvtColor(lab_merged, cv2.COLOR_LAB2BGR) def apply_gamma(bgr_image, gamma=0.85): table = np.linspace(0, 1, 256, dtype=np.float32) ** gamma table = np.clip(table * 255, 0, 255).astype(np.uint8) return cv2.LUT(bgr_image, table) def apply_usm(bgr_image, amount=1.2, sigma=1.0): blurred = cv2.GaussianBlur(bgr_image, (0, 0), sigma) detail = cv2.subtract(bgr_image, blurred) return cv2.addWeighted(bgr_image, 1.0, detail, amount, 0) def apply_denoise(bgr_image, strength=7): # 可选步骤,非局部均值去噪,对彩色图有效 return cv2.fastNlMeansDenoisingColored( bgr_image, None, strength, strength, 7, 21 ) def enhance_image(image, clip_limit=2.0, tile_size=8, gamma=0.85, use_sharpen=True, sharpen_amount=1.2, denoise=False, saturation=1.1): result = image.copy() if denoise: logger.info("执行去噪") result = apply_denoise(result, strength=7) result = apply_clahe(result, clip_limit, tile_size) result = apply_gamma(result, gamma) if saturation != 1.0: hsv = cv2.cvtColor(result, cv2.COLOR_BGR2HSV) h, s, v = cv2.split(hsv) s = np.clip(s.astype(np.float32) * saturation, 0, 255).astype(np.uint8) result = cv2.cvtColor(cv2.merge((h, s, v)), cv2.COLOR_HSV2BGR) if use_sharpen: result = apply_usm(result, amount=sharpen_amount, sigma=1.0) return result def process_file(input_path: Path, output_path: Path, args): try: start = time.time() image = read_image(input_path) logger.info("处理 %s, 尺寸: %s", input_path.name, image.shape) result = enhance_image( image, clip_limit=args.clip_limit, tile_size=args.tile_size, gamma=args.gamma, use_sharpen=args.sharpen, sharpen_amount=args.sharpen_amount, denoise=args.denoise, saturation=args.saturation, ) output_path.parent.mkdir(parents=True, exist_ok=True) save_image(output_path, result) cost_ms = (time.time() - start) * 1000 logger.info("已完成 %s,耗时 %.1f ms", input_path.name, cost_ms) except Exception as e: logger.error("处理失败 %s: %s", input_path.name, e) def main(): parser = argparse.ArgumentParser( description="图像增强处理Python程序:CLAHE + 伽马 + USM 批量工具" ) parser.add_argument("--input", type=str, default="test_images/input", help="输入图片目录") parser.add_argument("--output", type=str, default="test_images/output", help="输出图片目录") parser.add_argument("--ext", type=str, default=".jpg", help="要处理的后缀,默认.jpg,也支持.png") parser.add_argument("--clip-limit", type=float, default=2.0, help="CLAHE对比度限制值") parser.add_argument("--tile-size", type=int, default=8, help="CLAHE局部块大小") parser.add_argument("--gamma", type=float, default=0.85, help="伽马系数,小于1变亮") parser.add_argument("--sharpen", action="store_true", default=True, help="是否启用锐化") parser.add_argument("--sharpen-amount", type=float, default=1.2, help="USM锐化强度") parser.add_argument("--denoise", action="store_true", default=False, help="是否启用非局部均值去噪,默认关闭") parser.add_argument("--saturation", type=float, default=1.1, help="饱和度调整倍数") args = parser.parse_args() input_dir = Path(args.input) output_dir = Path(args.output) if not input_dir.exists(): logger.error("输入目录不存在: %s", input_dir) return # 只处理指定的扩展名文件 files = [p for p in input_dir.iterdir() if p.suffix.lower() == args.ext.lower()] if not files: logger.warning("目录中没有找到后缀为 %s 的图片", args.ext) return logger.info("共发现 %d 张图片", len(files)) for path in files: out = output_dir / path.name process_file(path, out, args) if __name__ == "__main__": main()这段代码有几个细节值得单独说明。
第一,read_image和save_image都用了np.fromfile加cv2.imdecode的组合。原生cv2.imread在处理中文路径时经常直接返回None,而我的输入目录图片大部分都是中文文件名,这个坑不提前规避,批处理时就会有一半图片神秘跳过。
第二,apply_gamma不是直接对整张图做浮点幂运算,而是先建一个0-255灰度值映射表,再用cv2.LUT做查表替换。这样做的好处除了速度快,还有一个隐藏优势:查表过程天然完成像素值裁剪,不会因为浮点溢出造成色斑。
第三,enhance_image里的顺序是固定的:去噪、CLAHE、伽马、饱和度、USM。这个顺序是我反复试过的,不能说绝对唯一,但对绝大多数自然图像是最稳的组合。如果先做伽马再CLAHE,效果会偏灰;如果锐化放在CLAHE前面,局部对比度增强会继续放大边缘过冲,很容易出现白边。
第四,饱和度调整放在伽马之后,是因为伽马在提升暗部时会改变颜色比例,此时统一调整饱和度,比在前面调整更接近最终的视觉效果。
3.3 命令行参数说明与效果自检
运行时,可以这样执行:
python enhance.py --input test_images/input --output test_images/output --gamma 0.8 --clip-limit 2.5 --sharpen-amount 1.3我整理了一份参数速查表,方便不同场景选择:
| 参数 | 默认值 | 适用场景 | 调整方向 |
|---|---|---|---|
--clip-limit | 2.0 | 自然照片、风景照 | 想更通透可以调大到2.5,画面发灰调小到1.5 |
--gamma | 0.85 | 偏暗照片 | 暗图调低到0.7,正常图用0.9到1.0 |
--sharpen-amount | 1.2 | 细节不足的截图 | 过锐调小到0.8,很糊调大到1.5 |
--saturation | 1.1 | 旧照片颜色暗淡 | 明显褪色可调到1.2,但别超过1.3 |
--denoise | 关闭 | 高ISO照片、夜间图 | 开启后处理时间会明显变长 |
怎么自检效果?我建议不要只靠眼睛看,可以做一个简单的客观判断:把原图和增强图都转成灰度,计算灰度直方图的标准差。标准差变大,说明对比度确实提升了;如果标准差没有明显变化,说明参数可能太保守。同时用cv2.PSNR比较图像与原图的差异,如果PSNR小于25,说明改动太大,很可能引入了明显失真。
4. 实际运行中的问题与排查速查表
4.1 高光过曝和暗部死黑
这是我在测试中最常见的问题,尤其是傍晚或逆光场景。症状是增强之后,天空云朵的位置变成一片纯白,人物头发的位置变成一团纯黑,完全看不到细节。
问题出在CLAHE和伽马同时发力,把动态范围拉伸过头了。
排查思路是:先单独禁用伽马,只用CLAHE,观察是否还有过曝;如果没有,说明是伽马把原本保留的高光细节压没了。反之,如果禁用CLAHE后问题依旧,说明是CLAHE的clip_limit过高。
总结一套公式化的调整方法:
- 高光过曝:把
clip_limit降低0.3到0.5,同时把gamma提高到0.9以上。 - 暗部死黑:把
gamma降到0.75以下,并且开启denoise,否则暗部噪声会非常显眼。 - 两者同时出现:先
clip_limit降到1.5,保证局部对比度温和;再设gamma=0.85,不要尝试一次性解决所有问题。
4.2 偏色与饱和度失衡
另一个高频问题是增强后颜色明显偏移,或者像被“洗过”一样发白。
我之前遇到过一整批夜景图处理完都偏紫,排查了很久才发现是LAB转换时,A和B通道本身包含了很多噪声,CLAHE只处理L通道,但后续伽马增强会让A、B通道的噪声也变得更加敏感。解决办法是在开启伽马的同时,增加一点饱和度补偿,同时把denoise打开,用非局部均值去噪压制色彩噪声。
如果只是饱和度不足,把--saturation从1.0提高到1.15基本就能解决。但有个禁忌:饱和度不要加到1.5,强行拉高会让肤色变成橙色、天空变成电光蓝,完全不可用。
4.3 中文路径读图失败
这是Windows用户极大概率会遇到的问题。OpenCV的imread在Windows下对中文路径处理不完善,经常返回None,而你根本找不到原因。
我的解决方式已经写进read_image函数里:
data = np.fromfile(str(path), dtype=np.uint8) image = cv2.imdecode(data, cv2.IMREAD_COLOR)先读文件为原始字节流,再解码成图像矩阵,完全绕过了imread的路径解析问题。写入时用cv2.imencode加tofile,同样能绕开中文保存路径的坑。如果你把脚本放在Linux服务器上跑,原生imread没问题,但为了代码可移植性,我建议还是统一用imdecode方案。
4.4 性能慢与大图内存不足
处理4K分辨率大图时,内存占用会突然飙升。原因是apply_usm里的高斯模糊和apply_clahe里的分块都会产生多个临时数组,叠加起来大约需要原始图像内存的4到6倍。
我的处理方法是分两种情况:
一种是对内存敏感的设备,先把图片缩放到长边2000像素再处理,处理完如果需要原分辨率输出,再对原图做一次轻度USM锐化。缩放后的增强参数可以直接沿用,因为CLAHE和伽马对分辨率不敏感。
另一种是性能敏感的设备,可以用多进程替代多线程。Python的多线程因为GIL限制,在CPU密集任务上基本没有加速效果。我在批量处理时用concurrent.futures.ProcessPoolExecutor,8核机器上能把吞吐提升到接近6倍。
fastNlMeansDenoisingColored是全流程最慢的环节,一张图可能要300毫秒到1秒。如果批处理文件特别多,建议默认关闭--denoise,只在肉眼可见有明显噪点时手动开启。
4.5 视频序列的增强节奏
我的程序做了静态图片批处理,但有人很快会问“能不能直接处理视频”。理论上可以,实际操作起来有好几个坑。视频帧逐帧做CLAHE时,因为每帧的直方图不同,画面明暗会闪烁,像老电影在抽风。伽马校正倒是稳定,但隐藏在帧间的噪声会被锐化放大。
稳妥的做法是:先从视频中抽帧,用我上面的工具批量增强,再用cv2.VideoWriter把增强后的帧拼成新视频。不要直接逐帧实时增强,否则帧率会掉到个位数,而且明暗不连续的问题会让你怀疑程序是不是真的在正常工作。
我这次没有把视频实时增强写进主程序,是因为这个功能定位不同:静态图看重画质,视频看重一致性和帧率。硬塞进同一个工具,两种需求都做不好。
4.6 常见问题速查
| 现象 | 原因 | 解决方案 |
|---|---|---|
| 画面太灰,和原图区别不大 | clip_limit太低 | 提高到2.5左右,检查直方图标准差 |
| 出现白边/黑边 | 锐化强度过高 | sharpen_amount降到1.0以下 |
| 整张图偏紫/偏绿 | 色彩噪声被放大 | 开启去噪,降低饱和度补偿到1.0 |
| 高光区变成纯白 | 伽马系数过低 | gamma提高到0.9以上 |
| 图片处理速度极慢 | 开启了非局部均值去噪 | 关闭去噪或缩小图尺寸 |
| 批处理时有几张图跳过 | 文件名后缀大小写不一致 | 同时收集.jpg和.JPG |
| 输出图片出现凭空的纹理 | CLAHE块太小 | tile_size从4调回8 |
5. 后续扩展与个人实操体会
5.1 从单张批处理到简单接口
程序写完以后,我给它加了一个非常朴素的做法:把所有图片处理逻辑封装成process_file函数,然后在自己写的一个简单脚本里逐张调用。这就够用了。
如果你想把它变成一个小服务,可以把process_file外包成一个FastAPI接口,接收上传图片和“增强强度”参数,返回处理后的图片。但我不建议在个人项目里搞得太复杂,因为图像处理本身就已经是CPU密集型,网上传图、排队、异步任务、结果存储,每一个环节都可能变成新的故障点。先用命令行工具把效果调好,再做接口,会比较顺。
5.2 图像增强处理Python程序里最容易被低估的参数
私心多说一个体会:最容易被低估的参数不是clip_limit,也不是gamma,而是sigma和tile_size这两个“空间尺度”参数。
很多人反复调gamma和sharpen_amount,效果就是不对,问题出在sigma太小或tile_size不合适。sigma=1.0的USM只增强高频细节,如果图片本身模糊得厉害,根本提取不出有效边缘;tile_size=8对常规照片合适,但如果是航拍图这种大场景图,说句实话,8x8还是16x16差异很明显,前者容易把地面纹理变得过于“颗粒感”。
我的经验是:遇到模糊图片,先尝试提高sigma而不是提高sharpen_amount。sigma=2.0加上sharpen_amount=1.0的效果,通常比sigma=1.0加上sharpen_amount=1.5更自然,因为后者本质上是在放大微小的像素噪声。
5.3 用了一周以后我的真实感受
这个图像增强处理Python程序我从头到尾跑了一周,处理了上千张图片,最大的感受是:图像增强不是“无脑变清晰”,而是一连串有条件的决策。你用CLAHE还是直方图均衡化,取决于原图是否有局部过曝;你用伽马还是线性乘系数,取决于暗部保留程度;你先去噪还是先锐化,取决于原图噪点水平和最终用途。
到最后,真正稳的反而是一套起手参数:clip_limit=2.0、gamma=0.85、sharpen_amount=1.2、saturation=1.1。绝大多数不极端的照片,用这套参数出来后都能正常交差。遇到特别个案,再去针对问题微调一个维度,不要四个参数一起改,否则你永远不知道到底是哪个参数救了图。
如果你也要写一个图像增强处理Python程序,我建议直接从上面的代码骨架开始,先跑通再谈优化。图像处理这种事,动手看效果比看一百篇文章都管用。