第三章

图像处理基础:让模糊的照片变清晰

第二层 · 滤波/边缘/形态学 拍照搜书的“预处理”工序

一、为什么要做"图像预处理"

"小陈,你手机拍的书封面,直接丢给识别程序,八成效果很差。"老周打开一张拍糊的照片,"因为真实世界的照片有各种'脏':"

真实照片的"脏":
  ① 噪声:传感器/光线带来的颗粒(雪花点)
  ② 模糊:手抖、对焦不准
  ③ 光线差:太暗、太亮、有阴影
  ④ 倾斜/透视:拍照角度歪
  ⑤ 背景杂乱:书背后有别的书、桌子

  图像预处理(Preprocessing)= 在"识别"之前,先"洗图"
  目标:突出有用的信息,去掉干扰信息

  图像处理在视觉流程中的位置:
    原始图像 → 预处理(本章)→ 特征提取/识别(后面)→ 结果

"预处理是视觉流水线的'第一道工序'——图洗得干不干净,直接决定后面识别准不准。老话:'垃圾进,垃圾出'(Garbage in, garbage out)。"


二、灰度化与二值化:从"彩色"到"黑白"

"很多处理不需要颜色,先简化。"老周说:

import cv2

img = cv2.imread("book_cover.jpg")

# 灰度化:彩色 → 灰度(去掉颜色,保留亮度)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 二值化:灰度 → 纯黑/纯白(0 或 255)
# 全局阈值:像素值 > 127 变白,否则变黑
_, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)

# 自适应阈值:不同区域用不同阈值(应对光照不均,更常用)
adaptive = cv2.adaptiveThreshold(gray, 255,
                                 cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
                                 cv2.THRESH_BINARY, 11, 2)
为什么要灰度/二值化?
  ① 数据量小:3 通道 → 1 通道(算得快)
  ② 很多算法只需要亮度信息
  ③ 二值化让"前景/背景"分明(文字、形状提取)
  例子:OCR 之前,通常先灰度+二值化
        → 文字变成白底黑字,识别更准

  Otsu 阈值(自动找最佳阈值):
  _, binary = cv2.threshold(gray, 0, 255,
                            cv2.THRESH_BINARY + cv2.THRESH_OTSU)
  → 不用手动设阈值,算法自动找

"灰度化是'减负',二值化是'分家'——把图变成'背景/前景'两家人,后面好处理。OCR、文档扫描都靠它。"


三、滤波:去掉噪声的"磨皮术"

"照片上的颗粒噪声,怎么去掉?用滤波(Filter)。"老周说:

import cv2

# 均值滤波:每个像素 = 周围像素的平均值(简单,但会模糊)
blur = cv2.blur(gray, (5, 5))

# 高斯滤波:加权平均(中间权重高,去噪声更自然,常用)
gauss = cv2.GaussianBlur(gray, (5, 5), 0)

# 中值滤波:取中位数(去椒盐噪声最好,但慢)
median = cv2.medianBlur(gray, 5)

# 双边滤波:去噪声同时保留边缘(磨皮神器,贵)
bilateral = cv2.bilateralFilter(img, 9, 75, 75)
滤波的本质:
  用一个"小窗口"(卷积核)滑过图像
  每个像素 = 窗口内像素的某种"统计值"
  → 把"异常值"(噪声)抹平

  三种滤波对比:
    均值/高斯:平滑去噪(会模糊边缘)
    中值:去椒盐噪声强(保留边缘较好)
    双边:去噪+保边(美颜原理,但慢)

  调参:窗口大小(3x3 / 5x5 / 7x7)
    越大越模糊,去噪越强
    → 平衡:去噪效果 vs 细节保留

"滤波是'给图像磨皮'——去噪、平滑,但别把细节磨没了。'去噪 vs 保细节'的平衡,是图像处理的永恒主题。"


四、边缘检测:找出物体的"轮廓线"

"识别物体,先找轮廓。边缘检测就是干这个的。"老周说:

import cv2

# Canny 边缘检测(最常用,效果最好)
edges = cv2.Canny(gray, 50, 150)
# 参数:低阈值 50,高阈值 150
# 梯度大于 150 = 确定边缘;小于 50 = 非边缘;中间 = 看连接

# 边缘检测前:先高斯模糊去噪(Canny 对噪声敏感)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
edges = cv2.Canny(blurred, 50, 150)

# 其他边缘算子(了解)
# Sobel:一阶导数算子(水平/垂直边缘)
sobel_x = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
# Laplacian:二阶导数算子(对噪声敏感)
边缘 = 像素亮度"突变"的地方
  书和背景交界处:亮度跳变 → 边缘
  书脊上的字:也是边缘

  Canny 原理(经典五步):
    ① 高斯滤波去噪
    ② 计算梯度(方向和强度)
    ③ 非极大值抑制(边缘变细)
    ④ 双阈值(确定强/弱边缘)
    ⑤ 滞后连接(弱边缘跟着强边缘)

  用途:
    找物体轮廓 → 后续轮廓分析
    找书的边界 → 透视矫正(把歪的书扶正)
    车道线/文字线检测

"边缘是图像里'信息最密集'的地方——物体边界、文字笔画、纹理转折,全在边缘上。Canny 是边缘检测的'标配',面试常问原理。"


五、形态学操作:给形状"整形"

"二值化后的图形,往往有毛刺、有小洞——用形态学(Morphology)修整。"老周说:

import cv2
import numpy as np

kernel = np.ones((3, 3), np.uint8)

# 腐蚀:白色区域"瘦一圈"(去小毛刺、断开相连)
eroded = cv2.erode(binary, kernel, iterations=1)

# 膨胀:白色区域"胖一圈"(补小洞、连接断裂)
dilated = cv2.dilate(binary, kernel, iterations=1)

# 开运算 = 先腐蚀后膨胀(去小噪点,保留形状)
opening = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)

# 闭运算 = 先膨胀后腐蚀(补小洞,保留形状)
closing = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)
形态学用途:
  开运算:去掉孤立小点(文字提取时去噪点)
  闭运算:填平小缺口(把断开的文字笔画连起来)
  腐蚀/膨胀:控制区域大小

  场景:OCR 前
    文字二值化后 → 闭运算把"断笔画"连起来 → 识别更准

  kernel 大小:窗口大小,影响整形力度

"形态学是二值图的'整形手术'——去掉该去的、补上该补的。OCR、轮廓提取、区域分析都用得上。"


六、直方图与均衡化:拯救过暗/过亮的图

"拍照光线不好,图太暗怎么办?直方图均衡化救场。"老周说:

import cv2

# 直方图:统计每个亮度值有多少像素
# 横轴 0-255 亮度,纵轴 像素数量
hist = cv2.calcHist([gray], [0], None, [256], [0, 256])

# 直方图均衡化:把分布"拉平"(暗的变亮,亮的微调)
# 让对比度更均匀
equalized = cv2.equalizeHist(gray)

# 彩色图均衡(YUV 的 Y 通道做,再转回)
yuv = cv2.cvtColor(img, cv2.COLOR_BGR2YUV)
yuv[:, :, 0] = cv2.equalizeHist(yuv[:, :, 0])
img_eq = cv2.cvtColor(yuv, cv2.COLOR_YUV2BGR)
为什么图会"灰蒙蒙"?
  像素集中在中间亮度(对比度低)
  → 均衡化把它"拉开"(暗的更暗、亮的更亮)
  → 细节显现

  用途:
    逆光拍的封面 → 均衡化 → 字和图案清楚
    监控视频(夜间)→ 预处理常用

  注意:均衡化会改变颜色观感
    → 后续要颜色信息时慎用

"直方图是'图像的体检报告'——一看分布就知道图是过暗、过亮还是对比度低。均衡化是'对比度治疗',拍照搜书、OCR 前的常客。"


七、完整预处理流程:一张"脏图"的逆袭

老周把本章所有技术串成一个完整案例:

import cv2
import numpy as np

def preprocess_for_ocr(image_path):
    """OCR 前的标准预处理流程"""
    img = cv2.imread(image_path)

    # 1. 灰度化(去掉颜色干扰)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    # 2. 去噪(高斯滤波,保留边缘)
    blur = cv2.GaussianBlur(gray, (3, 3), 0)

    # 3. 直方图均衡化(提升对比度,文字更清晰)
    eq = cv2.equalizeHist(blur)

    # 4. 二值化(自适应阈值,应对光照不均)
    binary = cv2.adaptiveThreshold(
        eq, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
        cv2.THRESH_BINARY, 11, 2)

    # 5. 形态学闭运算(连接断笔画)
    kernel = np.ones((3, 3), np.uint8)
    result = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)

    return result

# 脏图 → 干净二值图(后面 OCR 直接用)
clean = preprocess_for_ocr("blurry_photo.jpg")
cv2.imwrite("clean_for_ocr.png", clean)

"这套流程是视觉工程的'家常菜'——灰度→去噪→均衡→二值→形态学,五步走。每个视觉项目的第一版,几乎都从这套流程开始。"


八、章末:老周的第三章总结

第二层:图像处理基础
├── 预处理 = 识别前的"洗图"(垃圾进垃圾出)
├── 灰度化/二值化:减负 + 前后景分家(Otsu 自动阈值)
├── 滤波:均值/高斯(去噪)/中值(椒盐)/双边(保边)
├── 边缘检测:Canny(高斯→梯度→双阈值)
├── 形态学:腐蚀/膨胀/开运算/闭运算(整形)
├── 直方图与均衡化:提升对比度(救过暗图)
└── 标准五步:灰度→去噪→均衡→二值→形态学
第四层(预告):特征提取 —— 两张图怎么"找相同"

"小陈,现在你能把'脏图'洗成'干净图'了——这是视觉开发的基本功。"老周说,"但预处理只能'洗图',不能'认图'。怎么判断两张图'是不是同一个东西'?比如拍照搜书,怎么知道你拍的书封和数据库里哪本书最像?下一章:特征提取与匹配——给图像'提取指纹'。"

✌ 语言