OpenCV-图像的基础操作

图像的读取、显示与保存

  • 图片的读取与显示
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import cv2

    img = cv2.imread('img_path')
    cv2.imshow('windowname',img)
    cv2.waitkey(0) # 让窗口持续显示 不会一闪而过

    import matplotlib.pyplot as plt

    # 因为我用的vscode,更好的图片显示方式是plt
    plt.imshow(img)
    使用PIL读取图片
    1
    2
    3
    4
    5
    from PIL import Image

    image = Image.open(image_path)
    image.show()


PIL打开的通道顺序是RGB

opencv打开图片的通道顺序是BGR

shape:

opencv(820,818,3)

PIL(670760,3)


  • 图片的保存

cv2.imwrite(‘img_name.jpg’,img)

  • 视频的读取与显示
    1
    2
    3
    4
    5
    6
    7
    8
    9
    import cv2
    cap = cv2.VideoCapture('video_path')
    while(cap.isOpened()):
    ret,frame = cap.read()
    cv2.imshow('frame',frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
    break
    cap.release()
    cv2.destroyAllWindows()
  • 视频的保存 待验证
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import cv2
    cap = cv2.VideoCapture('video_path')
    fourcc = cv2.VideoWriter_fourcc(*'XVID')
    out = cv2.VideoWriter('output.avi',fourcc,20.0,(640,480))
    while(cap.isOpened()):
    ret,frame = cap.read()
    if ret == True:
    out.write(frame)
    cv2.imshow('frame',frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
    break
    cap.release()
    out.release()
    cv2.destroyAllWindows()

图像色彩空间

  • 色彩空间转换

    1
    2
    3
    4
    5
    6
    7
    import cv2
    src = cv2.imread('img_path')
    dst = cv2.cvtColor(src,cv2.COLOR_BGR2GRAY)
    cv2.imshow('src',src)
    cv2.imshow('dst',dst)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
  • 通道分离

    1
    2
    3
    4
    5
    import cv2
    img = cv2.imread('img_path')
    # b,g,r = cv2.split(img)
    img[:,:,0] = 0
    cv2.imshow('img',img)
  • HSV色彩空间
    HSV色彩 - 色相、飽和度、明度
    H的取值0-179,S的取值0-255,V的取值0-255。但是不同的软件的H取值范围不同,如OpenCV取值范围是0-179,Matlab取值范围是0-360。所以当你需要拿opencv的HSV值和matlab的HSV值做比较的时候,需要先做归一化。

  • 利用HSV空间提取红色 inRange bitwise_and

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import cv2
    img = cv2.imread('img_path')
    hsv = cv2.cvtColor(img,cv2.COLOR_BGR2HSV)
    lower_red = np.array([0,100,100])
    upper_red = np.array([10,255,255])
    # 根据颜色范围定义一个掩码,用于后续的图像处理
    # 这里使用cv2.inRange函数来创建一个掩码,掩码将HSV颜色空间中指定范围的像素设置为白色(即255),其余像素设置为黑色(即0)
    mask = cv2.inRange(hsv,lower_red,upper_red)

    # 应用掩码到原始图像上,只保留掩码中白色(即指定颜色范围)的区域
    # 这里使用cv2.bitwise_and函数进行位与操作,将原始图像与掩码进行运算,得到只有指定颜色范围的图像
    # 参数img被重复使用,表示对原始图像进行操作,mask参数指明使用之前定义的掩码
    res = cv2.bitwise_and(img,img,mask=mask)
    cv2.imshow('img',img)
    cv2.imshow('mask',mask)
    cv2.imshow('res',res)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
  • 得到BGR对应的HSV

    1
    2
    3
    4
    5
    6
    import cv2
    import numpy as np

    color = np.uint8([[[24, 19, 214]]])
    hsv_color = cv2.cvtColor(color, cv2.COLOR_BGR2HSV)
    print(hsv_color)

图形的绘制

  • 直线、圆、椭圆、矩形

    1
    2
    3
    4
    5
    6
    7
    8
    import cv2
    img = cv2.imread(r"1.jpg")
    # cv2.line(img, (100, 30), (210, 180), color=(0, 0, 255), thickness=2)
    # cv2.circle(img, (50, 50), 30, (0, 0, 255), 1)
    cv2.rectangle(img,(100,30),(210,180),color=(0,0,255),thickness=2)
    # cv2.ellipse(img, (100, 100), (100, 50), 0, 0, 360, (255, 0, 0), -1)
    cv2.imshow("pic show", img)
    cv2.waitKey(0)
  • 多边形 polylines

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import cv2
    import numpy as np
    img = cv2.imread(r"1.jpg")
    # 定义四个顶点坐标
    pts = np.array([[10, 5], [50, 10], [70, 20], [20, 30]], np.int32)
    # 顶点个数:4,矩阵变成4*1*2维
    pts = pts.reshape((-1, 1, 2))
    cv2.polylines(img, [pts], True, (0, 0, 255), 2)
    cv2.imshow("pic show", img)
    cv2.waitKey(0)
  • 添加文字 putText

    1
    2
    3
    4
    5
    6
    import cv2
    img = cv2.imread(r"1.jpg")
    font = cv2.FONT_HERSHEY_SIMPLEX
    cv2.putText(img, 'beautiful girl', (10, 30), font, 1, (0, 0, 255), 1,
    lineType=cv2.LINE_AA)
    cv2.imshow("pic show", img)cv2.waitKey(0)

OpenCV-高阶操作

阈值操作 threshold

  • OTSU二值化操作
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import cv2
    img = cv2.imread("1.jpg")
    # 将图像转换为灰度图像
    # 这一步是为了减少后续处理中对颜色信息的依赖,灰度图像处理起来更简单且计算量更小
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    # 应用二值化处理,同时使用OTSU算法自动确定阈值
    # 二值化将图像分为黑色和白色两个区域,便于后续的图像分析和处理
    # OTSU算法能自动计算最佳阈值,以区分背景和前景,减少人工干预
    ret, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
    cv2.imshow("gray",gray)
    cv2.imshow('binary', binary)
    cv2.waitKey(0)

简单阈值

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

  • cv2.THRESH_BINARY
  • cv2.THRESH_BINARY_INV
  • cv2.THRESH_TRUNC
  • cv2.THRESH_TOZERO
  • cv2.THRESH_TOZERO_INV
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('02.jpg',0)
ret,thresh1 = cv2.threshold(img,127,255,cv2.THRESH_BINARY)
ret,thresh2 = cv2.threshold(img,127,255,cv2.THRESH_BINARY_INV)
ret,thresh3 = cv2.threshold(img,127,255,cv2.THRESH_TRUNC)
ret,thresh4 = cv2.threshold(img,127,255,cv2.THRESH_TOZERO)
ret,thresh5 = cv2.threshold(img,127,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()

alt text

自适应阈值

当同一幅图像上的不同部分具有不同亮度时,使用自适应阈值。此时的阈值是根据图像上的局部像素值来决定的。
在OpenCV中,可以使用cv2.threshold()函数来执行简单阈值处理,也可以使用cv2.adaptiveThreshold()函数来执行自适应阈值处理。简单阈值处理是指将图像中的像素值与给定的阈值进行比较,如果大于阈值,则将该像素设置为白色(255),否则设置为黑色(0)。自适应阈值处理是指根据图像局部区域的统计信息动态确定阈值,而不是使用固定的阈值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import cv2
import numpy as np
from matplotlib import pyplot as plt

# 读取图像,并应用高斯模糊以减少噪声
img = cv2.imread('4.jpg', 0)
img = # 使用5x5的高斯核对图像进行高斯模糊处理,以减少图像中的噪声

# 参数:
# img: 输入图像,应为灰度图像
# (5, 5): 高斯核的大小,必须为正奇数且相同
# 0: 高斯核的标准差,如果设置为0,则根据高斯核大小自动计算
img = cv2.GaussianBlur(img, (5, 5), 0)(img, (5, 5), 0)

# 应用全局二值化阈值处理# 对图像进行阈值处理,将图像转换为二值图像
# 参数:
# img: 输入图像,通常为单通道图像
# 127: 阈值,像素值小于该值的将被设为黑色(0),大于等于该值的将被设为白色(255)
# 255: 最大值,用于二值化时超过阈值的像素点的赋值
# cv2.THRESH_BINARY: 阈值类型,这里使用的是二值化阈值
# 返回值:
# ret: 计算出的实际阈值,对于固定阈值处理,其值与设定的阈值相同
# th1: 处理后的二值图像
ret, th1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)

# 应用自适应均值二值化阈值处理
# # 对图像应用自适应阈值处理,根据局部区域的平均值来确定阈值
# 参数:
# img: 输入图像,应为灰度图像
# 255: 最大值,用于二值化时超过计算得到的阈值的像素点的赋值
# cv2.ADAPTIVE_THRESH_MEAN_C: 阈值类型,表示使用局部区域的平均值作为阈值
# cv2.THRESH_BINARY: 阈值模式,这里使用的是二值化模式
# 11: 邻域大小,用于计算局部阈值的矩形区域大小,必须为奇数
# 2: 常数C,从平均值或加权平均值中减去,用于调整阈值
# 返回值:
# th2: 经过自适应阈值处理后的二值图像
th2 = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C,
cv2.THRESH_BINARY, 11, 2)

# 应用自适应高斯二值化阈值处理
# 应用自适应阈值处理,基于局部区域的统计信息动态确定阈值
# 参数:
# img: 输入图像,应为灰度图像
# 255: 最大值,用于二值化时超过计算得到的阈值的像素点的赋值
# cv2.ADAPTIVE_THRESH_MEAN_C 或 cv2.ADAPTIVE_THRESH_GAUSSIAN_C:
# 阈值类型,分别表示使用局部区域的平均值或加权平均值作为阈值
# cv2.THRESH_BINARY: 阈值模式,这里使用的是二值化模式
# 11: 邻域大小,用于计算局部阈值的矩形区域大小,必须为奇数
# 2: 常数C,从平均值或加权平均值中减去,用于调整阈值
# 返回值:
# th3: 经过自适应阈值处理后的二值图像
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]

# 在matplotlib中设置图像布局,并逐一显示图像
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()

alt text

图像上的运算

  • 加减法

    1
    2
    3
    4
    5
    6
    import cv2
    import numpy as np
    x = np.uint8([250])
    y = np.uint8([10])
    print(cv2.add(x,y))
    print(cv2.subtract(x,y))
  • 图像混合

    本质上来讲就是加法,只是两幅图的权重不一样,,这就会给人一种混合或者透明的感觉。图像混合
    的计算公式如下:

    g (x) = (1 − α) f0 (x) + α f1 (x)
    其中,α 是混合因子,它控制着第一幅图像的权重,而 1 − α 是第二幅图像的权重。

    dst = α · img1 + β · img2 + γ

    这里 γ 的取值为 0。

1
2
3
4
5
6
7
import cv2
img1 = cv2.imread('1.jpg')
img2 = cv2.imread('9.jpg')
dst = cv2.addWeighted(img1, 0.7, img2, 0.3, 0)
cv2.imshow('dst', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()
  • 按位运算
    按位运算包括:AND OR NOT XOR
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    import cv2
    img1 = cv2.imread('1.jpg')
    img2 = cv2.imread('9.jpg')
    # 获取输入图像的尺寸
    rows, cols, channels = img2.shape

    # 从原始图像img1中定义一个与img2相同大小的区域作为感兴趣区域(ROI)
    roi = img1[0:rows, 0:cols]

    # 将img2转换为灰度图像,为后续处理做准备
    img2gray = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)

    # 使用二值化技术对灰度图像进行处理,生成掩模图像
    # 二值化的阈值为10,最大值为255,使用二值化阈值技术
    ret, mask = cv2.threshold(img2gray, 10, 255, cv2.THRESH_BINARY)

    # 对掩模图像进行取反操作,用于后续的背景提取
    mask_inv = cv2.bitwise_not(mask)

    # 通过与操作,从ROI中提取出背景部分
    img1_bg = cv2.bitwise_and(roi, roi, mask=mask_inv)

    # 通过与操作,从img2中提取出前景部分
    img2_fg = cv2.bitwise_and(img2, img2, mask=mask)

    # 将前景和背景叠加,得到最终的合成图像
    dst = cv2.add(img1_bg, img2_fg)

    # 将合成图像覆盖到原始图像的ROI区域
    img1[0:rows, 0:cols] = dst
    cv2.imshow('res', img1)
    cv2.waitKey(0)

图像的几何变换

  • Resize/trainsport/flip

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import cv2
    import matplotlib.pyplot as plt
    src = cv2.imread('01.jpg')
    rows, cols, channel = src.shape
    dst = cv2.resize(src, (cols * 2, rows * 2), interpolation=cv2.INTER_CUBIC) # 放大
    # interpolation参数说明
    # INTER_NEAREST - 最邻近插值
    # INTER_LINEAR - 双线性插值,如果最后一个参数你不指定,默认使用这种方法
    # INTER_AREA -区域插值
    # INTER_CUBIC - 4x4像素邻域内的双立方插值
    # INTER_LANCZOS4 - 8x8像素邻域内的Lanczos插值
    dst = cv2.transpose(src)# 旋转
    dst = cv2.flip(src, 0)# 翻转
    plt.imshow(dst)
  • 仿射变换
    任意一个二维图像,我们乘以一个仿射矩阵,就能得到仿射变换后的图像。变换包含:缩放、旋转、平
    移、倾斜、镜像。

alt text

线性代数中的矩阵运算,细看就能看懂。
alt text

1
2
3
4
5
6
7
8
9
10
11
12
13
import cv2
import numpy as np
src = cv2.imread('1.jpg')
rows, cols, channel = 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)

透视变换(Perspective Transformation)是指利用透视中心、像点、目标点三点共线的条件,按透视旋转定律使承影面(透视面)绕迹线(透视轴)旋转某一角度,破坏原有的投影光线束,仍能保持承影面上投影几何图形不变的变换。简而言之,就是将一个平面通过一个投影矩阵投影到指定平面上。

alt text

重要的是变换矩阵的计算

1
2
3
4
5
6
7
8
#计算变换矩阵
matrix = cv2.getPerspectiveTransform(clockwise_point,target_clockwise_point)
print(matrix)
#计算透视变换后的图片
perspective_img = cv2.warpPerspective(img,matrix,(target_clockwise_point[2][0],target_clockwise_point[2][1]))
cv2.imshow("perspective_img",perspective_img)
cv2.waitKey(0)

腐蚀膨胀操作

  • 膨胀操作可以让颜色值大的像素变得粗壮,膨胀之前需要二值化图像。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import cv2 as cv
img = cv.imread("01.jpg",0)

# 初始化结构元素
# 使用矩形结构元素进行膨胀操作,选择大小为5x5的结构元素可以有效扩展亮区域
kernel = cv.getStructuringElement(cv.MORPH_RECT, (5, 5))

# 对图像进行膨胀处理
# 膨胀操作用于扩展图像中的亮区域,这里应用于img图像,使用之前定义的结构元素
dst = cv.dilate(img, kernel)
cv.imshow('src', img)
cv.imshow('dst', dst)
cv.waitKey(0)

  • 腐蚀操作可以让颜色值大的像素变得更细,腐蚀前也需要二值化图像。
1
2
3
4
5
6
7
import cv2 as cv
img = cv.imread("02.jpg", 0)
kernel = cv.getStructuringElement(cv.MORPH_RECT, (5, 5))
dst = cv.erode(img, kernel)
cv.imshow('src', img)
cv.imshow('dst', dst)
cv.waitKey(0)

开闭运算

开操作是先腐蚀再膨胀,开操作可以用于去噪 MORPH_OPEN

1
2
3
4
5
6
7
import cv2 as cv
img = cv.imread("10.jpg", 0)
kernel = cv.getStructuringElement(cv.MORPH_RECT, (3, 3))
dst = cv.morphologyEx(img, cv.MORPH_OPEN, kernel, iterations=1)
cv.imshow('src', img)
cv.imshow('dst', dst)
cv.waitKey(0)

闭操作是先膨胀再腐,闭操作可以用于补漏洞 MORPH_CLOSE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import cv2 as cv
img = cv.imread("10.jpg", 0)

# 定义结构元素为3x3的矩形,用于形态学操作
# 这里的结构元素选择了矩形,是因为矩形对于关闭操作来说,可以有效地连接接近的物体
# 或者填充物体内部的空洞,参数(3, 3)指定了结构元素的大小
kernel = cv.getStructuringElement(cv.MORPH_RECT, (3, 3))

# 对图像进行形态学关闭操作
# 形态学关闭操作通常用于连接接近的物体或填充物体内部的空洞
# 这里的迭代次数设置为1,意味着操作将执行一次
dst = cv.morphologyEx(img, cv.MORPH_CLOSE, kernel, iterations=1)
cv.imshow('src', img)
cv.imshow('dst', dst)
cv.waitKey(0)

梯度操作

膨胀减去腐蚀

1
2
3
4
5
6
7
8
9
10
11
12
13
import cv2 as cv
img = cv.imread("11.jpg", 0)

# 定义结构元素为3x3的矩形,用于形态学操作
kernel = cv.getStructuringElement(cv.MORPH_RECT, (3, 3))

# 对图像应用形态学梯度操作,用于提取图像边缘
# 形态学梯度是通过先腐蚀后膨胀来实现的,可以突出物体的边界
dst = cv.morphologyEx(img, cv.MORPH_GRADIENT, kernel)
cv.imwrite("21.jpg", dst)
cv.imshow('src', img)
cv.imshow('dst', dst)
cv.waitKey(0)

alt text

顶帽与黑帽

参考博客

  • 礼帽,是原始图像与进行开运算之后得到的图像的差。

因为开运算到来的结果是放大了裂痕或者局部低亮度的区域,因此,从原图中减去运算后的图,得到的效果图突出了比原图轮廓周围的区域更明亮的区域,且这一操作和选择的核的大小相关。
顶帽运算往往用来分离比邻近点亮一些的斑块。当一幅图像具有大幅的背景的时候,而微小物品比较有规律的情况下,可以使用顶帽运算进行背景提取。

1
2
3
4
5
6
7
import cv2 as cv
img = cv.imread("11.jpg", 0)
kernel = cv.getStructuringElement(cv.MORPH_RECT, (3, 3))
dst = cv.morphologyEx(img, cv.MORPH_GRADIENT, kernel)
cv.imshow('src', img)
cv.imshow('dst', dst)
cv.waitKey(0)

alt text

  • 黑帽,是进行闭运算之后得到的图像与原始图像的差。

黑帽运算之后的效果图突出了与原图像轮廓周围的区域更暗的区域,且这一操作和选择的核大小相关。所以黑帽运算用来分离比邻近点暗一些的斑块。

1
2
3
4
5
6
7
import cv2 as cv
img = cv.imread("10.jpg", 0)
kernel = cv.getStructuringElement(cv.MORPH_RECT, (3, 3))
dst = cv.morphologyEx(img, cv.MORPH_BLACKHAT, kernel)
cv.imshow('src', img)
cv.imshow('dst', dst)
cv.waitKey(0)

alt text


OpenCV-图像分割

图像分割

cv2.watershed()

原理:

  • 任何一副灰度图像都可以被看成拓扑平面,灰度值高的区域可以被看成是山峰,灰度值低的区域可 以被看成是山谷。我们向每一个山谷中灌不同颜色的水。随着水的位的升高,不同山谷的水就会相遇汇合,为了防止不同山谷的水汇合,我们需要在水汇合的地方构建起堤坝。不停的灌水,不停的构建堤坝知道所有的山峰都被水淹没。我们构建好的堤坝就是对图像的分割。这就是分水岭算法的 背后哲理。
  • 但是这种方法通常都会得到过度分割的结果,这是由噪声或者图像中其他不规律的因素造成的。为了减少这种影响,OpenCV 采用了基于掩模的分水岭算法,在这种算法中我们要设置那些山谷点会汇合,那些不会。这是一种交互式的图像分割。我们要做的就是给我们已知的对象打上不 同的标签。如果某个区域肯定是前景或对象,就使用某个颜色(或灰度值)标签标记它。如果某个区域肯定不是对象而是背景就使用另外一个颜色标签标记。而剩下的不能确定是前景还是背景的区域就用 0 标记。这就是我们的标签。然后实施分水岭算法。每一次灌水,我们的标签就会被更新,当两个不同颜色的标签相遇时就构建堤坝,直到将所有山峰淹没,最后我们得到的边界对象(堤坝)的值为 -1。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    import numpy as np
    import cv2
    from matplotlib import pyplot as plt
    img = cv2.imread('1.jpg')
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV |
    cv2.THRESH_OTSU)
    # noise removal
    kernel = np.ones((3, 3), np.uint8)
    opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)
    # sure background area
    sure_bg = cv2.dilate(opening, kernel, iterations=3)
    dist_transform = cv2.distanceTransform(opening, 1, 5)
    ret, sure_fg = cv2.threshold(dist_transform, 0.7 * dist_transform.max(), 255, 0)
    # Finding unknown region
    sure_fg = np.uint8(sure_fg)
    unknown = cv2.subtract(sure_bg, sure_fg)
    # Marker labelling
    ret, markers1 = cv2.connectedComponents(sure_fg)
    # Add one to all labels so that sure background is not 0, but 1
    markers = markers1 + 1
    # Now, mark the region of unknown with zero
    markers[unknown == 255] = 0
    markers3 = cv2.watershed(img, markers)
    img[markers3 == -1] = [255, 0, 0]
    cv2.imshow("img", img)
    cv2.waitKey(0)
    alt text

OpenCV-图像滤波

图像滤波

保留图像细节特征的条件下对目标图像的噪声进行抑制

滤波的概念

  • 滤波过程就是把不需要的信号频率去掉的过程
  • 滤波操作一般用卷积操作来实现,卷积核一般称为滤波器
  • 滤波分:低通滤波,高通滤波,中通滤波,阻带滤波
  • 低通滤波也叫平滑滤波,可以使图像变模糊,主要用于去噪
  • 高通滤波一般用于获取图像边缘、轮廓或梯度
  • 中通滤波一般用于获取已知频率范围内的信号
  • 阻带滤波一般用于去掉已知频率范围内的信号
  • 滤波分析一般有时域分析和频域分析
  • 时域分析是直接对信号本身进行分析
  • 频域分析师对型号的变化快慢进行分析

卷积操作

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

平滑操作

  • 均值滤波
    1
    dst = cv2.blur(src, (5,5))
  • 高斯滤波
    1
    dst = cv2.GaussianBlur(src, (5, 5), 0)
  • 中值滤波
    1
    dst = cv2.medianBlur(src, 5)
    双边滤波
    1
    dst = cv2.bilateralFilter(src,9,75,75)

锐化操作

提高图像的锐利程度,使图像变得更清晰

  • Laplacian锐化
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
     kernel = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]], np.float32) #定义一个核 五领域?

    # 使用二维滤波器对图像进行滤波
    #
    # 该行代码应用了二维滤波器(kernel)对输入图像(src)进行滤波处理,生成的结果图像(dst)。
    # 这一操作可以用于平滑图像、边缘检测、特征提取等目的。
    #
    # 参数:
    # - src: 输入图像,可以是灰度图像或彩色图像。
    # - -1: 指定输出图像的深度为与输入图像相同。
    # - kernel: 用于滤波的核数组,定义了滤波器的特性。
    #
    # 返回值:
    # - dst: 应用滤波器处理后的输出图像。
    dst = cv2.filter2D(src, -1, kernel=kernel)
  • USM锐化
    1
    2
    3
    4
    5
    6
    7
    8
    9
     dst = cv2.GaussianBlur(src, (5, 5), 0)
    # 加权融合源图像和目标图像
    #
    # 通过加权合并源图像(src)和目标图像(dst),实现图像的融合处理。这种处理常用于图像叠加、透明效果处理等场景。
    # 参数src表示源图像,dst表示目标图像。alpha参数为源图像的权重,这里设置为2,表示源图像的贡献较大。
    # beta参数为目标图像的权重,这里设置为-1,表示目标图像的贡献由gamma参数来调整。
    # gamma参数为调整后的贡献值,这里设置为0,表示不进行额外的贡献值调整。
    # 函数返回加权融合后的图像。
    dst = cv2.addWeighted(src, 2, dst, -1, 0)

梯度操作

alt text
alt text

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import cv2
from matplotlib import pyplot as plt

img = cv2.imread('6.jpg', 0)
# 计算图像的拉普拉斯变换
# 使用OpenCV的Laplacian函数对图像进行处理,以获取图像的拉普拉斯变换。
# 这里的拉普拉斯变换是通过双线性内插实现的,可以用于边缘检测等应用。
# 参数img是输入的图像,使用cv2.CV_64F类型来存储计算结果,以确保高精度。
laplacian = cv2.Laplacian(img, cv2.CV_64F)
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()

alt text

傅里叶变换

alt text

  • numpy中的傅里叶变换

傅立叶变换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('1.jpg', 0)

# 加载图像并应用二维快速傅里叶变换(FFT)
f = np.fft.fft2(img)
# 对FFT结果进行傅里叶平移
fshift = np.fft.fftshift(f)
# 计算幅度谱,并应用对数尺度以增强可视化
magnitude_spectrum = 20 * np.log(np.abs(fshift))

# 创建一个新的图形窗口,大小为10x10
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

# 在幅度谱中,将中心附近的一定区域设为0,实现低频成分的去除
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()

alt text

  • opencv傅里叶变换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import cv2
import numpy as np
from matplotlib import pyplot as plt

# 读取图像并转换为灰度图像
img = cv2.imread('1.jpg', 0)
# 对图像进行二维快速傅里叶变换(FFT)
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
# 在中心区域设置为0,实现低频抑制
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()

alt text

直方图均衡化

直方图均衡化是图像处理领域中利用图像直方图对对比度进行调整的方法。这种方法通常用来增加许多图像的全局对比度,尤其是当图像的有用数据的对比度相当接近的时候。通过这种方法,亮度可以更好地在直方图上分布。这样就可以用于增强局部的对比度而不影响整体的对比度,直方图均衡化通过有效地扩展常用的亮度来实现这种功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
import cv2
import matplotlib.pyplot as plt
img = cv2.imread('2.jpg', 0)
cv2.imshow("src", img)
his = cv2.calcHist([img], [0], None, [256], [0, 256])
plt.plot(his, label='his', color='r')
# plt.show()
dst = cv2.equalizeHist(img)
cv2.imshow("dst", dst)
cv2.imwrite("15.jpg", dst)
his = cv2.calcHist([dst], [0], None, [256], [0, 256])
plt.plot(his, label='his', color='b')
plt.show()

alt text

自适应均衡化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import cv2
img = cv2.imread('3.jpg', 0)
cv2.imshow("src", img)

dst1 = cv2.equalizeHist(img)
cv2.imshow("dst1", dst1)

# 创建一个对比度受限的自适应直方图均衡化器(CLAHE)
# 参数clipLimit控制对比度限制的阈值,tileGridSize定义了处理区域的大小
# 这里的clipLimit设置为2.0,意味着只有超过此阈值的像素值才会被限制
# tileGridSize设置为(8, 8),意味着图像被划分为8x8的处理区域
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))

# 应用CLAHE处理到图像上
# 这一步将对图像进行自适应直方图均衡化,以增强局部对比度
dst2 = clahe.apply(img)

cv2.imshow("dst2", dst2)
cv2.waitKey(0)

alt text

直方图反向投影:

可以用来做图像分割,或者在图像中找到我们感兴趣的部分,简单来说,它会输出与输入图像(待搜索)同样大小的图像,其中的每一个像素值代表了输入图像上对应点属于目标对象的概率。用更简单的话来解释,输入图像中像素值越高(越白)的点就越可能代表我们要搜索的目标(在输入图像所在的位置)。这是一个直观的解释。直方图投影经常与camshift算法等一起使用。

我们要查找的对象要尽量占满这张图(换句话说,这张图像上最好是有且仅有我们要查找的对象)。最好使用颜色直方图,因为一个物体的颜色要比它的灰度更能更好的用来进行图像分割和图像识别。接着我们再把这个颜色直方图投影到输入图像中寻找我们的目标,也就是找到输入图像中的每一个像素点的像素值在直方图对应的概率,这样我们就得到了一个概率图像,最后设置适当的阈值对概率图进行二值化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import cv2
import numpy as np
roi = cv2.imread('7.jpg')
hsv = cv2.cvtColor(roi,cv2.COLOR_BGR2HSV)
target = cv2.imread('6.jpg')
hsvt = cv2.cvtColor(target,cv2.COLOR_BGR2HSV)

# 计算HSV色彩空间中色调和饱和度的直方图
# 这一步用于分析图像的色彩分布,以便后续的色彩定位
roihist = cv2.calcHist([hsv],[0, 1], None, [180, 256], [0, 180, 0, 256] )

# 对直方图进行归一化处理
# 使得直方图的值位于0-255之间,便于后续的处理和显示
cv2.normalize(roihist,roihist,0,255,cv2.NORM_MINMAX)

# 计算反向投影图像
# 反向投影是将直方图应用于原图像,用于突出显示与直方图匹配的色彩区域
dst = cv2.calcBackProject([hsvt],[0,1],roihist,[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)

alt text

图像金子塔

-上采样和下采样的目的与原理

上采样和下采样是图像处理中的两个重要操作,主要用于改变图像的分辨率。上采样通过增加像素点来放大图像,而下采样则通过减少像素点来缩小图像。

  • cv2.pyrUp()

该函数用于对图像进行上采样,即将图像的分辨率增加一倍。它通过插入像素值的平均值来实现放大。

参数说明:

src:输入的图像,可以是单通道或多通道的灰度图像或彩色图像。

dst:输出的图像,大小为输入图像的两倍。

dstsize:输出图像的大小,如果不指定,则默认为输入图像大小的两倍。

borderType:用于指定像素值的复制方式,可以是默认的边界模式。

返回值说明:返回经过上采样后的图像。

  • cv2.pyrDown()

该函数用于对图像进行下采样,即将图像的分辨率减小一半。它通过将相邻的像素值平均来实现图像的缩小。

参数说明:

src:输入的图像,可以是单通道或多通道的灰度图像或彩色图像。

dst:输出的图像,大小为输入图像的一半。

dstsize:输出图像的大小,如果不指定,则默认为输入图像大小的一半。

borderType:用于指定像素值的复制方式,可以是默认的边界模式。

返回值说明

返回经过下采样后的图像。

这两个函数在图像处理和计算机视觉中广泛应用,例如在图像金字塔的构建、图像的下采样和上采样等场景中。

高斯金字塔

alt text

拉普拉斯金子塔

alt text

1
2
3
4
5
6
7
8
9
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)

alt text

  • 图像金字塔还可以用来做图片融合

无缝融合

融合步骤

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

alt text

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

mport cv2
import numpy as np
A = cv2.imread('3.jpg')
B = cv2.imread('4.jpg')
# generate Gaussian pyramid for A
G = A.copy()
gpA = [G]
for i in range(6):
G = cv2.pyrDown(G)
gpA.append(G)
# generate Gaussian pyramid for B
G = B.copy()
gpB = [G]
for i in range(6):
G = cv2.pyrDown(G)
gpB.append(G)
# generate Laplacian Pyramid for A
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)
# generate Laplacian Pyramid for B
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)
# Now add left and right halves of images in each level
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)
# now reconstruct
ls_ = LS[0]
for i in range(1, 6):
ls_ = cv2.pyrUp(ls_)
ls_ = cv2.add(ls_, LS[i])
# image with direct connecting each half
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-模版匹配

模版匹配

单目标匹配

1
2
3
4
5
6
7
8
9
10
import cv2
img = cv2.imread('1.jpg', 0)
template = cv2.imread('2.jpg', 0)
h, w = template.shape
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, 255, 2)
cv2.imshow("img", img)
cv2.waitKey(0)

alt text

多目标匹配

1
2
3
4
5
6
7
8
9
10
11
12
import cv2
import numpy as np
img_rgb = cv2.imread('3.jpg')
img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
template = cv2.imread('4.jpg', 0)
h, w = template.shape
res = cv2.matchTemplate(img_gray, template, cv2.TM_CCOEFF_NORMED)
loc = np.where(res >= 0.8)
for pt in zip(*loc[::-1]):
cv2.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (0, 0, 255), 2)
cv2.imshow('img', img_rgb)
cv2.waitKey(0)

alt text

Canny边缘提取算法

1
2
3
4
5
6
import cv2
img = cv2.imread("1.jpg", 0)
img = cv2.GaussianBlur(img, (3, 3), 0)
canny = cv2.Canny(img, 50, 150)
cv2.imshow('Canny', canny)
cv2.waitKey(0)

alt text

轮廓

轮廓的查找与绘制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import cv2
img = cv2.imread('2.jpg')
imggray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(imggray,127,255,0)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE,
cv2.CHAIN_APPROX_SIMPLE)

# 从二值图像中提取轮廓
# 使用cv2.findContours函数来寻找图像中的轮廓,该函数返回一个轮廓列表和一个层次结构列表
# 参数thresh是经过二值化处理的图像,cv2.RETR_TREE定义了轮廓的检索模式为树形结构
# 参数cv2.CHAIN_APPROX_SIMPLE压缩了轮廓的水平方向、垂直方向和对角线方向的像素,只保留了轮廓的最外层像素
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)

面积、周长、重心

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import cv2
img = cv2.imread('03.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)

alt text

轮廓近似

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

1
2
3
4
5
6
7
8
9
10
11
12
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)

alt text

凸包与凸性检测 convexHull

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

函数 cv2.isContourConvex() 可以可以用来检测一个曲线是不是凸的。它只能返回 True 或 False
alt text

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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)) #False True
#说明轮廓曲线是非凸的,凸包曲线是凸的
img_contour= cv2.drawContours(img, [hull], -1, (0, 0, 255), 3)
cv2.imshow("img_contour", img_contour)
cv2.waitKey(0)

边界检测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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)

alt text

拟合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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)

alt text

轮廓性质

  • 边界矩形的宽高比
    alt text
    1
    2
    x,y,w,h = cv2.boundingRect(cnt)
    aspect_ratio = float(w)/h
  • 轮廓面积与边界矩形面积的比
    alt text
    1
    2
    3
    4
    area = cv2.contourArea(cnt)
    x,y,w,h = cv2.boundingRect(cnt)
    rect_area = w*h
    extent = float(area)/rect_area
  • 轮廓面积与凸包面积的比
    alt text
    1
    2
    3
    4
    area = cv2.contourArea(cnt)
    hull = cv2.convexHull(cnt)
    hull_area = cv2.contourArea(hull)
    solidity = float(area)/hull_area
  • 与轮廓面积相等的圆形的直径
    alt text
    1
    2
    area = cv2.contourArea(cnt)
    equi_diameter = np.sqrt(4*area/np.pi)
  • 对象的方向
    1
    2
    3
    # 通过最小二乘法拟合椭圆
    # 参数cnt是轮廓点的集合,函数返回椭圆的中心点坐标(x, y)、长轴和短轴的长度(MA, ma)以及旋转角度angle
    (x, y), (MA, ma), angle = cv2.fitEllipse(cnt)

对象的掩码

掩码mask:控制着处理感兴趣区域ROI,region of interest。掩码通常是一个二值图像,像素值为0表示不关心的区域,非0(通常是255)表示感兴趣的区域,通过应用掩码,我们可以将处理限制在感兴趣的区域内,从而提高处理效率并实现更精确的控制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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)
  • 最大值最小值以及它们的位置
    1
    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(imgray,mask = mask)
  • 使用相同的掩模求一个对象的平均颜色或平均灰度
    1
    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(imgray,mask = mask)

形状匹配

1
2
3
4
5
6
7
8
9
10
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, 2, 1)
cnt1 = contours[0]
ret, thresh2 = cv2.threshold(img2, 127, 255, 0)
_, contours, hierarchy = cv2.findContours(thresh2, 2, 1)
cnt2 = contours[0]
ret = cv2.matchShapes(cnt1, cnt2, 1, 0.0)
print(ret)

OpenCV-Hough变换

Hough变换

直线检测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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)
cv2.imshow("image_gray", image_gray)
edges = cv2.Canny(image_gray, 100, 150)
cv2.imshow("image_edges", edges)
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)

alt text

圆检测

1
2
3
4
5
6
7
8
9
10
11
12
13
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)

alt text

原理:

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

数学原理
alt text