OpenCV核心操作

前言

大家好,我是Loong,今天想和大家分享一些 OpenCV 的核心操作,帮助大家更好地理解和应用这个强大的图像处理库。OpenCV 提供了丰富的功能和工具,可以轻松地处理图像和视频数据,并应用于目标检测、图像识别、图像分割等多个领域。

从基础到进阶:OpenCV 的核心操作

OpenCV 的核心操作涵盖了图像处理的各个方面,从基础的图像读写到进阶的图像分割,我们可以一步步学习并掌握这些技能。

  • 图像读写:学习如何使用 OpenCV 读取、显示和保存图像,并理解 OpenCV 中图像的 BGR 格式。
  • 色彩空间与转换:了解 RGB、HSV 等常见色彩空间,并学习如何进行色彩空间转换,例如将 RGB 图像转换为灰度图像。
  • 基本图形绘制:掌握 OpenCV 绘制直线、矩形、圆、椭圆、多边形和文字的方法,为图像添加标注和注释。
  • 阈值操作:学习二值化、OTSU 二值化、自适应阈值等操作,将图像分割成不同的区域,并进行后续处理。
  • 图像运算:掌握图像的加减法、混合、按位运算等操作,实现图像融合、图像掩码等功能。
  • 图像几何变换:学习图像的缩放、旋转、平移、翻转、仿射变换和透视变换等操作,实现图像的几何变换。
  • 膨胀/腐蚀/开/闭/梯度/礼帽/黑帽:掌握图像形态学操作,用于去除噪声、补漏洞、提取边缘等。
  • 图像滤波:了解滤波的概念,学习均值滤波、高斯滤波、中值滤波、双边滤波等平滑操作,以及锐化操作和梯度操作。
  • 傅里叶变换:理解傅里叶变换的原理和作用,使用 OpenCV 和 Numpy 进行图像的傅里叶变换和逆变换。
  • 直方图:学习如何绘制一维和二维直方图,并进行直方图均衡化和自适应直方图均衡化操作。
  • 直方图反向投影:了解直方图反向投影的原理和应用,用于图像分割和目标识别。
  • 图像金字塔:学习高斯金字塔和拉普拉斯金字塔的概念和计算方法,并应用于图像融合等操作。
  • 模板匹配:掌握模板匹配的原理和方法,在一幅图像中查找目标对象。
  • Canny 边缘提取算法:学习 Canny 算法的原理和步骤,并使用 OpenCV 进行边缘提取。
  • 轮廓:理解什么是轮廓,并学习查找、绘制、分析和匹配轮廓的方法。
  • Hough 变换:了解 Hough 变换的原理,并使用 OpenCV 进行直线和圆的检测。
  • 图像分割:学习分水岭算法等图像分割方法,将图像分割成不同的区域。

注意: 由于 OpenCV 版本更新较快,代码中使用的函数或参数可能与您使用的版本略有差异。建议您参考 OpenCV 官方文档或使用代码补全功能进行确认。

目录

前言

1. 图像读写

2. 色彩空间与转换

2.1 色彩空间的转换

2.2 通道分离

2.3 HSV颜色空间

3. 基本图形绘制

4. 阈值操作

4.1 OTSU二值化

4.2 简单阈值

4.3 自适应阀值

5. 图像上的运算

5.1 加减法

5.2 图像混合

5.3 按位运算

6. 图像的几何变换

6.1 Resize/Transpose/Flip

6.2 仿射变换

6.3 透视变换

6.4 膨胀 / 腐蚀

6.5 开 / 闭运算

6.6 梯度操作

6.7 礼帽 / 黑帽操作

7. 图像滤波

7.1 卷积

7.2 平滑算子

7.3 锐化算子

7.4 梯度算子

8、傅里叶变换

8.1 Numpy中傅里叶变换

8.2 OpenCV傅里叶变换

9、直方图

9.1 直方图

9.2 直方图均衡化

9.3 自适应直方图均衡化

9.4 2D直方图

9.5 直方图反向投影

10、图像金字塔

10.1 高斯金字塔

10.2 拉普拉斯金字塔

11、 模板匹配

11.1 单对象匹配

11.2 多对象匹配

11.3 无缝融合

12、Canny边缘提取算法

13、轮廓

13.1 轮廓的查找与绘制

13.2 面积,周长,重心

13.3 轮廓近似

13.4 凸包与凸性检测

13.5 边界检测

13.6 轮廓性质

13.7 对象掩码

13.8 形状匹配

14、霍夫变换

14.1 直线检测

14.2 圆检测

14.3 原理详解

15、图像分割

结语


1. 图像读写

图像读取的返回值是一个numpy数据类型

  • 读取图像: 使用 cv2.imread() 函数读取图像文件,返回值为 NumPy 数组。
  • 显示图像: 使用 cv2.imshow() 函数显示图像,需要指定窗口名称和图像数据。
  • 保存图像: 使用 cv2.imwrite() 函数保存图像,需要指定保存路径、图像数据和编码格式(默认为 JPEG)。

示例代码:

import cv2

# 读取图像
img = cv2.imread("1.jpg")

# 显示图像
cv2.imshow("Image", img)
cv2.waitKey(0)  # 等待按键后关闭窗口
cv2.destroyAllWindows()

# 创建图像并保存
import numpy as np import cv2
img = np.empty((200, 200, 3), np.uint8)
img[..., 0] = 255 img[..., 1] = 0
img[..., 2] = 0
# 保存图像
cv2.imwrite("2.jpg", img)

# 读取视频或摄像头

# 创建 VideoCapture 对象
cap = cv2.VideoCapture("1.mp4")

# 检查视频是否打开成功
if not cap.isOpened():
    print("Error: Could not open video")
    exit()

# 读取视频帧
while cap.isOpened():
    ret, frame = cap.read()
    cv2.imshow('frame', frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()

2. 色彩空间与转换

  • 色彩空间: OpenCV 支持多种色彩空间,如 RGB、BGR、GRAY、HSV、YUV 等。
  • 色彩空间转换: 使用 cv2.cvtColor() 函数进行色彩空间转换,例如将 BGR 转换为 HSV。

2.1 色彩空间的转换

示例代码

# 读取图像
img = cv2.imread("1.jpg")

# 转换为 HSV
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

# 显示图像
cv2.imshow("HSV Image", hsv)
cv2.waitKey(0)
cv2.destroyAllWindows()

2.2 通道分离

示例代码

import cv2

# 读取图像
img = cv2.imread(r"1.jpg")

# 将图像的红色和绿色通道设置为 0
img[..., 0] = 0
img[..., 1] = 0

# 显示图像
cv2.imshow("dst show", img)

# 等待按键后关闭窗口
cv2.waitKey(0)

# 关闭所有 OpenCV 窗口
cv2.destroyAllWindows()

2.3 HSV颜色空间

 HSV 格式中, H(色彩/色度)的取值范围是 [0  179] S (饱和度)的取值范围 [0 255] V(亮 度)的取值范围 [0 255]。但是不同的软件使用的值可能不同。所以当你需要拿 OpenCV HSV 值与其他软件的 HSV 值进行对比时, 一定要记得归一化。 HSV中,我们按颜色提取变得会更加方便。

示例代码

import cv2
import numpy as np

# 读取图像
img = cv2.imread("3.jpg")

# 将图像转换为 HSV 色彩空间
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

# 定义蓝色范围的 HSV 阈值
lower_blue = np.array([100, 200, 100])
upper_blue = np.array([200, 255, 200])

# 创建掩码,掩码中蓝色区域为白色,其他区域为黑色
mask = cv2.inRange(hsv, lower_blue, upper_blue)

# 将掩码与原图像进行位运算,得到只包含蓝色区域的图像
res = cv2.bitwise_and(img, img, mask=mask)

# 显示原图像和掩码图像
cv2.imshow('frame', img)
cv2.imshow('mask', mask)

# 等待按键后关闭窗口
cv2.waitKey(0)

# 关闭所有 OpenCV 窗口
cv2.destroyAllWindows()

# frame: 原始图像。
# mask: 蓝色区域的掩码图像,其中蓝色区域为白色,其他区域为黑色。

3. 基本图形绘制

OpenCV 提供了丰富的函数,可以绘制各种基本图形,例如直线、矩形、圆、椭圆、多边形和文字等。

示例代码

import cv2

# 创建一个白色背景图像
img = np.zeros((300, 500, 3), dtype=np.uint8)
img[:] = 255

# 绘制直线
cv2.line(img, (50, 50), (450, 50), (0, 0, 255), 2)

# 绘制矩形
cv2.rectangle(img, (50, 100), (200, 200), (0, 255, 0), 2)

# 绘制圆
cv2.circle(img, (100, 250), 50, (255, 0, 0), -1)

# 绘制椭圆
cv2.ellipse(img, (300, 250), (100, 50), 0, 0, 360, (0, 0, 255), -1)

# 定义多边形顶点坐标
pts = np.array([[200, 300], [300, 350], [250, 400]], np.int32)

# 绘制多边形
cv2.polylines(img, [pts], True, (255, 0, 0), 2)

# 绘制文字
cv2.putText(img, 'OpenCV', (50, 280), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2, cv2.LINE_AA)

# 显示图像
cv2.imshow("Image", img)
cv2.waitKey(0)
cv2.destroyAllWindows()

4. 阈值操作

  • 理解二值化: 将图像中的像素值分为两类,高于阈值的像素值为 255(白色),低于阈值的像素值为 0(黑色)。
  • OTSU 二值化: 自动选择最佳阈值进行二值化。
  • 简单阈值: 手动指定阈值进行二值化。
  • 自适应阈值: 根据图像局部区域的像素值自适应地选择阈值进行二值化。

4.1 OTSU二值化

示例代码

# 读取图像
img = cv2.imread("1.jpg", 0)  # 读取灰度图像

# OTSU 二值化
ret, thresh = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)

# 显示图像
cv2.imshow("Threshold Image", thresh)
cv2.waitKey(0)
cv2.destroyAllWindows()

4.2 简单阈值

与名字一样,这种方法非常简单。但像素值高于阈值时,我们给这个像素赋予一个新值(可能是白

),否则我们给它赋予另外一种颜色(也许是黑色)。这个函数就是 cv2.threshhold()。这个函数的  第一个参数就是原图像,原图像应该是灰度图。第二个参数就是用来对像素值进行分类的阈值。第三个 参数就是当像素值高于(有时是小于)阈值时应该被赋予的新的像素值。  OpenCV提供了多种不同的阈  值方法,这是有第四个参数来决定的。这些方法包括:

  • • cv2.THRESH_BINARY
  • • cv2.THRESH_BINARY_INV
  • • cv2.THRESH_TRUNC
  • • cv2.THRESH_TOZERO
  • • cv2.THRESH_TOZERO_INV

示例代码

import cv2
import numpy as np
from matplotlib import pyplot as plt

# 读取图像
img = cv2.imread('5.jpg', 0)

# 定义阈值
threshold = 127

# 使用不同的阈值方法进行阈值处理
ret, thresh1 = cv2.threshold(img, threshold, 255, cv2.THRESH_BINARY)
ret, thresh2 = cv2.threshold(img, threshold, 255, cv2.THRESH_BINARY_INV)
ret, thresh3 = cv2.threshold(img, threshold, 255, cv2.THRESH_TRUNC)
ret, thresh4 = cv2.threshold(img, threshold, 255, cv2.THRESH_TOZERO)
ret, thresh5 = cv2.threshold(img, threshold, 255, cv2.THRESH_TOZERO_INV)

# 创建标题列表
titles = ['Original Image', 'BINARY', 'BINARY_INV', 'TRUNC', 'TOZERO', 'TOZERO_INV']

# 创建图像列表
images = [img, thresh1, thresh2, thresh3, thresh4, thresh5]

# 创建子图并显示图像
for i in range(6):
    plt.subplot(2, 3, i + 1), plt.imshow(images[i], 'gray')
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])

# 显示所有图像
plt.show()

4.3 自适应阀值

前面的部分我们使用是全局阈值,整幅图像采用同一个数作为阈值。当时这种方法并不适应与所有情

况,尤其是当同一幅图像上的不同部分的具有不同亮度时。这种情况下我们需要采用自适应阈值。此时 的阈值是根据图像上的每一个小区域计算与其对应的阈值。因此在同一幅图像上的不同区域采用的是不 同的阈值,从而使我们能在亮度不同的情况下得到更好的结果。

这种方法需要我们指定三个参数,返回值只有一个。

Adaptive Method- 指定计算阈值的方法。

– cv2.ADPTIVE_THRESH_MEAN_C:阈值取自相邻区域的平均值

– cv2.ADPTIVE_THRESH_GAUSSIAN_C:阈值取值相邻区域的加权和,权重为一个高斯窗口。

 Block Size - 邻域大小(用来计算阈值的区域大小)。

• C - 这就是是一个常数,阈值就等于的平均值或者加权平均值减去这个常数 

示例代码

import cv2
import numpy as np
from matplotlib import pyplot as plt

# 读取图像
img = cv2.imread('4.jpg', 0)

# 高斯模糊
img = cv2.GaussianBlur(img, (5, 5), 0)

# 全局阈值处理
ret, th1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)

# 自适应阈值处理(均值)
th2 = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 11, 2)

# 自适应阈值处理(高斯)
th3 = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2)

# 创建标题列表
titles = ['Original Image', 'Global Thresholding (v = 127)',
'Adaptive Mean Thresholding', 'Adaptive Gaussian Thresholding']

# 创建图像列表
images = [img, th1, th2, th3]

# 创建子图并显示图像
for i in range(4):
    plt.subplot(2, 2, i + 1), plt.imshow(images[i], 'gray')
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])

# 显示所有图像
plt.show()

5. 图像上的运算

  • 加减法: 对图像进行像素级的加法或减法运算。
  • 图像混合: 将两幅图像按照不同的权重进行混合。
  • 按位运算: 对图像进行 AND、OR、NOT、XOR 等按位运算。

5.1 加减法

示例代码

import cv2
import numpy as np

# 创建一个包含单个元素的数组
x = np.uint8([250])
y = np.uint8([10])

# 执行加法操作
print(cv2.add(x, y))

# 执行减法操作
print(cv2.subtract(x, y))

5.2 图像混合

这其实也是加法,但是不同的是两幅图像的权重不同,这就会给人一种混合或者透明的感觉。图像混合 的计算公式如下:

g (x) = (1  α) f0 (x) + α f1 (x)

通过修改 α 的值(0 → 1),可以实现非常酷的混合。

现在我们把两幅图混合在一起。第一幅图的权重是 0.7,第二幅图的权重是 0.3。函数 cv2.addWeighted() 可以按下面的公式对图片进行混合操作。

dst = α · img1 + β · img2 + γ

这里 γ 的取值为 0

示例代码

import cv2

# 读取两幅图像
img1 = cv2.imread('1.jpg')
img2 = cv2.imread('2.jpg')

# 使用 cv2.addWeighted() 函数进行加权叠加
# 参数说明:
# img1: 第一个图像
# alpha: 第一个图像的权重
# img2: 第二个图像
# beta: 第二个图像的权重
# gamma: 缩放因子
dst = cv2.addWeighted(img1, 0.7, img2, 0.3, 0)

# 显示叠加后的图像
cv2.imshow('dst', dst)

# 等待按键后关闭窗口
cv2.waitKey(0)

# 关闭所有 OpenCV 窗口
cv2.destroyAllWindows()

5.3 按位运算

这里包括的按位操作有: AND OR  NOT XOR 等。当我们提取图像的一部分,选择非矩形 ROI 时这 些操作会很有用(下一章你就会明白)。下面的例子就是教给我们如何改变一幅图的特定区域。

  • import cv2
    
    # 读取两幅图像
    img1 = cv2.imread('1.jpg')
    img2 = cv2.imread('9.jpg')
    
    # 获取 img2 的尺寸
    rows, cols, channels = img2.shape
    
    # 计算 img1 中与 img2 尺寸相同的区域
    roi = img1[0:rows, 0:cols]
    
    # 将 img2 转换为灰度图像
    img2gray = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
    
    # 对灰度图像进行阈值处理,得到二值化掩码
    ret, mask = cv2.threshold(img2gray, 10, 255, cv2.THRESH_BINARY)
    mask_inv = cv2.bitwise_not(mask)
    
    # 显示二值化掩码
    # cv2.imshow("mask_inv", mask_inv)
    
    # 使用掩码从 img1 中提取背景区域
    img1_bg = cv2.bitwise_and(roi, roi, mask=mask_inv)
    # 显示提取的背景区域
    # cv2.imshow("img1_bg", img1_bg)
    
    # 使用掩码从 img2 中提取前景区域
    img2_fg = cv2.bitwise_and(img2, img2, mask=mask)
    # 显示提取的前景区域
    # cv2.imshow("img2_fg", img2_fg)
    
    # 将前景区域和背景区域叠加到 img1 中
    dst = cv2.add(img1_bg, img2_fg)
    img1[0:rows, 0:cols] = dst
    
    # 显示最终结果
    cv2.imshow('res', img1)
    
    # 等待按键后关闭窗口
    cv2.waitKey(0)
    
    # 关闭所有 OpenCV 窗口
    cv2.destroyAllWindows()
    

6. 图像的几何变换

  • Resize: 调整图像大小。
  • Transpose: 沿着矩阵的副对角线翻转图像。
  • Flip: 沿着水平或垂直方向翻转图像。
  • 仿射变换: 对图像进行缩放、旋转、平移、倾斜、镜像等操作。
  • 透视变换: 将图像从一种视角变换到另一种视角。
  • 膨胀:扩大图像中目标区域的大小。
  • 腐蚀:缩小图像中目标区域的大小。
  • 开运算:先腐蚀后膨胀,用于去除小的物体和噪声。
  • 闭运算:先膨胀后腐蚀,用于填充物体内部的孔洞。
  • 梯度操作:计算图像的边缘强度,得到梯度图像。
  • 礼帽操作:从原图像中减去开运算后的图像,得到明亮区域。
  • 黑帽操作:从原图像中减去闭运算后的图像,得到暗淡区域。

6.1 Resize/Transpose/Flip

OpenCV 提供了几个函数来执行图像的缩放(Resize)、转置(Transpose)和翻转(Flip)操作。

示例代码

import cv2

# 读取图像
image = cv2.imread('1.jpg')

# 缩放图像
scaled_image = cv2.resize(image, (300, 200))

# 转置图像
transposed_image = cv2.transpose(scaled_image)

# 水平翻转图像
flipped_horizontal = cv2.flip(transposed_image, 1)

# 垂直翻转图像
flipped_vertical = cv2.flip(flipped_horizontal, 0)

# 显示翻转后的图像
cv2.imshow('Final Image', flipped_vertical)
cv2.waitKey(0)
cv2.destroyAllWindows()

6.2 仿射变换

任意一个二维图像,我们乘以一个仿射矩阵,就能得到仿射变换后的图像。变换包含:缩放、旋转、平 移、倾斜、镜像。

示例代码

import cv2
import numpy as np

# 读取图像
src = cv2.imread('1.jpg')

# 获取图像的尺寸
rows, cols, channels = src.shape

# 定义仿射变换矩阵
# M = np.float32([[1, 0, 50], [0, 1, 50]])
# M = np.float32([[0.5, 0, 0], [0, 0.5, 0]])
# M = np.float32([[-0.5, 0, cols // 2], [0, 0.5, 0]])
# M = np.float32([[1, 0.5, 0], [0, 1, 0]])
# M = cv2.getRotationMatrix2D((cols / 2, rows / 2), 45, 0.7)

# 应用仿射变换
dst = cv2.warpAffine(src, M, (cols, rows))

# 显示原始图像和变换后的图像
cv2.imshow('src pic', src)
cv2.imshow('dst pic', dst)

# 等待按键后关闭窗口
cv2.waitKey(0)

# 关闭所有 OpenCV 窗口
cv2.destroyAllWindows()

6.3 透视变换

透视变换(Perspective Transformation)是一种将图像从一个平面转换到另一个平面的几何变换。这种变换在计算机视觉和图像处理中非常有用,因为它可以用来校正图像中的透视失真,或者将一个平面上的图像内容变换到另一个平面上,同时保持原始图像的形状和大小。

import cv2
import numpy as np

# 读取图像
img = cv2.imread("4.jpg")

# 定义两个点集,用于透视变换
pts1 = np.float32([[25, 30], [179, 25], [12, 188], [189, 190]])
pts2 = np.float32([[0, 0], [200, 0], [0, 200], [200, 200]])

# 计算透视变换矩阵
M = cv2.getPerspectiveTransform(pts1, pts2)

# 应用透视变换
dst = cv2.warpPerspective(img, M, (200, 201))

# 显示原始图像和变换后的图像
cv2.imshow("src", img)
cv2.imshow("dst", dst)

# 等待按键后关闭窗口
cv2.waitKey(0)

# 关闭所有 OpenCV 窗口
cv2.destroyAllWindows()

6.4 膨胀 / 腐蚀

膨胀(Dilation)腐蚀(Erosion)是两种基本的形态学操作,它们用于改变图像的形状和结构。
1. 膨胀(Dilation):
   膨胀是一种填充操作,它通过一个结构元素(通常是方形或圆形)在图像上滑动,并将结构元素覆盖的像素值替换为结构元素的最小值。这意味着如果结构元素的中心像素值小于结构元素覆盖的所有像素值,那么中心像素值将被替换为结构元素的最小值。这种操作可以用来扩大图像中的物体,去除小的孔洞,或者填充噪声。
2. 腐蚀(Erosion):
   腐蚀是一种消除操作,它通过一个结构元素在图像上滑动,并将结构元素覆盖的像素值替换为结构元素的最大值。这意味着如果结构元素的中心像素值大于结构元素覆盖的所有像素值,那么中心像素值将被替换为结构元素的最大值。这种操作可以用来缩小图像中的物体,去除噪声,或者平滑图像的边缘。
在 OpenCV 中,`cv2.dilate()` 函数用于执行膨胀操作,而 `cv2.erode()` 函数用于执行腐蚀操作。这些函数接受一个图像和一个结构元素作为输入,并返回经过膨胀或腐蚀处理后的图像。

示例代码

import cv2
import numpy as np

# 读取图像
image = cv2.imread('1.jpg', cv2.IMREAD_GRAYSCALE)

# 定义结构元素,用于膨胀和腐蚀操作
kernel = np.ones((5, 5), np.uint8)

# 腐蚀操作
eroded_image = cv2.erode(image, kernel, iterations=1)

# 膨胀操作
dilated_image = cv2.dilate(image, kernel, iterations=1)

# 显示原始图像、腐蚀后的图像和膨胀后的图像
cv2.imshow('Original Image', image)
cv2.imshow('Eroded Image', eroded_image)
cv2.imshow('Dilated Image', dilated_image)

# 等待按键后关闭窗口
cv2.waitKey(0)
cv2.destroyAllWindows()

6.5 开 / 闭运算

开运算(Opening)闭运算(Closing)是形态学操作中的组合操作,它们分别由腐蚀和膨胀操作组成。

  1. 开运算(Opening): 开运算是一种先腐蚀后膨胀的操作,它通过先用一个结构元素对图像进行腐蚀,然后用同一个结构元素对腐蚀后的图像进行膨胀。这种操作可以用来去除图像中的噪声和小的物体,同时保持图像中的大物体不变。

  2. 闭运算(Closing): 闭运算是一种先膨胀后腐蚀的操作,它通过先用一个结构元素对图像进行膨胀,然后用同一个结构元素对膨胀后的图像进行腐蚀。这种操作可以用来填充图像中的小孔洞,同时保持图像中的大物体不变。

示例代码

import cv2
import numpy as np

# 读取图像
image = cv2.imread('1.jpg', cv2.IMREAD_GRAYSCALE)

# 定义结构元素,用于开运算和闭运算
kernel = np.ones((5, 5), np.uint8)

# 开运算
opened_image = cv2.morphologyEx(image, cv2.MORPH_OPEN, kernel)

# 闭运算
closed_image = cv2.morphologyEx(image, cv2.MORPH_CLOSE, kernel)

# 显示原始图像、开运算后的图像和闭运算后的图像
cv2.imshow('Original Image', image)
cv2.imshow('Opened Image', opened_image)
cv2.imshow('Closed Image', closed_image)

# 等待按键后关闭窗口
cv2.waitKey(0)
cv2.destroyAllWindows()

6.6 梯度操作

膨胀减去腐蚀(也称为形态学梯度)是一种形态学操作,它通过先对图像进行膨胀操作,然后对原始图像进行腐蚀操作,最后将膨胀结果减去腐蚀结果。这种操作可以用来增强图像中的边缘,同时保留图像中的噪声。

示例代码

import cv2
import numpy as np

# 读取图像
image = cv2.imread('1.jpg', cv2.IMREAD_GRAYSCALE)

# 定义结构元素,用于膨胀和腐蚀操作
kernel = np.ones((5, 5), np.uint8)

# 膨胀操作
dilated_image = cv2.dilate(image, kernel, iterations=1)

# 腐蚀操作
eroded_image = cv2.erode(image, kernel, iterations=1)

# 形态学梯度操作
gradient_image = cv2.subtract(dilated_image, eroded_image)

# 显示原始图像、膨胀后的图像、腐蚀后的图像和梯度后的图像
cv2.imshow('Original Image', image)
cv2.imshow('Dilated Image', dilated_image)
cv2.imshow('Eroded Image', eroded_image)
cv2.imshow('Gradient Image', gradient_image)

# 等待按键后关闭窗口
cv2.waitKey(0)
cv2.destroyAllWindows()

6.7 礼帽 / 黑帽操作

礼帽操作(Top Hat Transform)黑帽操作(Black Hat Transform)是形态学操作中的两种组合操作,它们分别由开运算和闭运算组成。

  1. 礼帽操作(Top Hat Transform): 礼帽操作是一种先对图像进行开运算,然后从原图像中减去开运算后的图像。这种操作可以用来增强图像中的明亮区域,因为明亮区域在开运算后会被缩小,而原图像中的明亮区域减去这个缩小后的图像,结果就是增强的明亮区域。

  2. 黑帽操作(Black Hat Transform): 黑帽操作是一种先对图像进行闭运算,然后从原图像中减去闭运算后的图像。这种操作可以用来增强图像中的暗淡区域,因为暗淡区域在闭运算后会被扩大,而原图像中的暗淡区域减去这个扩大后的图像,结果就是增强的暗淡区域。

示例代码

import cv2
import numpy as np

# 读取图像
image = cv2.imread('1.jpg', cv2.IMREAD_GRAYSCALE)

# 定义结构元素,用于开运算和闭运算
kernel = np.ones((5, 5), np.uint8)

# 开运算
opened_image = cv2.morphologyEx(image, cv2.MORPH_OPEN, kernel)

# 闭运算
closed_image = cv2.morphologyEx(image, cv2.MORPH_CLOSE, kernel)

# 礼帽操作
top_hat_image = cv2.subtract(image, opened_image)

# 黑帽操作
black_hat_image = cv2.subtract(image, closed_image)

# 显示原始图像、开运算后的图像、闭运算后的图像、礼帽后的图像和黑帽后的图像
cv2.imshow('Original Image', image)
cv2.imshow('Opened Image', opened_image)
cv2.imshow('Closed Image', closed_image)
cv2.imshow('Top Hat Image', top_hat_image)
cv2.imshow('Black Hat Image', black_hat_image)

# 等待按键后关闭窗口
cv2.waitKey(0)
cv2.destroyAllWindows()

7. 图像滤波

  1. 滤波过程:滤波通过一个特定的滤波器(也称为卷积核)对信号或图像进行卷积操作。这个过程可以去除噪声、锐化图像、平滑图像或者提取特定频率范围的信号。

  2. 滤波器:滤波器是决定滤波效果的关键,不同的滤波器适用于不同的滤波需求。常见的滤波器包括高斯滤波器、Sobel算子、Laplacian算子、中值滤波器等。

  3. 滤波类型

    • 低通滤波:允许低频率成分通过,抑制高频成分,常用于平滑图像以去除噪声。
    • 高通滤波:允许高频成分通过,抑制低频成分,常用于增强图像边缘。
    • 中通滤波:允许特定频率范围内的成分通过,常用于提取特定频率的信号。
    • 阻带滤波:抑制特定频率范围内的成分,常用于去除特定频率的噪声。
  4. 滤波分析

    • 时域分析:直接分析信号在时间轴上的变化,即信号的时序特性。
    • 频域分析:将信号从时域转换到频域进行分析,即分析信号在不同频率上的分布。

7.1 卷积

示例代码

import cv2
import numpy as np

# 读取图像
src = cv2.imread(r"1.jpg")

# 定义一个核
kernel = np.array([[1, 1, 0], [1, 0, -1], [0, -1, -1]], np.float32)

# 使用 cv2.filter2D() 函数进行滤波操作
dst = cv2.filter2D(src, -1, kernel=kernel)

# 显示原始图像和滤波后的图像
cv2.imshow("src show", src)
cv2.imshow("dst show", dst)

# 等待按键后关闭窗口
cv2.waitKey(0)

# 关闭所有 OpenCV 窗口
cv2.destroyAllWindows()

7.2 平滑算子

  • 均值滤波、高斯滤波、中值滤波、双边滤波

示例代码

import cv2
import numpy as np

# 读取图像
image = cv2.imread('1.jpg', cv2.IMREAD_GRAYSCALE)

# 均值滤波
mean_filtered_image = cv2.blur(image, (5, 5))

# 高斯滤波
gaussian_filtered_image = cv2.GaussianBlur(image, (5, 5), 0)

# 中值滤波
median_filtered_image = cv2.medianBlur(image, 5)

# 双边滤波
bilateral_filtered_image = cv2.bilateralFilter(image, 9, 75, 75)

# 显示原始图像和各种滤波后的图像
cv2.imshow('Original Image', image)
cv2.imshow('Mean Filtered Image', mean_filtered_image)
cv2.imshow('Gaussian Filtered Image', gaussian_filtered_image)
cv2.imshow('Median Filtered Image', median_filtered_image)
cv2.imshow('Bilateral Filtered Image', bilateral_filtered_image)

# 等待按键后关闭窗口
cv2.waitKey(0)
cv2.destroyAllWindows()

7.3 锐化算子

  • Laplacian锐化、USM锐化

  1. Laplacian算子: Laplacian算子是一种二阶导数算子,用于检测图像中的边缘和细节。它通常用于图像锐化,因为它可以增强图像中的高频成分。

  2. USM(Unsharp Masking,非锐化遮罩)是一种常用的图像锐化技术,它通过对比度增强来突出图像的边缘和细节。USM锐化通常包括以下步骤:

  • 模糊处理:首先,对原始图像进行轻微的模糊处理,以创建一个“遮罩”图像。
  • 加权求和:将原始图像与遮罩图像进行加权求和,权重通常由一个阈值确定,这个阈值定义了锐化处理的强度。

示例代码

import cv2
import numpy as np

# 读取图像
image = cv2.imread('1.jpg', cv2.IMREAD_GRAYSCALE)

# 使用Laplacian算子进行锐化
sharpened_image = cv2.Laplacian(image, cv2.CV_64F)

# 显示原始图像和锐化后的图像
cv2.imshow('Original Image', image)
cv2.imshow('Sharpened Image', sharpened_image)
cv2.waitKey(0)
cv2.destroyAllWindows()
import cv2
import numpy as np

# 读取图像
image = cv2.imread('1.jpg', cv2.IMREAD_GRAYSCALE)

# 创建一个小的模糊核(遮罩)
kernel_size = (5, 5)
kernel = cv2.getGaussianKernel(kernel_size[0], kernel_size[1])
kernel = kernel * kernel.T
kernel = kernel / np.sum(kernel)

# 应用USM锐化
sharpened_image = cv2.filter2D(image, -1, kernel)

# 显示原始图像和锐化后的图像
cv2.imshow('Original Image', image)
cv2.imshow('Sharpened Image', sharpened_image)
cv2.waitKey(0)
cv2.destroyAllWindows()

7.4 梯度算子

Sobel Scharr Laplacian梯度滤波器

Sobel Scharr 其实就是求一阶或二阶导数。 Scharr 是对 Sobel (使用小的卷积核求解求解梯度角 度时)的优化。 Laplacian 是求二阶导数

Sobel 算子是高斯平滑与微分操作的结合体,所以它的抗噪声能力很好 

Scharr 滤波器是对Sobel滤波器的改进版本

Soble算子

Scharr算子

计算梯度幅值和方向

为了加快计算,用该公式简化计算

 拉普拉斯算子可以使用二阶导数的形式定义,可假设其离散实现类似于二阶 Sobel 导数,事实

上,OpenCV 在计算拉普拉斯算子时直接调用 Sobel 算子

 Laplacian 算子

示例代码

import cv2
import numpy as np
from matplotlib import pyplot as plt

# 读取图像
img = cv2.imread('6.jpg', cv2.IMREAD_GRAYSCALE)

# 计算拉普拉斯算子
laplacian = cv2.Laplacian(img, cv2.CV_64F)

# 计算Sobel算子
sobelx = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=5)
sobely = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=5)

# 显示图像
plt.subplot(2, 2, 1), plt.imshow(img, cmap='gray')
plt.title('Original'), plt.xticks([]), plt.yticks([])
plt.subplot(2, 2, 2), plt.imshow(laplacian, cmap='gray')
plt.title('Laplacian'), plt.xticks([]), plt.yticks([])
plt.subplot(2, 2, 3), plt.imshow(sobelx, cmap='gray')
plt.title('Sobel X'), plt.xticks([]), plt.yticks([])
plt.subplot(2, 2, 4), plt.imshow(sobely, cmap='gray')
plt.title('Sobel Y'), plt.xticks([]), plt.yticks([])

# 显示所有图像
plt.show()

8、傅里叶变换

  • 原理如下图:

8.1 Numpy中傅里叶变换

示例代码

import cv2
import numpy as np
import matplotlib.pyplot as plt

# 读取图像
img = cv2.imread('1.jpg', cv2.IMREAD_GRAYSCALE)

# 进行傅里叶变换
f = np.fft.fft2(img)
fshift = np.fft.fftshift(f)
magnitude_spectrum = 20 * np.log(np.abs(fshift))

# 显示原始图像和频谱图
plt.figure(figsize=(10, 10))
plt.subplot(221), plt.imshow(img, cmap='gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(222), plt.imshow(magnitude_spectrum, cmap='gray')
plt.title('Magnitude Spectrum'), plt.xticks([]), plt.yticks([])

# 定义高斯核
rows, cols = img.shape
crow, ccol = rows // 2, cols // 2
fshift[crow - 30:crow + 30, ccol - 30:ccol + 30] = 0

# 进行逆傅里叶变换
f_ishift = np.fft.ifftshift(fshift)
img_back = np.fft.ifft2(f_ishift)
img_back = np.abs(img_back)

# 显示滤波后的图像
plt.subplot(223), plt.imshow(img_back, cmap='gray')
plt.title('Image after HPF'), plt.xticks([]), plt.yticks([])
plt.subplot(224), plt.imshow(img_back, cmap='jet')
plt.title('Result in JET'), plt.xticks([]), plt.yticks([])

# 显示所有图像
plt.show()

8.2 OpenCV傅里叶变换

示例代码

import cv2
import numpy as np
import matplotlib.pyplot as plt

# 读取图像
img = cv2.imread('1.jpg', cv2.IMREAD_GRAYSCALE)

# 进行傅里叶变换
f = np.fft.fft2(img)
fshift = np.fft.fftshift(f)
magnitude_spectrum = 20 * np.log(np.abs(fshift))

# 显示原始图像和频谱图
plt.figure(figsize=(10, 10))
plt.subplot(221), plt.imshow(img, cmap='gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(222), plt.imshow(magnitude_spectrum, cmap='gray')
plt.title('Magnitude Spectrum'), plt.xticks([]), plt.yticks([])

# 定义高斯核
rows, cols = img.shape
crow, ccol = rows // 2, cols // 2
fshift[crow - 30:crow + 30, ccol - 30:ccol + 30] = 0

# 进行逆傅里叶变换
f_ishift = np.fft.ifftshift(fshift)
img_back = np.fft.ifft2(f_ishift)
img_back = np.abs(img_back)

# 显示滤波后的图像
plt.subplot(223), plt.imshow(img_back, cmap='gray')
plt.title('Image after HPF'), plt.xticks([]), plt.yticks([])
plt.subplot(224), plt.imshow(img_back)
plt.title('Result in JET'), plt.xticks([]), plt.yticks([])

# 显示所有图像
plt.show()

9、直方图

  • 直方图
  • 直方图均衡化
  • 自适应直方图均衡化

9.1 直方图

示例代码

import cv2
import matplotlib.pyplot as plt

# 读取图像
img = cv2.imread('1.jpg')

# 计算每个通道的直方图
img_B = cv2.calcHist([img], [0], None, [256], [0, 256])
img_G = cv2.calcHist([img], [1], None, [256], [0, 256])
img_R = cv2.calcHist([img], [2], None, [256], [0, 256])

# 绘制直方图
plt.plot(img_B, label='B', color='b')
plt.plot(img_G, label='G', color='g')
plt.plot(img_R, label='R', color='r')

# 显示直方图
plt.show()

9.2 直方图均衡化

直方图均衡化(Histogram Equalization)是一种增强图像对比度的技术,它通过重新分布图像的像素值来使图像的直方图更加均匀。这种方法可以增强图像的视觉效果,使得图像中的亮度分布更加均匀,从而提高图像的可视性。

示例代码

import cv2
import matplotlib.pyplot as plt

# 读取图像
img = cv2.imread('2.jpg', cv2.IMREAD_GRAYSCALE)

# 显示原始图像
cv2.imshow('src', img)

# 计算原始图像的直方图
his = cv2.calcHist([img], [0], None, [256], [0, 256])
plt.plot(his, label='his', color='r')

# 进行直方图均衡化
dst = cv2.equalizeHist(img)

# 显示均衡化后的图像
cv2.imshow('dst', dst)

# 将均衡化后的图像保存为文件
cv2.imwrite('15.jpg', dst)

# 计算均衡化后图像的直方图
his_equalized = cv2.calcHist([dst], [0], None, [256], [0, 256])
plt.plot(his_equalized, label='his_equalized', color='b')

# 显示两个直方图
plt.show()

9.3 自适应直方图均衡化

自适应直方图均衡化是对图片的每一个局部进行均衡化操作

示例代码

import cv2
import numpy as np

# 读取图像
img = cv2.imread('3.jpg', cv2.IMREAD_GRAYSCALE)

# 显示原始图像
cv2.imshow('src', img)

# 进行直方图均衡化
dst1 = cv2.equalizeHist(img)

# 显示均衡化后的图像
cv2.imshow('dst1', dst1)

# 创建自适应直方图均衡化(CLAHE)对象
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))

# 使用CLAHE进行处理
dst2 = clahe.apply(img)

# 显示CLAHE处理后的图像
cv2.imshow('dst2', dst2)

# 等待按键后关闭窗口
cv2.waitKey(0)

# 关闭所有 OpenCV 窗口
cv2.destroyAllWindows()

9.4 2D直方图

在前面的部分我们介绍了如何绘制一维直方图,之所以称为一维,是因为我们只考虑了图像的一个
特征:灰度值。但是在 2D 直方图中我们就要考虑两个图像特征。对于彩色图像的直方图通常情况
下我们需要考虑每个的颜色( Hue )和饱和度( Saturation )。根据这两个特征绘制 2D 直方图。

示例代码

import cv2
import numpy as np

# 读取图像
img = cv2.imread('3.jpg')

# 将图像从BGR转换为HSV
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

# 计算HSV图像的直方图
hist = cv2.calcHist([hsv], [0, 1], None, [180, 256], [0, 180, 0, 256])

# 显示直方图
cv2.imshow('hist', hist)

# 等待按键后关闭窗口
cv2.waitKey(0)

# 关闭所有 OpenCV 窗口
cv2.destroyAllWindows()

9.5 直方图反向投影

  • 它可以用来做图像分割,或者在图像中找寻我们感兴趣的部分。简单来说,它会输出与输入图像 (待搜索)同样大小的图像,其中的每一个像素值代表了输入图像上对应点属于目标对象的概率。 用更简单的话来解释,输出图像中像素值越高(越白)的点就越可能代表我们要搜索的目标(在输 入图像所在的位置)。这是一个直观的解释。直方图投影经常与 camshift算法等一起使用。
  • 我们要查找的对象要尽量占满这张图像(换句话说,这张图像上最好是有且仅有我们要查找的对 象)。最好使用颜色直方图,因为一个物体的颜色要比它的灰度能更好的被用来进行图像分割与对 象识别。接着我们再把这个颜色直方图投影到输入图像中寻找我们的目标,也就是找到输入图像中 的每一个像素点的像素值在直方图中对应的概率,这样我们就得到一个概率图像,最后设置适当的 阈值对概率图像进行二值化。

示例代码

import cv2
import numpy as np

# 读取ROI和目标图像
roi = cv2.imread('7.jpg')
target = cv2.imread('6.jpg')

# 将ROI和目标图像转换为HSV
hsv_roi = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV)
hsv_target = cv2.cvtColor(target, cv2.COLOR_BGR2HSV)

# 计算ROI的颜色直方图
roi_hist = cv2.calcHist([hsv_roi], [0, 1], None, [180, 256], [0, 180, 0, 256])

# 对直方图进行归一化
cv2.normalize(roi_hist, roi_hist, 0, 255, cv2.NORM_MINMAX)

# 计算目标图像的逆向投影
dst = cv2.calcBackProject([hsv_target], [0, 1], roi_hist, [0, 180, 0, 256], 1)

# 定义结构元素
disc = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))

# 使用结构元素进行滤波
dst = cv2.filter2D(dst, -1, disc)

# 进行阈值处理
ret, thresh = cv2.threshold(dst, 50, 255, 0)

# 将阈值转换为三通道图像
thresh = cv2.merge((thresh, thresh, thresh))

# 与目标图像进行位运算
res = cv2.bitwise_and(target, thresh)

# 合并结果
res = np.hstack((target, thresh, res))

# 保存结果图像
cv2.imwrite('65.jpg', res)

# 显示结果图像
cv2.imshow('img', res)
cv2.waitKey(0)

# 关闭所有 OpenCV 窗口
cv2.destroyAllWindows()

10、图像金字塔

高斯金字塔和拉普拉斯金字塔是图像处理中的两种多尺度分析技术:
- 高斯金字塔:通过高斯模糊和下采样创建一系列不同尺度的图像,用于概貌提取和图像融合。
- 拉普拉斯金字塔:从高斯金字塔派生,通过上采样和减去对应的高斯层图像来保留细节,用于细节特征提取和图像融合。

10.1 高斯金字塔

示例代码

import cv2

# 读取图像
img = cv2.imread(r"1.jpg")

# 创建窗口并显示原始图像
cv2.imshow(f"img{0}", img)
cv2.waitKey(0)

# 循环进行高斯金字塔的每一层
for i in range(3):
    # 如果已经是金字塔的底层,则退出循环
    if img is None:
        break
    
    # 应用高斯金字塔下采样
    img = cv2.pyrDown(img)
    
    # 创建新窗口并显示当前层的图像
    cv2.imshow(f"img{i+1}", img)
    cv2.waitKey(0)

# 等待最后一个窗口关闭
cv2.destroyAllWindows()

10.2 拉普拉斯金字塔

拉普拉斯金字塔由高斯金字塔计算得来

示例代码

import cv2

# 读取图像
img = cv2.imread(r"1.jpg")

# 应用高斯金字塔下采样
img_down = cv2.pyrDown(img)

# 应用高斯金字塔上采样
img_up = cv2.pyrUp(img_down)

# 计算低通滤波结果
img_new = cv2.subtract(img, img_up)

# 提高对比度
img_new = cv2.convertScaleAbs(img_new, alpha=5, beta=0)

# 显示低通滤波结果
cv2.imshow("img_LP", img_new)

# 等待按键后关闭窗口
cv2.waitKey(0)

# 关闭所有 OpenCV 窗口
cv2.destroyAllWindows()

11、 模板匹配

  • 使用模板匹配在一幅图像中查找目标
  • 单对象匹配
  • 多对象匹配
  • cv2.TM_CCOEFFcv2.TM_CCOEFF_NORMED、cv2.TM_CCORR、cv2.TM_CCORR_NORMED、 cv2.TM_SQDIFF、cv2.TM_SQDIFF_NORMED
  • 函数:cv2.matchTemplate()cv2.minMaxLoc()
  • 注意:如果你使用的比较方法是 cv2.TM_SQDIFF和cv2.TM_SQDIFF_NORMED,最小值对应 的位置才是匹配的区域。
  • 原理
模板匹配是用来在一副大图中搜寻查找模版图像位置的方法。 OpenCV 为我们提供了函数:
cv2.matchTemplate() 。和 2D 卷积一样,它也是用模板图像在输入图像(大图)上滑动,并
在每一个位置对模板图像和与其对应的输入图像的子区域进行比较。 OpenCV 提供了几种不
同的比较方法(细节请看文档)。返回的结果是一个灰度图像,每一个像素值表示了此区域
与模板的匹配程度。
如果输入图像的大小是(WxH ),模板的大小是( wxh ),输出的结果的大小就是( W-w+1, H-h+1 )。当你得到这幅图之后,就可以使用函数 cv2.minMaxLoc() 来找到其中的最 小值和最大值的位置了。第一个值为矩形左上角的点(位置),(w h )为 moban 模板矩形的宽和高。这个矩形就是找到的模板区域了。

11.1 单对象匹配

示例代码

import cv2

# 读取模板图像和目标图像
template = cv2.imread('2.jpg', cv2.IMREAD_GRAYSCALE)
img = cv2.imread('1.jpg', cv2.IMREAD_GRAYSCALE)

# 获取模板图像的尺寸
h, w = template.shape

# 使用 cv2.matchTemplate() 函数进行模板匹配
res = cv2.matchTemplate(img, template, cv2.TM_CCOEFF)

# 找到匹配结果中的最小值和最大值
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)

# 计算模板在目标图像中的边界点
bottom_right = (max_loc[0] + w, max_loc[1] + h)

# 在目标图像上绘制矩形框
cv2.rectangle(img, max_loc, bottom_right, (0, 0, 255), 2)

# 显示目标图像
cv2.imshow("img", img)

# 等待按键后关闭窗口
cv2.waitKey(0)

# 关闭所有 OpenCV 窗口
cv2.destroyAllWindows()

11.2 多对象匹配

示例代码

import cv2
import numpy as np

# 读取彩色图像和模板图像
img_rgb = cv2.imread('3.jpg')
template = cv2.imread('4.jpg', 0)

# 将彩色图像转换为灰度图像
img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)

# 计算匹配结果
res = cv2.matchTemplate(img_gray, template, cv2.TM_CCOEFF_NORMED)

# 设置匹配阈值
threshold = 0.8

# 找到匹配结果大于阈值的区域
loc = np.where(res >= threshold)

# 遍历每个匹配点,并在原图像上绘制矩形框
for pt in zip(*loc[::-1]):
    cv2.rectangle(img_rgb, pt, (pt[0] + template.shape[1], pt[1] + template.shape[0]), (0, 0, 255), 2)

# 显示结果图像
cv2.imshow('img', img_rgb)

# 等待按键后关闭窗口
cv2.waitKey(0)

# 关闭所有 OpenCV 窗口
cv2.destroyAllWindows()

11.3 无缝融合

我们可以利用金字塔对两种物体进行无缝对接

融合步骤:

  1. 1. 读入两幅图像,苹果和橘子
  2. 2. 构建苹果和橘子的高斯金字塔(6 层)
  3. 3. 根据高斯金字塔计算拉普拉斯金字塔
  4. 4. 在拉普拉斯的每一层进行图像融合(苹果的左边与橘子的右边融合)
  5. 5. 根据融合后的图像金字塔重建原始图像。

示例代码

import cv2
import numpy as np

# 读取图像
A = cv2.imread('3.jpg')
B = cv2.imread('4.jpg')

# 生成高斯金字塔
gpA = [A.copy()]
for i in range(6):
    G = cv2.pyrDown(gpA[-1])
    gpA.append(G)

gpB = [B.copy()]
for i in range(6):
    G = cv2.pyrDown(gpB[-1])
    gpB.append(G)

# 生成拉普拉斯金字塔
lpA = [gpA[5]]
for i in range(5, 0, -1):
    GE = cv2.pyrUp(gpA[i])
    L = cv2.subtract(gpA[i - 1], GE)
    lpA.append(L)

lpB = [gpB[5]]
for i in range(5, 0, -1):
    GE = cv2.pyrUp(gpB[i])
    L = cv2.subtract(gpB[i - 1], GE)
    lpB.append(L)

# 合并图像的左右半部分
LS = []
for la, lb in zip(lpA, lpB):
    rows, cols, dpt = la.shape
    ls = np.hstack((la[:, 0:cols // 2], lb[:, cols // 2:]))
    LS.append(ls)

# 重建图像
ls_ = LS[0]
for i in range(1, 6):
    ls_ = cv2.pyrUp(ls_)
    ls_ = cv2.add(ls_, LS[i])

# 直接连接图像的左右半部分
real = np.hstack((A[:, :cols // 2], B[:, cols // 2:]))

# 显示结果
cv2.imshow('Pyramid_blending.jpg', ls_)
cv2.imshow('Direct_blending.jpg', real)

# 等待按键后关闭窗口
cv2.waitKey(0)

# 关闭所有 OpenCV 窗口
cv2.destroyAllWindows()

12、Canny边缘提取算法

原理详解

步骤
  • 彩色图像转换为灰度图像
  • 高斯滤波,滤除噪声点
  • 计算图像梯度,根据梯度计算边缘幅值与角度
  • 非极大值抑制
  • 双阈值边缘连接处理
  • 二值化图像输出结果
非极大值抑制
非极大值抑制是一种边缘稀疏技术,非极大值抑制的作用在于 边。对图像进行梯度计算后,仅仅基 于梯度值提取的边缘仍然很模糊。而非极大值抑制则可以帮助将局部最大值之外的所有梯度值抑制为0。
  1. 将当前像素的梯度强度与沿正负梯度方向上的两个像素进行比较。
  2. 如果当前像素的梯度强度与另外两个像素相比最大,则该像素点保留为边缘点,否则该像素点将被抑制。

双阈值边缘连接处理

这个阶段决定哪些边缘是边缘,哪些边缘不是边缘。为此,我们需要两个阈值, minVal maxVal 。强度梯度大于maxVal 的边缘肯定是边缘,而 minVal 以下的边缘肯定是非边缘的,因此被丢弃 两者之间的值要判断是否与真正的边界相连,相连就保留,不相连舍弃。

示例代码
import cv2

# 读取图像
img = cv2.imread('1.jpg', cv2.IMREAD_GRAYSCALE)

# 应用高斯模糊
img = cv2.GaussianBlur(img, (3, 3), 0)

# 应用Canny边缘检测
canny = cv2.Canny(img, 50, 150)

# 显示Canny边缘检测结果
cv2.imshow('Canny', canny)

# 等待按键后关闭窗口
cv2.waitKey(0)

# 关闭所有 OpenCV 窗口
cv2.destroyAllWindows()

13、轮廓

13.1 轮廓的查找与绘制

示例代码

import cv2

# 读取图像
img = cv2.imread('2.jpg')

# 将图像转换为灰度图像
imggray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 使用二值化方法对图像进行阈值分割
ret, thresh = cv2.threshold(imggray, 127, 255, 0)

# 查找图像中的轮廓
image, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

# 绘制所有轮廓
img_contour = cv2.drawContours(img, contours, -1, (0, 255, 0), 3)

# 显示绘制了轮廓的图像
cv2.imshow("img_contour", img_contour)

# 等待按键后关闭窗口
cv2.waitKey(0)

# 关闭所有 OpenCV 窗口
cv2.destroyAllWindows()

13.2 面积,周长,重心

示例代码

import cv2

# 读取图像
img = cv2.imread('3.jpg', 0)

# 使用二值化方法对图像进行阈值分割
ret, thresh = cv2.threshold(img, 127, 255, 0)

# 查找图像中的轮廓
_, contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

# 计算轮廓的矩
M = cv2.moments(contours[0])

# 计算轮廓的重心
cx, cy = int(M['m10'] / M['m00']), int(M['m01'] / M['m00'])
print("重心:", cx, cy)

# 计算轮廓的面积
area = cv2.contourArea(contours[0])
print("面积:", area)

# 计算轮廓的周长
perimeter = cv2.arcLength(contours[0], True)
print("周长:", perimeter)

13.3 轮廓近似

将轮廓形状近似到另外一种由更少点组成的轮廓形状,新轮廓的点的数目由我们设定的准确度来决定。

示例代码

import cv2

# 读取图像
img = cv2.imread('3.jpg')

# 将图像转换为灰度图像
imggray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 使用二值化方法对图像进行阈值分割
ret, thresh = cv2.threshold(imggray, 127, 255, 0)

# 查找图像中的轮廓
_, contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

# 设置轮廓近似的精度
epsilon = 60

# 对轮廓进行近似
approx = cv2.approxPolyDP(contours[0], epsilon, True)

# 绘制近似后的轮廓
img_contour = cv2.drawContours(img, [approx], -1, (0, 255, 0), 3)

# 显示绘制了近似轮廓的图像
cv2.imshow("img_contour", img_contour)

# 等待按键后关闭窗口
cv2.waitKey(0)

# 关闭所有 OpenCV 窗口
cv2.destroyAllWindows()

13.4 凸包与凸性检测

凸包与轮廓近似相似,但不同,虽然有些情况下它们给出的结果是一样的。函数 cv2.convexHull() 可以用来检测一个曲线是否具有凸性缺陷,并能纠正缺陷 一般来说,凸 性曲线总是凸出来的,至少是平的。如果有地方凹进去了就被叫做凸性缺陷。函数 cv2.isContourConvex() 可以可以用来检测一个曲线是不是凸的。它只能返回 TrueFalse。

示例代码

import cv2

# 读取图像
img = cv2.imread('3.jpg')

# 将图像转换为灰度图像
imggray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 使用二值化方法对图像进行阈值分割
ret, thresh = cv2.threshold(imggray, 127, 255, 0)

# 查找图像中的轮廓
_, contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

# 计算轮廓的凸包
hull = cv2.convexHull(contours[0])

# 判断轮廓和凸包是否为凸曲线
print(cv2.isContourConvex(contours[0]), cv2.isContourConvex(hull))

# 绘制凸包
img_contour = cv2.drawContours(img, [hull], -1, (0, 0, 255), 3)

# 显示绘制了凸包的图像
cv2.imshow("img_contour", img_contour)

# 等待按键后关闭窗口
cv2.waitKey(0)

# 关闭所有 OpenCV 窗口
cv2.destroyAllWindows()

13.5 边界检测

  • 边界矩形
  • 最小矩形
  • 最小外切圆

示例代码

import cv2
import numpy as np

# 读取图像
img = cv2.imread('4.jpg')

# 将图像转换为灰度图像
imggray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 使用二值化方法对图像进行阈值分割
ret, thresh = cv2.threshold(imggray, 127, 255, 0)

# 查找图像中的轮廓
_, contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

# 获取轮廓的边界矩形
x, y, w, h = cv2.boundingRect(contours[0])
img_contour = cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2)

# 获取轮廓的最小矩形
rect = cv2.minAreaRect(contours[0])
box = cv2.boxPoints(rect)
box = np.int0(box)
img_contour = cv2.drawContours(img, [box], 0, (0, 0, 255), 2)

# 获取轮廓的最小外切圆
(x, y), radius = cv2.minEnclosingCircle(contours[0])
center = (int(x), int(y))
radius = int(radius)
img_contour = cv2.circle(img, center, radius, (255, 0, 0), 2)

# 显示绘制了轮廓、边界矩形、最小矩形和最小外切圆的图像
cv2.imshow("img_contour", img_contour)

# 等待按键后关闭窗口
cv2.waitKey(0)

# 关闭所有 OpenCV 窗口
cv2.destroyAllWindows()

import cv2
import numpy as np

# 读取图像
img = cv2.imread('4.jpg')

# 将图像转换为灰度图像
imggray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 使用二值化方法对图像进行阈值分割
ret, thresh = cv2.threshold(imggray, 127, 255, 0)

# 查找图像中的轮廓
_, contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

# 椭圆拟合
ellipse = cv2.fitEllipse(contours[0])
cv2.ellipse(img, ellipse, (255, 0, 0), 2)

# 直线拟合
h, w, _ = img.shape
[vx, vy, x, y] = cv2.fitLine(contours[0], cv2.DIST_L2, 0, 0.01, 0.01)
lefty = int((-x * vy / vx) + y)
righty = int(((w - x) * vy / vx) + y)
cv2.line(img, (w - 1, righty), (0, lefty), (0, 0, 255), 2)

# 显示绘制了轮廓、椭圆和直线的图像
cv2.imshow("img_contour", img)

# 等待按键后关闭窗口
cv2.waitKey(0)

# 关闭所有 OpenCV 窗口
cv2.destroyAllWindows()

13.6 轮廓性质

  • 边界矩形的宽高比

x,y,w,h = cv2.boundingRect(cnt)
aspect_ratio = float(w)/h
  • 轮廓面积与边界矩形面积的比

area = cv2.contourArea(cnt)
x,y,w,h = cv2.boundingRect(cnt)
rect_area = w*h
extent = float(area)/rect_area
  • 轮廓面积与凸包面积的比

area = cv2.contourArea(cnt)
hull = cv2.convexHull(cnt)
hull_area = cv2.contourArea(hull)
solidity = float(area)/hull_area
  • 与轮廓面积相等的圆形的直径

area = cv2.contourArea(cnt)
equi_diameter = np.sqrt(4*area/np.pi)
  • 对象的方向
下面的方法还会返回长轴和短轴的长度
(x,y),(MA,ma),angle = cv2.fitEllipse(cnt)

13.7 对象掩码

  • 用于获取构成对象的所有像素点

示例代码

import cv2
import numpy as np

# 读取图像
img = cv2.imread('3.jpg')

# 将图像转换为灰度图像
imggray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 使用二值化方法对图像进行阈值分割
ret, thresh = cv2.threshold(imggray, 127, 255, 0)

# 查找图像中的轮廓
_, contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

# 创建一个掩码,初始值为零
mask = np.zeros(imggray.shape, np.uint8)

# 在掩码上绘制轮廓
cv2.drawContours(mask, [contours[0]], 0, 255, -1)

# 获取轮廓上的像素点
pixelpoints = np.transpose(np.nonzero(mask))
print(pixelpoints)

# 显示掩码图像
cv2.imshow("mask", mask)

# 等待按键后关闭窗口
cv2.waitKey(0)

# 关闭所有 OpenCV 窗口
cv2.destroyAllWindows()
  • 最大值和最小值及它们的位置
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(imgray,mask = mask)
  • 使用相同的掩模求一个对象的平均颜色或平均灰度
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(imgray,mask = mask)

13.8 形状匹配

示例代码

import cv2
import numpy as np

# 读取图像
img1 = cv2.imread('4.jpg', 0)
img2 = cv2.imread('5.jpg', 0)

# 使用阈值分割图像
ret, thresh = cv2.threshold(img1, 127, 255, 0)
_, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cnt1 = contours[0]

ret, thresh2 = cv2.threshold(img2, 127, 255, 0)
_, contours, hierarchy = cv2.findContours(thresh2, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cnt2 = contours[0]

# 使用匹配形状函数比较两个轮廓
ret = cv2.matchShapes(cnt1, cnt2, 1, 0.0)
print(ret)

14、霍夫变换

  • 在图像处理中,霍夫变换用来检测任意能够用数学公式表达的形状,即使这个形状被破坏或者有点扭曲
  • 使用Hough变换进行直线检测
  • 使用Hough变换进行圆检测
  • 了解Hough变换的原理

14.1 直线检测

示例代码

import cv2
import numpy as np

# 读取图像
image = cv2.imread("1.jpg")

# 使用高斯模糊平滑图像,去除噪声
image = cv2.GaussianBlur(image, (5, 5), 50)

# 将图像转换为灰度图像
image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# 使用 Canny 算法提取边缘
edges = cv2.Canny(image_gray, 100, 150)

# 使用霍夫变换检测图像中的直线
lines = cv2.HoughLines(edges, 1, np.pi / 180, 100)

# 遍历检测到的直线
for line in lines:
    # 获取直线的参数
    rho, theta = line[0]
    
    # 计算直线的坐标
    a = np.cos(theta)
    b = np.sin(theta)
    x0 = a * rho
    y0 = b * rho
    x1 = int(x0 + 1000 * (-b))  # 直线起点横坐标
    y1 = int(y0 + 1000 * (a))  # 直线起点纵坐标
    x2 = int(x0 - 1000 * (-b))  # 直线终点横坐标
    y2 = int(y0 - 1000 * (a))  # 直线终点纵坐标
    
    # 在图像上绘制直线
    cv2.line(image, (x1, y1), (x2, y2), (0, 0, 255), 2)

# 显示结果图像
cv2.imshow("image_lines", image)
cv2.waitKey(0)
cv2.destroyAllWindows()

14.2 圆检测

示例代码

import cv2
import numpy as np

# 读取图像
image = cv2.imread("2.jpg")

# 将图像转换为灰度图像
dst = cv2.cvtColor(image, cv2.COLOR_BGRA2GRAY)

# 使用霍夫变换检测图像中的圆
circle = cv2.HoughCircles(dst, cv2.HOUGH_GRADIENT, 1, 30, param1=40, param2=20,
minRadius=20, maxRadius=300)

# 如果检测到圆,则绘制圆
if not circle is None:
    circle = np.uint16(np.around(circle))  # 四舍五入结果
    for i in circle[0, :]:  # 遍历检测到的圆
        cv2.circle(image, (i[0], i[1]), i[2], (0, 255, 0), 2)  # 绘制圆

# 显示结果图像
cv2.imshow("circle", image)
cv2.waitKey(0)
cv2.destroyAllWindows()

14.3 原理详解

步骤:

  • 轮廓检测算法检测出轮廓
  • 投射到hough空间进行形状检测

数学原理:

15、图像分割

  • 使用分水岭算法基于掩模的图像分割
  • 函数:cv2.watershed()
原理
任何一副灰度图像都可以被看成拓扑平面,灰度值高的区域可以被看成是山峰,灰度值低的区域可
以被看成是山谷。我们向每一个山谷中灌不同颜色的水。随着水的位的升高,不同山谷的水就会相
遇汇合,为了防止不同山谷的水汇合,我们需要在水汇合的地方构建起堤坝。不停的灌水,不停的
构建堤坝知道所有的山峰都被水淹没。我们构建好的堤坝就是对图像的分割。这就是分水岭算法的
背后哲理。
但是这种方法通常都会得到过度分割的结果,这是由噪声或者图像中其他不规律的因素造成
的。为了减少这种影响, OpenCV 采用了基于掩模的分水岭算法,在这种算法中我们要设置那些
山谷点会汇合,那些不会。这是一种交互式的图像分割。我们要做的就是给我们已知的对象打上不
同的标签。如果某个区域肯定是前景或对象,就使用某个颜色(或灰度值)标签标记它。如果某个
区域肯定不是对象而是背景就使用另外一个颜色标签标记。而剩下的不能确定是前景还是背景的区
域就用 0 标记。这就是我们的标签。然后实施分水岭算法。每一次灌水,我们的标签就会被更
新,当两个不同颜色的标签相遇时就构建堤坝,直到将所有山峰淹没,最后我们得到的边界对象
(堤坝)的值为 -1

示例代码

import numpy as np
import cv2
from matplotlib import pyplot as plt

# 读取图像
img = cv2.imread('1.jpg')

# 将图像转换为灰度图像
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 应用Otsu阈值进行二值化
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)

# 应用开运算去除噪声
kernel = np.ones((3, 3), np.uint8)
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)

# 确定背景区域
sure_bg = cv2.dilate(opening, kernel, iterations=3)

# 计算距离变换
dist_transform = cv2.distanceTransform(opening, 1, 5)

# 应用Otsu阈值确定前景区域
ret, sure_fg = cv2.threshold(dist_transform, 0.7 * dist_transform.max(), 255, 0)

# 确定未知区域
sure_fg = np.uint8(sure_fg)
unknown = cv2.subtract(sure_bg, sure_fg)

# 标记连接区域
ret, markers1 = cv2.connectedComponents(sure_fg)

# 所有标签加1,确保背景不是0而是1
markers = markers1 + 1

# 标记未知区域为0
markers[unknown == 255] = 0

# 应用水印算法进行标记
markers3 = cv2.watershed(img, markers)

# 标记未知区域
img[markers3 == -1] = [255, 0, 0]

# 显示结果图像
cv2.imshow("img", img)

# 等待按键后关闭窗口
cv2.waitKey(0)

# 关闭所有 OpenCV 窗口
cv2.destroyAllWindows()

结语

OpenCV 的核心操作为我们打开了图像处理的大门,让我们能够轻松地处理各种图像和视频数据,并将其应用于各个领域。希望这份文档能够帮助大家更好地学习和理解 OpenCV,并激发进一步探索图像处理和计算机视觉领域的热情。

欢迎留言讨论,一起学习 OpenCV

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值