三、OpenCV之边缘检测与图像轮廓

一、图像阈值

"""
图像(threshold)是图像分割的基准,基于这个基准,我们可以完成图像的二值化。这种分割是基于图像像素值级别的差异,通常应用于灰度图像。在二值化过程中,像素值高于(或有时低于)阈值的像素会被赋予一个新的像素值,通常是最大值(如255),而低于阈值的像素则会被赋予另一个值,通常是0。
图像阈值分割主要利用图像中要提取的目标区域与其背景在灰度特性上的差异,将图像看作具有不同灰度级的两类区域的组合。通过选取一个合理的阈值,可以确定图像中每个像素点应该属于目标区域还是背景区域,从而产生相应的二值图像。

ret, dst = cv2.threshold(src, thresh, max_val, type)
src: 输入图, 只能输入单通道图,通常来说就是灰度图
dst: 输出图
thresh: 阈值
max_val: 当像素值超过了阈值(或者小于阈值,根据type来决定),所赋予的值
type: 二值化操作类型, 包含以下5种类型:

    cv2.THRESH_BINARY       超过阈值 部分取max_val(最大值),否则取0
    cv2.THRESH_BINARY_INV   THRESH_BINARY的反转
    cv2.THRESH_TRUNC        大于阈值部分设为阈值,否则不变
    cv2.THRESH_TOZERO       大于阈值部分不改变,否则设为0
    cv2.THRESH_TOZERO_INV   THRESH_TOZERO的反转
"""
import cv2
import matplotlib.pyplot as plt

img_gray = cv2.imread("../images/cat.jpg", cv2.IMREAD_GRAYSCALE)
ret1, thresh1 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY)
ret2, thresh2 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY_INV)
ret3, thresh3 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_TRUNC)
ret4, thresh4 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_TOZERO)
ret5, thresh5 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_TOZERO_INV)

titles = ["Original image", "BINARY", "BINARY_INV", "TRUNC", "TOZERO", "TPZERO_INV"]
images = [img_gray, 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()

 

二、图像平滑处理(滤波函数)

# 均值卷积计算,类似中间数字 204, (3, 3)卷积就是,周围包含自己的 9 个数相加,然后求平均值

 

import cv2
import numpy as np


def cv_show(name, img):
    cv2.imshow(name, img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


img = cv2.imread("../images/lenaNoise.png")
# 均值滤波, 简单平均卷积操作
blur = cv2.blur(img, (3, 3))
res = np.hstack((img, blur))
cv_show("blur", res)

 

# 方框滤波, 基本和均值一样,可以选择归一化,
# -1 代表得到的和结果和颜色通道数是一致的,默认不用改
# normalize=True 类似就相当于求平均值, =False就是可能会越界,越界全部取最大值255
box_T = cv2.boxFilter(img, -1, (3, 3), normalize=True)
box_F = cv2.boxFilter(img, -1, (3, 3), normalize=False)
res = np.hstack((img, box_T, box_F))
cv_show("box", res)

 

# 高斯滤波, 高斯模糊的卷积核里的数值是满足高斯分布,相当于更重视中间
gaussian = cv2.GaussianBlur(img, (5, 5), 1)
res = np.hstack((img, gaussian))
cv_show("gaussian", res)

 

# 中值滤波,相当于用中值代替
median = cv2.medianBlur(img, 5)
res = np.hstack((img, median))
cv_show("median", res)

三、Canny边缘检测

Canny 边缘检测
1. 使用高斯滤波器, 以平滑图像, 滤除噪声
2. 计算图像中每个像素点的梯度强度和方向。
3. 应用非极大值抑制, 以消除边缘检测带来的杂散响应
4. 应用双阈值检测来确定真实的潜在的边缘
5. 通过抑制孤立的弱边缘最终完成边缘检测

 

 

 

 

"""
Canny 边缘检测
    1. 使用高斯滤波器, 以平滑图像, 滤除噪声
    2. 计算图像中每个像素点的梯度强度和方向。
    3. 应用非极大值抑制, 以消除边缘检测带来的杂散响应
    4. 应用双阈值检测来确定真实的潜在的边缘
    5. 通过抑制孤立的弱边缘最终完成边缘检测
"""
import cv2
import numpy as np


def cv_show(name, img):
    cv2.imshow(name, img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


img = cv2.imread("../images/lena.jpg", cv2.IMREAD_GRAYSCALE)
# 80, 150 指的就是 minValue, maxValue
v1 = cv2.Canny(img, 80, 150)
v2 = cv2.Canny(img, 50, 100)
res = np.hstack((img, v1, v2))
cv_show("res", res)

img = cv2.imread("../images/car.png", cv2.IMREAD_GRAYSCALE)
v1 = cv2.Canny(img, 120, 250)
v2 = cv2.Canny(img, 50, 100)
res = np.hstack((v1, v2))
cv_show("res", res)

 

四、图像金字塔

 

4.1 高斯金字塔

4.1.1(缩小,向上)

4.1.2 高斯金字塔(放大,向下)

import cv2
import numpy as np


def cv_show(name, img):
    cv2.imshow(name, img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


img = cv2.imread("../images/cat.jpg")
print(img.shape)  # (442, 340, 3)
up = cv2.pyrUp(img)
print(up.shape)  # (884, 680, 3)
down = cv2.pyrDown(img)
print(down.shape)  # (221, 170, 3)

# 注意: 原始图像放大之后的图像再缩小,得到的图像大小和原始图像一样,但是会出现丢失一些信息的操作

4.2 拉普拉斯金字塔

 

import cv2
import numpy as np


def cv_show(name, img):
    cv2.imshow(name, img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


img = cv2.imread("../images/cat.jpg")
down = cv2.pyrDown(img)
down_up = cv2.pyrUp(down)
l_l = img - down_up
res = np.hstack((img,  l_l))
cv_show("l_l", res)

五、图像轮廓

5.1 轮廓检测

"""
边缘检测主要是通过检测数字图像中明暗变化剧烈(即梯度变化比较大)的像素点,更偏向于关注图像中像素点的变化。例如,边缘是像素值发生突变的点,
这些点构成了一阶导数的极大值点或者二阶导数的过零点。边缘检测的结果通常是一幅边缘图,其中包含了边缘点的位置信息,这些信息可以用于进一步分析或处理。 相比之下,轮廓检测则更偏向于关注图像中对象的边界,以及这些边界所构成的完整形状。轮廓是由一系列具有相同或类似的BGR值或灰度值的连续的点构成的曲线,
可以用于形状分析以及物体的检测和识别。轮廓检测不仅提取出边缘,还包括边缘内部的点,以及这些点之间的拓扑关系,如父轮廓和内嵌轮廓的索引编号。
因此,轮廓检测得到的信息比边缘检测更为丰富和全面。
在实际应用中,边缘检测主要用于提取图像中的特征,如区分不同的物体或区域。而轮廓检测则更多地用于识别和理解图像中的目标对象,以及分析这些对象的形状和结构。 最后,需要注意的是,在进行轮廓检测之前,通常需要对图像进行预处理,如二值化或边缘检测,以提高轮廓检测的准确性和效果。这是因为轮廓检测对图像的清晰度和
对比度要求较高,而预处理步骤可以帮助改善图像的质量,使其更适合进行轮廓检测。 binary, contours, hierarchy = cv2.findContours(img, mode, method) CV3.X 有binary返回值 CV4.x没有 binary返回了 mode轮廓检索方式: cv2.RETR_EXTERNAL: 只检索最外面的轮廓 cv2.RETR_LIST : 检测所有的轮廓,并将其保存到一条链表中 cv2.RETR_CCOMP : 检测所有的轮廓,并将他们组织为两层;顶层为各部分的外部边界,第二层是空洞的边界 cv2.RETR_TREE : 检索所有的轮廓,并重构嵌套轮廓的整个层次 method轮廓逼近方法: cv2.CHAIN_APPROX_NONE : 以Freeman链码的方式输出轮廓,所有其他方法输出多边形(顶点的序列) cv2.CHAIN_APPROX_SIMPLE : 压缩水平的,垂直的和斜的部分,也就是函数只保留他们的终点部分 binary: 修改后的图像(通常不需要) contours: 检测到的轮廓,作为点集的列表 hierarchy: 关于图像拓扑的信息,例如那些轮廓是其他轮廓的子轮廓或者父轮廓等
""" import cv2 import numpy as np def cv_show(name, img): cv2.imshow(name, img) cv2.waitKey(0) cv2.destroyAllWindows() # 为了更高的准确率,使用二值图像 img = cv2.imread("../images/contours.png") gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 使用的CV4.X contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE) # 绘制轮廓 # 出入绘制图像,轮廓,轮廓索引,颜色模式,线条厚度 # 注意使用copy,否则原图会变 draw_img = img.copy() # -1代表所有的轮廓, (0, 0, 255)代表用红色来画, 2线条的宽度 res_1 = cv2.drawContours(draw_img, contours, -1, (0, 0, 255), 2) res = np.hstack((img, res_1)) cv_show("res", res) draw_img = img.copy() res_2 = cv2.drawContours(draw_img, contours, 0, (0, 0, 255), 2) res = np.hstack((img, res_2)) cv_show("res", res) # 轮廓特征 # 得到轮廓的信息 cnt = contours[0] # 面积 print(cv2.contourArea(cnt)) # 周长, True表示闭合的 print(cv2.arcLength(cnt, True))

 

 

res_2 = cv2.drawContours(draw_img, contours, 0, (0, 0, 255), 2)
res = np.hstack((img, res_2))
cv_show("res", res)

5.2 轮廓近似

import cv2
import numpy as np


def cv_show(name, img):
    cv2.imshow(name, img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


img = cv2.imread("../images/contours2.png")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
cnt = contours[0]

draw_img = img.copy()
res = cv2.drawContours(draw_img, [cnt], -1, (0, 0, 255), 2)
cv_show("res", res)

 

epsion = 0.05 * cv2.arcLength(cnt, True)  # 轮廓近似, 0.05代表近似取值
approx = cv2.approxPolyDP(cnt, epsion, True)
draw_img = img.copy()
res = cv2.drawContours(draw_img, [approx], -1, (0, 0, 255), 2)
cv_show("res", res)

5.3 边界图形

import cv2
import numpy as np


def cv_show(name, img):
    cv2.imshow(name, img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


img = cv2.imread("../images/contours.png")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
cnt = contours[0]

# 外接矩形
x, y, w, h = cv2.boundingRect(cnt)
draw_img1 = img.copy()
draw_img1 = cv2.rectangle(draw_img1, (x, y), (x + w, y + h), (0, 255, 0), 2)
cv_show("img", draw_img1)
# 外接圆
(x, y), radius = cv2.minEnclosingCircle(cnt)
center = (int(x), int(y))
radius = int(radius)
draw_img2 = img.copy()
img_radius = cv2.circle(draw_img2, center, radius, (0, 255, 0), 2)
cv_show("img", img_radius)

area = cv2.contourArea(cnt)
x, y, w, h = cv2.boundingRect(cnt)
rect_area = w * h
extent = float(area) / rect_area
print("轮廓面积与边界面积之比", extent)

 

cnt = contours[2]
x, y, w, h = cv2.boundingRect(cnt)
draw_img3 = img.copy()
draw_img3 = cv2.rectangle(draw_img3, (x, y), (x + w, y + h), (0, 255, 0), 2)
cv_show("img", draw_img3)

 

cnt = contours[3]
x, y, w, h = cv2.boundingRect(cnt)
draw_img4 = img.copy()
draw_img4 = cv2.rectangle(draw_img4, (x, y), (x + w, y + h), (0, 255, 0), 2)  # x
cv_show("img", draw_img4)

六、模板匹配

6.1 匹配一个对象

"""
模板匹配和卷积原理很像,模板在原图像上从原点开始滑动,计算模板与(图像呗模板被覆盖的地方)的
差别程度,这个差别程度的计算方法在OpenCV里有6中,然后将每次计算的结果放在一个矩阵里,作为结
果输出。假设原图形式是 A x B大小,而模板是 a x b 大小,则输出结果的矩阵是 (A-a+1)(B-b+1)

# 模板匹配函数
cv2.matchTemplate(src, template, type)

cv2.TM_SQDIFF          : 计算平方不同, 计算出来的值越小,越相关
cv2.TM_CCORR           : 计算相关性, 计算出来的值越大,越相关
cv2.TM_CCOEFF          : 计算相关系数,计算出来的值越大,越相关
cv2.TM_SQDIFF_NORMED   : 计算归一化平方不同, 计算出来的值越接近0, 越相关
cv2.TM_CCORR_NORMED    : 计算归一化相关性, 计算出来的值越接近1, 越相关
cv2.TM_CCOEFF_NORMED   : 计算归一化相关系数, 计算出来的值越接近1,越相关
"""

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


def cv_show(name, img):
    cv2.imshow(name, img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


def plt_show(num, res):
    plt.subplot(num)
    plt.imshow(res, cmap="gray")
    plt.xticks()
    plt.yticks()


img = cv2.imread("../images/lena.jpg", 0)  # 0 代表灰度图
template = cv2.imread("../images/face.png", 0)
h, w = template.shape[:2]
print(img.shape)  # (263, 263)
print(template.shape)  # (108, 96)

res = cv2.matchTemplate(img, template, cv2.TM_SQDIFF)
print(res.shape)  # (156, 168) (A-a+1)(B-b+1)

methods = ["cv2.TM_SQDIFF", "cv2.TM_CCORR", "cv2.TM_CCOEFF", "cv2.TM_SQDIFF_NORMED", "cv2.TM_CCORR_NORMED",
           "cv2.TM_CCOEFF_NORMED"]

min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
print(min_val)  # 5376.0        最小值
print(max_val)  # 84387872.0    最大值
print(min_loc)  # (101, 90)     最小值坐标位置
print(max_loc)  # (33, 155)     最大值坐标位置

for meth in methods:
    img2 = img.copy()
    # 匹配方法的真值
    method = eval(meth)
    res = cv2.matchTemplate(img, template, method)
    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)

    # 如果是平方差匹配TM_SQDIFF或归一化平方擦匹配TM_SQDIFF_NORMED,取最小值
    if method in (cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED):
        top_left = min_loc
    else:
        top_left = max_loc
    bottom_right = (top_left[0] + w, top_left[1] + h)

    # 画矩形
    cv2.rectangle(img2, top_left, bottom_right, 255, 2)
    plt_show(121, res)
    plt_show(122, img2)
    plt.suptitle(meth)
    plt.show()

6.2 匹配多个对象

# 说明: 代码有问题,具体情况尚不明白
import cv2
import numpy as np


def cv_show(name, img):
    cv2.imshow(name, img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
 

img_rgb = cv2.imread("../images/lena.jpg")
img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
template = cv2.imread("../images/face.png", 0)
h, w = template[:2]
res = cv2.matchTemplate(img_gray, template, cv2.TM_CCORR_NORMED)
threshold = 0.8  # 取匹配度大于80的坐标

loc = np.where(res >= threshold)
for pt in zip(*loc[::-1]):  # *表示可选参数
    bottom_right = (pt[0] + w, pt[1] + h)
    cv2.rectangle(img_rgb, pt, bottom_right, (0, 0, 255), 2)
cv_show("img_rgb", img_rgb)

 

posted on 2024-03-11 15:49  软饭攻城狮  阅读(120)  评论(0编辑  收藏  举报

导航