《零基础入门AI: 从轮廓查找到形态学变换(OpenCV图像预处理)》

发布于:2025-08-01 ⋅ 阅读:(16) ⋅ 点赞:(0)

本文针对图像处理初学者,详细解析OpenCV核心预处理技术,包含概念解释、可视化示例和关键代码片段,帮助您建立系统的图像处理知识体系。


一、图像轮廓特征查找:对象的几何描述

轮廓是连接图像中所有连续边界点的曲线,用于描述物体的形状特征。在OpenCV中,轮廓查找通常需要先进行二值化处理:

import cv2
import numpy as np

# 读取图像并转为灰度图
img = cv2.imread('object.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 二值化处理
_, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)

# 查找轮廓
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
  1. 外接矩形(Bounding Rect)

    • 能完全包围轮廓的正矩形,边与坐标轴平行(不旋转)
    • 特点:计算简单快速
    • 应用:物体定位、碰撞检测
    • cv2.boundingRect()返回矩形左上角坐标和宽高
    • 数学表示:(x, y, width, height)
    x, y, w, h = cv2.boundingRect(contour)
    cv2.rectangle(img, (x, y), (x+w, y+h), (0, 255, 0), 2)
    

    Bounding Rect

  2. 最小外接矩形(Rotated Rect)

    • 可旋转的矩形,面积最小且完全包围轮廓
    • 特点:更紧密包围对象
    • 应用:物体方向检测、精密测量
    • cv2.minAreaRect()返回中心点、尺寸和旋转角度
    • 数学表示:(center_x, center_y), (width, height), angle
    rect = cv2.minAreaRect(contour)
    box = cv2.boxPoints(rect)  # 获取四个顶点
    box = np.int0(box)
    cv2.drawContours(img, [box], 0, (0, 0, 255), 2)
    

    Rotated Rect

    • 完整示例
    import cv2
    import numpy as np
    
    # 读图
    num = cv2.imread('../images/num.png')
    
    # 转灰度
    gray = cv2.cvtColor(num, cv2.COLOR_BGR2GRAY)
    
    # 二值化处理
    _,thresh = cv2.threshold(gray,200,255,cv2.THRESH_BINARY_INV)
    
    # 查找轮廓
    contours,hierarchy = cv2.findContours(thresh,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
    
    # 绘制轮廓
    cv2.drawContours(num,contours,-1,(255,0,0),2)
    
    # 循环遍历每一条轮廓
    for cnt in contours:
        # 图像轮廓特征查找:最小外接矩形  (x , y)  w,h  angle
        result = cv2.minAreaRect(cnt)
        # print(result)
    
        # 处理点坐标
        points = cv2.boxPoints(result).astype(np.int32)
    
        # 绘制最小外接矩形
        cv2.drawContours(num,[points],0,(0,0,255),2)
    
        # 绘制外接矩形
        x,y,w,h = cv2.boundingRect(cnt)
        cv2.rectangle(num,(x,y),(x+w,y+h),(0,255,0),2)
    
    # 显示
    cv2.imshow('num',num)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    
  3. 最小外接圆(Min Enclosing Circle)

    • 完全包围轮廓的最小圆形
    • 特点:对不规则形状更有效
    • 应用:细胞分析、粒子检测
    • cv2.minEnclosingCircle()返回圆心和半径
    • 数学表示:(center_x, center_y), radius
    (x, y), radius = cv2.minEnclosingCircle(contour)
    center = (int(x), int(y))
    radius = int(radius)
    cv2.circle(img, center, radius, (255, 0, 0), 2)
    
    • 完整示例
    import cv2
    import numpy as np
    
    # 读图
    num = cv2.imread('../images/num.png')
    
    # 转灰度
    gray = cv2.cvtColor(num, cv2.COLOR_BGR2GRAY)
    
    # 二值化
    _,thresh = cv2.threshold(gray,127,255,cv2.THRESH_BINARY_INV)
    
    # 轮廓查找
    contours,hierarchy = cv2.findContours(thresh,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
    
    # 循环遍历轮廓
    for cnt in contours:
        # 获取最小外接圆
        (x,y),radius = cv2.minEnclosingCircle(cnt)
    
        # 处理点坐标
        (x, y) = np.int32((x,y))
        radius = np.int32(radius)
    
        # 绘制圆
        cv2.circle(num,(x,y),radius,(0,0,255),2)
    
    # 显示
    cv2.imshow('num',num)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    

应用场景:物体尺寸测量、方向识别、目标定位


二、直方图均衡化:提升图像对比度

直方图是图像像素强度的统计分布图:

  • X轴:像素强度值(0-255)
  • Y轴:具有该强度的像素数量
  • 应用:分析图像亮度分布、指导增强处理

直方图示例

  • 完整示例(绘制直方图)
import cv2
import numpy as np

# 读图
bg = cv2.imread('../images/bg.png')

# 创建一个黑色背景图,绘制直方图
black = np.zeros((256,256,3), np.uint8)

# 统计像素
hist = cv2.calcHist([bg], [1], None, [256], [0, 256])
# print(hist)
# print(hist[241,0])

# 获取直方图的最小值、最大值及其对应的最小值的位置索引、最大值的位置索引
minval, maxval, minloc, maxloc = cv2.minMaxLoc(hist)
# print(minval, maxval, minloc, maxloc)

# 定义直方图的高
h_hist = np.int32(256)

# 循环拿像素的个数
for i in range(256):   # [num]
    l = np.int32(hist[i].item() * h_hist / maxval)
    point1 = (i,h_hist - l)
    point2 = (i,h_hist)
    cv2.line(black, point1, point2, (0,255,0), 2)

# 显示
cv2.imshow('black', black)
cv2.waitKey(0)
cv2.destroyAllWindows()
  1. 直方图均衡化(HE)

    直方图均衡化通过重新分布像素强度来增强对比度:

    • 原理:将集中分布的强度值扩展到整个范围
    • 效果:增强图像细节,特别是暗区和亮区,增强整体对比度但可能放大噪声
    • 数学基础:累积分布函数(CDF)
    import cv2
    
    # 读图   读为灰度图
    img = cv2.imread('../images/zhifang.png',cv2.IMREAD_GRAYSCALE)
    
    # 直方图均衡化处理
    dst = cv2.equalizeHist(img)
    
    cv2.imshow('img',img)
    cv2.imshow('dst',dst)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    
  2. 自适应直方图均衡化(AHE)

    • 将图像分块后单独均衡化
    • 增强局部对比度但放大噪声
  3. 对比度受限自适应均衡化(CLAHE)

    • 限制局部对比度增强幅度
    • 最佳效果:增强细节同时抑制噪声
    import cv2
    
    # 读图
    img = cv2.imread('../images/zhifang.png',cv2.IMREAD_GRAYSCALE)
    
    # 创建一个clahe对象
    clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
    
    # 使用clahe 对象去调用apply()方法
    cl1 = clahe.apply(img)
    
    cv2.imshow('img', img)
    cv2.imshow('cl1', cl1)
    
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    

三、模板匹配:在图像中查找特定模式

在输入图像中滑动搜索与模板最相似的区域:

  • 原理:滑动模板图像,计算相似度
  • 应用:目标检测、工业质检、OCR预处理
import cv2
import numpy as np

# 读图
img = cv2.imread('../images/game.png')
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
temp = cv2.imread('../images/temp.png')
gray_temp = cv2.cvtColor(temp, cv2.COLOR_BGR2GRAY)

print(gray_img.shape)
print(gray_temp.shape)

# 获取 temp 的 h, w
h, w = gray_temp.shape

# 模板匹配  拿到匹配结果矩阵
#
res = cv2.matchTemplate(gray_img,gray_temp,cv2.TM_CCOEFF_NORMED)
# print(res.shape)

# 设置阈值
threshold = 0.8

# 获取匹配上的结果的索引
loc = np.where(res >= threshold)
# print(loc)

# 解包,拿到成对的(x, y) 的索引
# print(zip(*loc))
for i in zip(*loc):
    # print(i)
    x,y = i[1],i[0]
    # print(x,y)
    cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),1)

# 显示
cv2.imshow('temp',temp)
cv2.imshow('img',img)
cv2.waitKey(0)
cv2.destroyAllWindows()

匹配方法对比

六种主要相似度计算方法:

方法 公式 特点 适用场景 最佳匹配位置
平方差匹配(TM_SQDIFF) R=∑(T−I)2R = \sum(T-I)^2R=(TI)2 值越小越匹配 精确匹配 最小值
归一化平方差(TM_SQDIFF_NORMED) R=∑(T−I)2∑T2∑I2R = \frac{\sum(T-I)^2}{\sqrt{\sum T^2 \sum I^2}}R=T2I2 (TI)2 不受亮度影响 光照变化 最小值
相关匹配(TM_CCORR) R=∑(T×I)R = \sum(T \times I)R=(T×I) 值越大越匹配 快速匹配 最大值
归一化相关(TM_CCORR_NORMED) R=∑(T×I)∑T2∑I2R = \frac{\sum(T \times I)}{\sqrt{\sum T^2 \sum I^2}}R=T2I2 (T×I) 最常用 通用场景 最大值
相关系数(TM_CCOEFF) R=∑(T′×I′)∑T′2∑I′2R = \frac{\sum(T' \times I')}{\sqrt{\sum T'^2 \sum I'^2}}R=T′2I′2 (T×I) 消除均值影响 纹理匹配 最大值
归一化相关系数TM_CCOEFF_NORMED 同上,归一化 最鲁棒 复杂场景 最大值

四、霍夫变换:检测几何形状
  1. 霍夫变换原理

    霍夫变换将图像空间中的形状映射到参数空间:

    • 核心思想:“投票机制” - 图像中的点投票支持可能的形状
    • 优势:对噪声和部分遮挡鲁棒
  2. 霍夫直线变换

    检测图像中的直线(计算量大):

    • 参数空间(ρ,θ)(\rho, \theta)(ρ,θ)
      • ρ\rhoρ:原点到直线的距离
      • θ\thetaθ:直线与x轴的夹角
    import cv2
    import numpy as np
    
    # 读图
    img = cv2.imread('../images/huofu.png')
    
    # 灰度化
    gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    
    # 二值化
    _,thresh = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
    
    # 边缘检测
    edges = cv2.Canny(thresh,100,200)
    
    # 霍夫变换   返回的是[r, theta]
    lines = cv2.HoughLines(edges,0.8,np.pi/180,90)
    # print(lines)
    
    for line in lines:
        r, theta = line[0]
        sin_theta = np.sin(theta)
        cos_theta = np.cos(theta)
        x1,x2 = 0, img.shape[1]
        y1 = int((r - x1 *cos_theta) / sin_theta)
        y2 = int((r - x2 *cos_theta) / sin_theta)
        cv2.line(img,(x1,y1),(x2,y2),(255,0,0),2)
    
    cv2.imshow('img',img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    
  3. 统计概率霍夫变换(HoughLinesP)

    改进版直线检测:

    • 优点:只检测线段,直接返回线段端点
    • 效率:比标准霍夫变换更快
    import cv2
    import numpy as np
    
    # 读图
    img = cv2.imread('../images/huofu.png')
    
    # 灰度化
    gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    
    # 二值化
    _,thresh = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
    
    # 边缘检测
    edges = cv2.Canny(thresh,100,200)
    
    lines = cv2.HoughLinesP(edges,0.8,np.pi/180,90,minLineLength =50,maxLineGap=100)
    print(lines)
    
    for line in lines:
        x1,y1,x2,y2 = line[0]
        cv2.line(img,(x1,y1),(x2,y2),(255,0,0),2)
    
    cv2.imshow('img',img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    
  4. 霍夫圆变换

    检测图像中的圆形:

    • 参数空间(x,y,r)(x, y, r)(x,y,r)
    • 原理:三维累加器投票
    import cv2
    import numpy as np
    
    # 读图
    img = cv2.imread('../images/huofu.png')
    
    # 灰度化
    gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    
    # 二值化
    _,thresh = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
    
    # 边缘检测
    edges = cv2.Canny(thresh,100,200)
    
    # 霍夫圆变换
    circles = cv2.HoughCircles(edges,cv2.HOUGH_GRADIENT,1,10,param2 = 20)
    # print(circles)
    
    for circle in circles:
        x,y,radius = np.int32(circle[0])
        # print(x,y,radius)
        cv2.circle(img,(x,y),radius,(0,255,0),2)
    
    cv2.imshow('img',img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    

五、图像亮度变换:像素值调整
  1. 亮度变换基础

    调整图像整体或局部区域的亮度:

    • 全局调整:影响整个图像
    • 局部调整:影响特定区域
  2. 线性变换

    最基础的亮度调整方法:

    • 公式g(x,y)=α⋅f(x,y)+βg(x,y) = \alpha \cdot f(x,y) + \betag(x,y)=αf(x,y)+β
    • α\alphaα:控制对比度(>1增加,<1减小)
    • β\betaβ:控制亮度(正数变亮,负数变暗)
    import cv2
    
    # 读图
    cat = cv2.imread('../images/cat1.png')
    
    # 线性变换
    alpha = 1.5  # 对比度增益
    beta = 1    # 亮度增量
    dst = cv2.addWeighted(cat, alpha, cat, beta, 0)
    
    # 显示
    cv2.imshow('cat', cat)
    cv2.imshow('dst', dst)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    
  3. 直接像素修改

    通过遍历像素直接修改值:

    • 应用:创建自定义滤镜、选择性调整
    • 注意:效率较低,应谨慎使用
    bright_img = np.zeros_like(img)
    for y in range(img.shape[0]):
        for x in range(img.shape[1]):
            bright_img[y,x] = min(255, img[y,x] + 30)  # 增加亮度
    
  4. 滑条控制亮度

import cv2
import numpy as np

# 读图
cat = cv2.imread('../images/cat1.png')

#创建窗口  用于实现滑条
window_name = "slide"
cv2.namedWindow(window_name,cv2.WINDOW_AUTOSIZE)\

img = cv2.imread('../images/cat1.png')

def change(p):
    x = p/256*511-255
    dst = np.uint8(np.clip(img + x,0,255))
    cv2.imshow('dst',dst)
    print(x)

# 创建一个滑条
initial_value = 100
cv2.createTrackbar("add_p","slide",initial_value,255,change)

cv2.waitKey(0)
cv2.destroyAllWindows()

实际应用:使用向量化操作提高效率
bright_img = cv2.add(img, 30)


六、形态学变换:形状处理的基础
  1. 结构元素(核)

    形态学操作的核心组件:

    • 作用:定义邻域大小和形状
    • 类型:矩形、椭圆、十字形
    • 大小:奇数尺寸(3×3、5×5等)

    基于结构元素(核) 的形状操作:

    kernel = np.ones((5,5), np.uint8)  # 5x5正方形核
    
  2. 基本操作

操作 函数 效果 可视化 原理 应用场景
腐蚀 cv2.erode() 缩小物体边界,消除小点 Erosion 用核的最小值替换锚点 去除噪声
膨胀 cv2.dilate() 扩大物体边界,填补空洞 Dilation 用核的最大值替换锚点 连接断裂
开运算 cv2.morphologyEx(MORPH_OPEN) 先腐蚀后膨胀 → 去噪 Opening 先腐蚀后膨胀 背景分割
闭运算 cv2.morphologyEx(MORPH_CLOSE) 先膨胀后腐蚀 → 补洞 Closing 先膨胀后腐蚀 前景完整
礼帽 cv2.morphologyEx(MORPH_TOPHAT) 原图 - 开运算 → 突出亮区域 Top Hat 原图 - 开运算 背景均匀的亮特征
黑帽 cv2.morphologyEx(MORPH_BLACKHAT) 闭运算 - 原图 → 突出暗区域 Black Hat 闭运算 - 原图 背景均匀的暗特征
形态学梯度 cv2.morphologyEx(MORPH_GRADIENT) 膨胀图 - 腐蚀图 → 提取边缘 Gradient 膨胀 - 腐蚀 边缘检测
import cv2
import numpy as np

# 读图
car = cv2.imread('../images/car.png',cv2.IMREAD_GRAYSCALE)

# 定义核
kernel = np.ones((5,5),np.uint8)

# 腐蚀
erosion = cv2.erode(car,kernel,iterations = 1)

# 膨胀
dilation = cv2.dilate(car,kernel,iterations = 1)

# 开运算
opening = cv2.morphologyEx(car,cv2.MORPH_OPEN,kernel)

# 闭运算
close = cv2.morphologyEx(car,cv2.MORPH_CLOSE,kernel)

# 礼帽运算
tophat = cv2.morphologyEx(car,cv2.MORPH_TOPHAT,kernel)

# 黑帽运算
blackhat = cv2.morphologyEx(car,cv2.MORPH_BLACKHAT,kernel)

# 形态学梯度 膨胀和腐蚀差
gradient = cv2.morphologyEx(car,cv2.MORPH_GRADIENT,kernel)

cv2.imshow('car',car)
cv2.imshow('erosion',erosion)
cv2.imshow('dilation',dilation)
cv2.imshow('opening',opening)
cv2.imshow('close',close)
cv2.imshow('tophat',tophat)
cv2.imshow('blackhat',blackhat)
cv2.imshow('gradient',gradient)

cv2.waitKey(0)
cv2.destroyAllWindows()

核形状选择

  • cv2.MORPH_RECT 矩形核(默认)
  • cv2.MORPH_ELLIPSE 椭圆核
  • cv2.MORPH_CROSS 十字形核

形态学操作可视化

原始图像: [1 1 1 0 0]
          [1 1 1 0 0]
          [1 1 1 0 0]
          [0 0 0 0 0]
          
腐蚀后:   [1 1 0 0 0]
          [1 1 0 0 0]
          [0 0 0 0 0]
          [0 0 0 0 0]
          
膨胀后:   [1 1 1 1 0]
          [1 1 1 1 0]
          [1 1 1 0 0]
          [0 0 0 0 0]
技术组合应用示例
# 完整的预处理流程
img = cv2.imread('industrial_part.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 1. 亮度调整
adjusted = cv2.convertScaleAbs(gray, alpha=1.2, beta=20)

# 2. CLAHE增强对比度
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
clahe_img = clahe.apply(adjusted)

# 3. 中值滤波去噪
blurred = cv2.medianBlur(clahe_img, 5)

# 4. 形态学开运算去噪
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5))
opened = cv2.morphologyEx(blurred, cv2.MORPH_OPEN, kernel)

# 5. Canny边缘检测
edges = cv2.Canny(opened, 50, 150)

# 6. 查找轮廓并绘制最小外接矩形
contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for cnt in contours:
    rect = cv2.minAreaRect(cnt)
    box = cv2.boxPoints(rect)
    box = np.int0(box)
    cv2.drawContours(img, [box], 0, (0,0,255), 2)

cv2.imshow('Result', img)
cv2.waitKey(0)