快速入门opencv(python版)

news2024/11/20 1:37:14

Open Source Computer Vision Library。OpenCV是一个(开源)发行的跨平台计算机视觉库,可以运行在Linux、Windows和Mac OS操作系统上。它轻量级而且高效——由一系列 C 函数和少量 C++ 类构成,同时提供了Python、Ruby、MATLAB等语言的接口,实现了图像处理和计算机视觉方面的很多通用算法。
在学习过程中遇到问题最好的办法就是查询opencv官方文档

笔者的运行环境:python3.7+pycharm+opencv4.6
资源下载:点这里哦

1. 图像的读取、显示与写入

图像的读取、显示与写入分别对应三个函数,cv2. imread()cv2.imshow()cv2.imwrite()

1.1. 读取图像

语法:cv2.imread(filename[, flags])--->image
参数:
filename---文件路径(相对路径和绝对路径),路径中不要带有中文
flags---可选标志,用于指定读取图像的样式,常见的有cv2.IMREAD_UNCHANGED(-1)、cv2.IMREAD_GRAYSCALE(0)、cv2.IMREAD_COLOR(1)。默认为1。
注意:opencv读取彩色图像的格式是BGR,而大多数视觉库使用的是RGB,因此当将 OpenCV 与其他工具包一起使用时,当从一个库切换到另一个库时,不要忘记交换蓝色和红色通道。

1.2. 显示图像

语法:cv2.imshow(window_name, image)--->None
参数:
window_name---显示图像的窗口的名字。
image---显示图像的变量名。
注意:该函数一般和cv2.waitKey()cv2.destroyAllWindows()cv2.destroyWindow()一起使用。cv2.waitKey()函数是键盘绑定函数,等待键击任意键或指定键继续程序。cv2.destroyAllWindows()用于销毁全部窗口(从内存中清除)、cv2.destroyWindow()销毁指定窗口(从内存中清除)。

import cv2

# 查看opencv版本print(cv2.getVersionString())

# 读取图像image_unchanged = cv2.imread("image\\cat.jpg", cv2.IMREAD_UNCHANGED)image_grayscale = cv2.imread("image\\cat.jpg", cv2.IMREAD_GRAYSCALE)image_color = cv2.imread("image\\cat.jpg", cv2.IMREAD_COLOR)

# 显示图像cv2.imshow('unchanged', image_unchanged)cv2.imshow('grayscale', image_grayscale)cv2.imshow('color', image_color)

# 永远暂停程序直到键击任意键cv2.waitKey(0)# 销毁所有窗口cv2.destroyAllWindows()

运行结果:

1.3. 写入图像

语法:cv2.imwrite(filename, image[, params])
参数:
filename---文件名,它必须包含文件的扩展名(如,.jpg.png
image----要保存的图像变量名。

import cv2

image_grayscale = cv2.imread("image\\cat.jpg", cv2.IMREAD_GRAYSCALE)cv2.imshow('grayscale', image_grayscale)cv2.imwrite("image\\cat_grayscale.jpg", image_grayscale)cv2.waitKey(0)cv2.destroyAllWindows()

运行结果如下:

2. 图像的色彩空间

常用的图像色彩空间有RGB、Lab、YCrCb、HSV等模式,
RGB模式:通过对红(R)、绿(G)、蓝(B)三个颜色通道的变化以及它们相互之间的叠加来得到各种颜色。依赖于光线。R、G、B的取值范围:[0, 255]。容易理解,但连续变换时不直观。
详见:RGB_百度百科
Lab模式:它是一种设备无关的颜色模型,也是一种基于生理特征的颜色模型。它由亮度(L)、颜色(a:绿色到洋红色;b:蓝色到黄色)组成。
详见:Lab颜色模型_百度百科
YCrCb模式:它派生自RGB颜色模式,主要应用在优化彩色视频信号的传输,使其向后相容老式黑白电视。Y:伽马校正后从RGB获得的亮度或亮度分量;Cr:反映了RGB输入信号红色部分与RGB信号亮度值之间的差异;Cb:反映的是RGB输入信号蓝色部分与RGB信号亮度值之间的差异。
HSV模式:它是H、S、V三维颜色空间中的一个可见光子集。是为了数字化图像提出来的,不能很好的表示人眼解释图像的过程。H:色相;S:饱和度;V:明度。取值范围,H---[0, 179],S---[0, 255],V---[0, 255]。
详见:HSV颜色模型_百度百科
灰度图:图像是有不同灰度的像素组成的,每个像素都是在[0, 255]取值。根据人眼敏感度,把RGB图片转换为灰度图,不是简单把RGB每个通道取平均值而是:Y = 0.299R + 0.587G + 0.114*B
注意:必须要指出的是图像就是一个数组,因此可以使用image.shape查询形状,其输出结果为 (pixel_height, pixel_width, 3)pixel_heightpixel_width代表图像的像素尺寸,3代表每个像素的颜色是由一个三维数组确定的(opencv中是BGR模式的数组)。
如何查看某像素的RGB、Lab、YCrCb、HSV值?
由于在读取图像时直接读取的是RGB值,因此可通过cv2.imshow()函数直接查看。
利用cv2.cvtColor(np.uint8([[[image[x, y]]]]), cv2.COLOR_BGR2Lab)[0][0]查看位置(x, y)处的Lab值。YCrCb、HSV与Lab类似。
注意:图像中某位置的图像色彩空间转换为其他时,必须要使其BGR "空间值"(由三个通道组成)三维化。
练习:如何通过鼠标的移动实时获取指定图片的不同色彩空间值?

import cv2import globimport numpy as np

# 定义鼠标回调的‘动作’函数def showPixelValue(event, x, y, flags, param):

    # 定义全局变量

    global img, combinedResult, placeholder

    # 判断鼠标是否移动

    if event == cv2.EVENT_MOUSEMOVE:

        # 获取鼠标坐标的RGB值

        bgr = img[y, x]

        # 转换色彩空间值

        ycb = cv2.cvtColor(np.uint8([[bgr]]), cv2.COLOR_BGR2YCrCb)[0][0]

        lab = cv2.cvtColor(np.uint8([[bgr]]), cv2.COLOR_BGR2Lab)[0][0]

        hsv = cv2.cvtColor(np.uint8([[bgr]]), cv2.COLOR_BGR2HSV)[0][0]

        # 创建一个与载入图片高度相同的黑色图块,作为结果区域

        placeholder = np.zeros((img.shape[0], 400, 3), dtype=np.uint8)

        # 在结果区域内显示鼠标位置不同色彩空间的值

        cv2.putText(placeholder, "BGR {}".format(bgr), (20, 70), cv2.FONT_HERSHEY_COMPLEX, .9, (255, 255, 255), 1,

                    cv2.LINE_AA)

        cv2.putText(placeholder, "HSV {}".format(hsv), (20, 140), cv2.FONT_HERSHEY_COMPLEX, .9, (255, 255, 255), 1,

                    cv2.LINE_AA)

        cv2.putText(placeholder, "YCrCb {}".format(ycb), (20, 210), cv2.FONT_HERSHEY_COMPLEX, .9, (255, 255, 255), 1,

                    cv2.LINE_AA)

        cv2.putText(placeholder, "LAB {}".format(lab), (20, 280), cv2.FONT_HERSHEY_COMPLEX, .9, (255, 255, 255), 1,

                    cv2.LINE_AA)

        # 合并加载的图片和结果区域

        combinedResult = np.hstack([img, placeholder])

        # 显示合并后的图像

        cv2.imshow('PRESS P for Previous, N for Next Image', combinedResult)

if __name__ == '__main__':

    # 读取指定的图片,并转换尺寸

    files = glob.glob('image/rub*.jpg')

    files.sort()

    img = cv2.imread(files[0])

    img = cv2.resize(img, (400, 400))

    # 显示转换尺寸后的图像(在‘PRESS...’显示框加载)

    cv2.imshow('PRESS P for Previous, N for Next Image', img)

    # 创建一个无加载图像的显示框,并覆盖了原‘PRESS...’显示框

    cv2.namedWindow('PRESS P for Previous, N for Next Image')

    # 创建一个鼠标回调,加载回调‘动作’函数,并且该动作在‘PRESS...’显示框内实现

    cv2.setMouseCallback('PRESS P for Previous, N for Next Image', showPixelValue)

    i = 0

    while 1:

        k = cv2.waitKey(1) & 0xFF

        # 键击‘n’切换到下一张图片

        if k == ord('n'):

            i += 1

            img = cv2.imread(files[i % len(files)])

            img = cv2.resize(img, (400, 400))

            cv2.imshow('PRESS P for Previous, N for Next Image', img)

        # 键击‘p’切换到上一张图片

        elif k == ord('p'):

            i -= 1

            img = cv2.imread(files[i % len(files)])

            img = cv2.resize(img, (400, 400))

            cv2.imshow('PRESS P for Previous, N for Next Image', img)

        # 键击‘Esc’退出程序

        elif k == 27:

            cv2.destroyAllWindows()

            break

运行结果如下(静态展示):

image.png

3. 图像编辑

图像编辑主要包括调整图像大小、裁剪图像、图像的旋转和移动、翻转,透视变换。

3.1. 调整大小

在opencv中使用image.shape获得图像大小,image.shape获得结果是(heigh, width, channels)即高度、宽度、通道数。在opencv中使用resize()函数调整图像的大小。
语法:cv2.resize(src, dsize[, dst[, fx[, fy[, interpolation]]]])--->dst
参数:
src---源图像。
dsize---可为tuple参数或None。目标图像的大小,即新的图像宽高。需要注意的是,dsize=(width, height)中第 一个参数是图像宽度第二个是高度,与shape=(height, width, channels)的相对位置刚好相反。
dst---目标图像,在python中无任何意义,一般不传参或设成None。
fx---沿水平轴的比例因子。fy---沿垂直轴的比例因子。
interpolation---插值方式,提供了调整图像大小的不同方法。其本质是一个int数值,不过一般用opencv内置的参 数名称以提高可读性。默认为cv2.INTER_LINEAR。其他还有cv2.INTER_AREAcv2.INTER_CUBICcv2.INTER_NEAREST
注意:当目标图像与源图像的宽高比不一致,代表经缩放后图像失真。
关于插值参数,①要缩小图片选用cv2.INTER_AREA;②放大图片选用cv2.INTER_CUBIC(速度慢)或cv2.INTER_LINEAR(速度快,效果还行)。

import cv2

cat = cv2.imread('image\\cat.jpg')

# 指定目标图像的宽高# 图像不失真cat_down_0 = cv2.resize(cat, (150, 150))cat_up_0 = cv2.resize(cat, (500, 500))cv2.imshow('cat_down-No distortion', cat_down_0)cv2.imshow('cat_up-No distortion', cat_up_0)# 图像失真cat_distortion = cv2.resize(cat, (200, 300))cv2.imshow('cat distortion', cat_distortion)cv2.waitKey(0)cv2.destroyAllWindows()

# 指定图像的比例因子(需要指出的是dsize必须要传参,None)# 图像不失真cat_down_1 = cv2.resize(cat, None, fx=0.75, fy=0.75)cat_up_1 = cv2.resize(cat, None, fx=1.2, fy=1.2)cv2.imshow('cat_down-No distortion1', cat_down_1)cv2.imshow('cat_up-No distortion1', cat_up_1)# 图像失真cat_distortion1 = cv2.resize(cat, None, fx=0.75, fy=1.2)cv2.imshow('cat distortion1', cat_distortion1)cv2.waitKey(0)cv2.destroyAllWindows()

# 指定图像缩放方法cat_scale0 = cv2.resize(cat, (150, 150), interpolation=cv2.INTER_AREA)cat_scale1 = cv2.resize(cat, (450, 450), interpolation=cv2.INTER_CUBIC)cat_scale2 = cv2.resize(cat, (450, 450), interpolation=cv2.INTER_LINEAR)cat_scale3 = cv2.resize(cat, (150, 150), interpolation=cv2.INTER_NEAREST)cv2.imshow('cat_scale0', cat_scale0)cv2.imshow('cat_scale1', cat_scale1)cv2.imshow('cat_scale2', cat_scale2)cv2.imshow('cat_scale3', cat_scale3)cv2.waitKey(0)cv2.destroyAllWindows()

运行结果(部分)如下:

3.2. 图像裁剪

裁剪图像是为了从图像中删除不需要的对象或区域;也就是删除其他,保留想要的图像区域。
opencv中的图像裁剪是利用numpy数组切片的方法来实现的。首先,我们要知道图像就是一个数组,其由高、宽、通道数三个维度组成。仅对图像进行裁剪,就是意味着对宽和高两个维度进行切片操作。具体如下:
语法:image[start_row : end_row, start_col : end_col]--->dst
参数:
start_row 、end_row---图像的开始与结束的行坐标。
start_col :、end_col---图像的开始与结束的列坐标。
注意:图像可视为坐标系,坐标原点为图像的左上角顶点,裁剪区域即是四条直线的围合区域。

import cv2

cat = cv2.imread('image\\cat.jpg')print(cat.shape)# 裁剪图像cropped_cat = cat[25:190, 50:300]cv2.imshow('original', cat)cv2.imshow('cropped', cropped_cat)cv2.waitKey(0)cv2.destroyAllWindows()

运行结果如下:

3.3. 旋转和平移

图像的旋转和平移是图像编辑中最基本的操作之一。旋转和平移都属于同一种图像编辑类型,即放射变换(Affine transformations)类型。
旋转和平移都是借助cv2.warpAffine()函数实现的,其区别是转换矩阵M的不同。旋转利用cv2.getRotationMatrix2D()函数构造旋转矩阵,平移矩阵利用二维数组构建。

首先看一下仿射变换函数,cv.warpAffine()需要指出的是,图像变换时其坐标系是不变的,与源图像一致。
语法:cv2.warpAffine(src, M, desize[, dst[, flags[, borderMode[, borderValue]]]])--->dst
参数:
src---源图像。M---转换矩阵。
desize---tuple类型参数。目标图像的宽高,即新的图像宽高(新的图像可能包含部分或者全部变换后的图像,其他区域一般由黑色像素填充)。坐标系遵循源图像坐标系。
dst---目标图像,在python中无任何意义,一般不传参或设成None。
flags---插值方法。cv2.INTER_NEAREST,最近邻插值;cv2.INTER_LINEAR,线性插值(默认值);cv2.INTER_AREA,区域插值;cv2.INTER_CUBIC,三次样条插值;cv2.INTER_LANCZOS4,Lanczos插值,等。
borderMode---边界像素模式。
borderValue---边界填充值(即显示框内图像之外的区域),默认值为0。

旋转矩阵的构造,cv2.getRotationMatrix2D()
语法:cv2.getRotationMatrix2D(center, angle, scale)--->M
参数:
center---图像的旋转中心,是一个元组类型的参数。即在图像构建的坐标系中选取旋转中心。
angle---旋转角度(角度制),正负值遵循坐标系角度规则。
scale---图像的缩放比例因子。

平移矩阵的构造。
构造数组,2Darray = np.ndarray([[1 0 tx], [0, 1, ty]])
参数:
tx---正值,图像将右移|tx|个像素;负值,将向左移|tx|个像素。
ty---正值,图像将下移|ty|个像素;负值,将向上移|ty|个像素
平移矩阵,M = 2Darray

import cv2import numpy as np

cat = cv2.imread('image\\cat.jpg')print(cat.shape)height, width = cat.shape[:2]

# 旋转图像center = (width/3, height/10)rotate_matrix = cv2.getRotationMatrix2D(center, -15, scale=1.2)rotated_cat = cv2.warpAffine(cat, rotate_matrix, dsize=(500, 300))# 原图像以坐标(width/3, height/10)为旋转中心旋转-15度然后再缩放1.2倍cv2.imshow('original image', cat)cv2.imshow('rotated_cat', rotated_cat)cv2.waitKey(0)cv2.destroyAllWindows()

# 平移图像tx, ty = width/5, -height/5translation_matrix = np.array([[1, 0, tx], [0, 1, ty]])translation_cat = cv2.warpAffine(cat, translation_matrix, dsize=(500, 300))cv2.imshow('original image', cat)cv2.imshow('translation_cat', translation_cat)cv2.waitKey(0)cv2.destroyAllWindows()

运行结果如下:

3.4. 图像翻转

图像翻转,也就是通常所说的镜像。主要包括垂直翻转、水平翻转、水平垂直翻转。
语法:cv2.flip(src, flipCode[, dst])--->dst
参数:
src---源图像。
flipCode---翻转方式。flipCode == 0,垂直翻转(沿x轴翻转);flipCode>0,水平翻转(沿y轴翻转);flipCode< 0,水平垂直翻转(先沿X轴翻转,再沿Y轴翻转,等价于旋转180°)。

import cv2import numpy as np

cat = cv2.imread('image\\cat.jpg')# 翻转图像flip_cat1 = cv2.flip(cat, flipCode=0)flip_cat2 = cv2.flip(cat, flipCode=1)flip_cat3 = cv2.flip(cat, flipCode=-1)combined = np.hstack((cat, flip_cat1, flip_cat2, flip_cat3))cv2.imshow('combined', combined)cv2.waitKey(0)cv2.destroyAllWindows()

运行结果:

4. 图像注释

注释图像即是在图像添加注释性信息,通常是绘制图形标出对象或者添加文字信息。需要指出的是在进行注释图像前一定要使用copy()方法创建副本,以确保源图像的完整性。

4.1. 绘制直线

语法:cv2.line(image, start_point, end_point, color, thickness)--->image
参数:
image---图像,通常是源图像的副本。
start_point---直线的起点坐标,元组参数。
end_point---直线的终点坐标,元组参数。
color---直线的颜色,元组参数。
thickness---直线的厚度。
注意:start_point与end_point的值第一个值代表的是横轴值,第二个值代表的是竖轴值,可简单理解为x、y轴的值。

import cv2

girl = cv2.imread('image\\girl.jpg')print(girl.shape)# 绘制一条长260像素的横向白色线girl_line = girl.copy()cv2.line(girl_line, (0, 250), (260, 250), (255, 255, 255), thickness=2)cv2.imshow('girl_line', girl_line)cv2.waitKey()cv2.destroyAllWindows()

运行结果如下:

4.2. 绘制圆形

语法:cv2.circle(image, center_coordinates, radius, color[, thickness[, lineType[, shift]]])--->image
参数:
image---图像,通常是源图像的副本。
center_coordinates---圆形的圆心坐标,元组参数。
radius---圆形的半径。
color---直线的颜色,元组参数。
thickness---正值,直线的厚度;负值表示绘制一个填充圆。
lineType---圆边线类型。
shift---圆心和半径值中的小数位数。
注意:start_point与end_point的值第一个值代表的是横轴值,第二个值代表的是竖轴值,可简单理解为x、y轴的值。

import cv2

# 绘制一个圆形girl = cv2.imread('image\\girl.jpg')girl_circle = girl.copy()cv2.circle(girl_circle, (380, 270), 175, (255, 255, 255), thickness=2)cv2.imshow('girl_circle', cv2.resize(girl_circle, None, fx=0.6, fy=0.6))

# 绘制一个填充圆girl_circle1 = girl.copy()cv2.circle(girl_circle1, (380, 270), 175, (255, 255, 255), thickness=-1)cv2.imshow('girl_circle1', cv2.resize(girl_circle1, None, fx=0.6, fy=0.6))cv2.waitKey()cv2.destroyAllWindows()

运行结果如下:

4.3. 绘制矩形

语法:cv2.rectangle(image, start_point, end_point, color[, thickness[, lineType[, shift]]])--->image
参数:
image---图像,通常是源图像的副本。
start_point---矩形的起始点,即其左上角,元组参数。
end_point---矩形的终点,即其右下角, 元组参数。
color---直线的颜色,元组参数。
thickness---正值,直线的厚度;负值表示绘制一个填充矩形。
lineType---矩形边线类型。
shift---坐标中的小数位数。
注意:start_point与end_point的值第一个值代表的是横轴值,第二个值代表的是竖轴值,可简单理解为x、y轴的值。

import cv2

# 绘制一个矩形girl = cv2.imread('image\\girl.jpg')print(girl.shape)girl_rec = girl.copy()cv2.rectangle(girl_rec, (200, 170), (570, 400), (255, 255, 255), thickness=2)cv2.imshow('girl_rec', cv2.resize(girl_rec, None, fx=0.6, fy=0.6))

# 绘制一个填充矩形girl_rec1 = girl.copy()cv2.rectangle(girl_rec1, (200, 170), (570, 400), (255, 255, 255), thickness=-2)cv2.imshow('girl_rec1', cv2.resize(girl_rec1, None, fx=0.6, fy=0.6))cv2.waitKey()cv2.destroyAllWindows()

运行结果如下:

4.4. 绘制椭圆

语法:cv2.ellipse(image, center, axesLength, angle, startAngle, endAngle, color[, thickness[,lineType[, shift]]])--->image
参数:
image---图像,通常是源图像的副本。
centert---椭圆的圆心坐标,元组参数。
axesLength---椭圆长轴与短轴大小的一半,元组参数。
angle---椭圆的旋转角度(角度制)。
startAngle---椭圆弧的起始角度。
endAngle---椭圆弧的结束角度。
color---直线的颜色,元组参数。
thickness---正值,直线的厚度;负值表示绘制一个填充矩形。
lineType---椭圆边线类型。
shift---坐标中的小数位数。
注意:①椭圆的角度均遵循顺逆时针规则,x轴正半轴为起始轴,顺时针为正,逆时针为负。②先绘制正常的椭圆,然后再根据参数angle以center为旋转中心旋转椭圆。

import cv2

# 绘制一个椭圆girl = cv2.imread('image\\girl.jpg')print(girl.shape)

girl_ellipse = girl.copy()cv2.ellipse(girl_ellipse, (380, 300), (200, 100), 45, 180, 360, (255, 255, 255), thickness=2)cv2.ellipse(girl_ellipse, (380, 300), (200, 100), 45, 0, 180, (0, 0, 255), thickness=2)cv2.imshow('girl_ellipse', girl_ellipse)cv2.waitKey()cv2.destroyAllWindows()

运行结果如下:

4.5. 添加文本

语法:cv2.putText(image, text, org, fontFace, fontScale, color[, thickness[, lineType[, bottomLeftOrigin]]])--->image
参数:
image---图像,通常是源图像的副本。
text---文本字符串。
org---text的左下角的位置。
fontFace---字体类型。
fontScale---字体的比例(用于乘以特定字体的基大小)。
color---字体颜色。
thickness---文本字体线条的粗细
lineType---字体边线类型。
bottomLeftOrigin---如果为 true,则图像数据原点位于左下角。否则,它位于左上角。

import cv2

girl = cv2.imread('image\\girl.jpg')print(girl.shape)girl_putText = girl.copy()text = 'Finally, let’s try annotating images with text. To do this, use the function in OpenCV'cv2.putText(girl_putText, text, (50, 400), cv2.QT_FONT_BLACK, 0.85, (255, 255, 255))cv2.imshow('1111', girl_putText)cv2.waitKey()cv2.destroyAllWindows()

运行结果如下:

5. 鼠标回调函数和跟踪栏回调函数

鼠标回调函数和跟踪栏可以达到如下动图的效果:


鼠标指针是图形用户界面(GUI)中的关键组件,没有鼠标指针就无法真正做到图形界面的人机交互。opencv中与鼠标指针相关的有两个重要的函数,即鼠标回调函数和创建跟踪栏。

5.1. 鼠标回调函数

我们通过实现鼠标标注图像这一功能来了解鼠标回调函数。
首先,定义一个特殊的“回调”函数,该函数将在显示窗口中显示的图像上绘制一个矩形。仅在计算机检测到某些用户界面事件时调用的函数,被称为“回调”函数。由于这个函数与鼠标相关联,因此一般命名为MouseCallback,不过你也可以自定义。这个函数在调用时无需输入任何参数,这是因为在用户与鼠标进行交互时,所需参数会自动填充。
然后,将“回调”函数与特定的鼠标事件相关联。每当调用此函数(用户和鼠标交互)时,位于显示图像上的鼠标会记录鼠标x轴、y轴的坐标,并记录鼠标的事件类型和事件标志。对于本例,当触发动作 cv2.LEFTBUTTONDOWN 和 cv2.LEFTBUTTONUP 时,我们将坐标存储在各自的变量中,并使用它们来绘制矩形。

①怎么定义鼠标回调函数?
语法:def MouseCallback(event, x, y, flags, userdata)
参数:
event---鼠标事件。
x, y---鼠标的x轴和y轴的坐标。
flags---事件标志。
userdata---其他的输入数据。
注意:定义回调函数时,函数名是可变的。

②怎么调用鼠标回调函数?
使用cv2.setMouseCallback()函数来调用鼠标回调函数。
语法:cv2.setMouseCallback(winname, onMouse[, userdata])
参数:
winname---窗口名称(必须是已经存在的窗口)。
onMouse---鼠标回调函数名。
userdata:传递给回调函数的可选参数。

③怎么使显示窗口和用户发生交互?
利用while循环和cv2.waitKey()函数指定键盘字符发生交互。具体而言:

  • 创建一个持续显示图像的 while 循环,直到用户按 'q' 键(ASCII 代码:113)退出应用程序脚本。
  • 在循环中为用户提供清除所有先前批注的功能。用户可以将命名窗口中的图像重置为我们在原始图像中读取时所做的副本。这可以通过检查键盘条目“c”(ASCII 代码:99)来实现。
  • 当用户退出循环时,我们使用cv2.destroyAllWindows() 销毁窗口。

④代码的具体实现。

import cv2

# 初始化参数top_left_corner, bottom_right_corner = [], []

# flags,*userdata虽然没有调用,但不可缺少def drawRectangle(event, x, y, flags, *userdata):

    global top_left_corner, bottom_right_corner

    if event == cv2.EVENT_LBUTTONDOWN:

        top_left_corner = [(x, y)]

    elif event == cv2.EVENT_LBUTTONUP:

        bottom_right_corner = [(x, y)]

        cv2.rectangle(image, top_left_corner[0], bottom_right_corner[0], (0, 255, 0), 2, 8)

        cv2.imshow('Window', image)

image = cv2.imread('image\\cat.jpg')temp = image.copy()cv2.namedWindow('Window')cv2.setMouseCallback('Window', drawRectangle)

k = 0# 键击‘q’退出程序,键击‘c’重新绘制矩形while k != 113:

    cv2.imshow("Window", image)

    k = cv2.waitKey(0)

    if k == 99:

        image = temp.copy()

        cv2.imshow("Window", image)

cv2.destroyAllWindows()

运行过程截图:

5.2. 跟踪栏回调函数

跟踪栏回调函数与鼠标回调函数类似。

①怎么定义跟踪栏回调函数?
语法:def TrackbarCallback(args)
参数:args---跟踪栏参数。args[0]可以检索跟踪栏条目位置,这是一个与用户发生交互的参数,其范围是(0, count]内的整数。
注意:定义回调函数时,函数名是可变的。

②怎么调用跟踪栏回调函数?
使用cv2.createTrackbar()函数来调用跟踪栏回调函数。
语法:cv2.createTrackbar(trackbarName, winname, value, count, onChange[, userdata])
参数:
trackbarName---创建的跟踪栏名称。
winname---显示窗口的名称。
value---滑块的默认位置。
count---滑块可滑到的最大位置。
onChange---回调函数名。
userdata---传递给回调函数的用户数据。它可用于处理跟踪栏事件,而无需使用全局变量。

③代码的具体实现。

import cv2

# 初始化参数maxScaleUp = 100scaleFactor = 1windowName = "Resize Image"trackbarValue = "Scale"

# 创建回调函数,图像可以在[1, 2]之间缩放def scaleImage(*args):

    scaleFactor = 1 + args[0]

    scaledImage = cv2.resize(image, None, fx=scaleFactor, fy=scaleFactor, interpolation=cv2.INTER_LINEAR)

    cv2.imshow(windowName, scaledImage)

image = cv2.imread("image\\jellyfish.jpg")cv2.namedWindow(windowName, cv2.WINDOW_AUTOSIZE)# 调用跟踪栏回调函数cv2.createTrackbar(trackbarValue, windowName, scaleFactor, maxScaleUp, scaleImage)

cv2.imshow(windowName, image)c = cv2.waitKey(0)cv2.destroyAllWindows()

运行过程截图:

6. 图像过滤

opencv使用卷积内核模糊(平滑)、锐化图像,也就是过滤图像。本节涉及到了卷积,建议对卷积有个基本了解,可参考:如何通俗易懂地解释卷积? - 知乎 本小节以下内容的图文来自如何通俗易懂地解释卷积? - 马同学的回答

6.1 卷积核及其应用

卷积核也被称为卷积矩阵,是用于过滤图像的二维矩阵,它通常是一个N阶方阵,其中N是奇数。卷积核的作用:使用卷积核对图像的每个像素执行数学运算,可以达到模糊(平滑)或锐化图像的目的。

卷积计算

前面已经提到,图像就是一个三维数组,当去掉通道维度时它就是一个矩阵,可用于参与矩阵的运算。比如,下图的噪点较多,此时我们可以使用卷积操作将图像进行平滑处理。具体过程如下:
①用矩阵表示图像;

②设置卷积核(卷积矩阵),做归一化处理(不改变图像的亮度)。(需要注意的是,图像处理中一般用的是正态分布矩阵,这里为了简单,就用了算术平均矩阵)

③卷积计算。若要平滑a1,1点,就在矩阵中,取出a1,1 点附近的点组成矩阵 f(形状与卷积矩阵相同) ,和卷积核进行卷积计算后,再填回去:

下面用一个动图诠释计算过程,

④写成一个公式:

将卷积核应用于图像

这里的仅简单介绍cv2.filter()函数。
语法:cv2.filter2D(src, ddepth, kernel)--->dst
参数:
src---源图像。
ddepth---目标图像的深度值。-1,表示目标图像与源图像的深度相同。
kernel---内核,也就是卷积核。矩阵。
注意:array = np.array[[0, 0, 0], [0, 1, 0], [0, 0, 0]],是一个恒等核,根据上述卷积计算可知经过恒等核过滤后的目标图像与源图像是一样的(外观上是一样的)。

import cv2import numpy as np

car = cv2.imread('image\\car.jpg')# 创建恒等核kernel1 = np.array([[0, 0, 0],

                    [0, 1, 0],

                    [0, 0, 0]])# 使用filter2D()函数执行线性滤波操作smooth_car = cv2.filter2D(car, ddepth=-1, kernel=kernel1)combine = np.hstack((car, smooth_car))# 显示原始图像和经过恒等核过滤后的图像cv2.imshow('original image on the left, smooth image on the right', combine)cv2.waitKey(0)cv2.imwrite('image\\smooth_car.jpg', smooth_car)cv2.destroyAllWindows()

运行结果如下:

import cv2import numpy as np

car = cv2.imread('image\\car.jpg')# 创建算术平方根核kernel1 = np.ones((3, 3), 'f4')/9# 使用filter2D()函数执行线性滤波操作smooth_car = cv2.filter2D(car, ddepth=-1, kernel=kernel1)combine = np.hstack((car, smooth_car))# 显示原始图像和经过恒等核过滤后的图像cv2.imshow('original image on the left, smooth image on the right', combine)cv2.waitKey(0)cv2.destroyAllWindows()

运行结果如下:

6.2. 内置函数模糊图像

语法:cv2.blur(src, ksize[, dst[, anchor[, borderType]]])--->dst
参数:
src---源图像。
ksize---模糊函数的内核尺寸,元组类型参数。
dst---目标图像,在python中无任何意义,一般不传参或设成None。
borderType---制作图形边界。
注意:该函数与使用算术平方根核模糊的图像结果完全相同。

import cv2import numpy as np

car = cv2.imread('image\\car.jpg')smooth_car = cv2.blur(car, ksize=(3, 3))combine = np.hstack((car, smooth_car))cv2.imshow('original image on the left, smooth image on the right', combine)cv2.waitKey(0)cv2.destroyAllWindows()

运行结果如下:

6.3. 高斯模糊

高斯模糊使用了高斯滤波器(即使用高斯(又称正太)分布作为滤波函数),该滤波器执行加权平均值,根据像素值与内核中心的距离对像素值进行加权,离中心较远的像素对加权平均值的影响较小。
语法:cv2.GaussianBlur(src, ksize, sigmaX[, dst[, sigmaY[, borderType]]])--->dst
参数:
src---源图像。
ksize---高斯核的尺寸,元组类型参数,一般设置为奇数方阵。
sigmaX---X方向上的高斯核标准差。
dst---目标图像,在python中无任何意义,一般不传参或设成None。
sigmaY---Y方向上的高斯核标准差。sigmaY=0,表示sigmaY=sigmaXsigmaY=0sigmaX=0,表示X和Y方向的高斯核标准差需要根据ksize.widthksize.height计算;为了完全控制结果,建议指定ksize、sigmaX和sigmaY的值。
borderType---制作图形边界。

import cv2import numpy as np

car = cv2.imread('image\\car.jpg')gaussian_blur_car = cv2.GaussianBlur(car, (3, 3), sigmaX=1, sigmaY=0)combine = np.hstack((car, gaussian_blur_car))cv2.imshow('original image on the left, smooth image on the right', combine)cv2.waitKey(0)cv2.imwrite('image\\gaussian_blur_car.jpg', gaussian_blur_car)cv2.destroyAllWindows()

运行结果如下:

6.4. 中值模糊

中值模糊是在源图像中的每个像素都替换为内核区域中图像像素的中值。
语法:cv2.medianBlur(src, ksize[, dst])--->dst
参数:
src---源图像。
ksize---内核的大小,其必须是大于1的奇数。
注意:对于相同的内核大小,中值模糊的效果比高斯模糊更突出。

import cv2import numpy as np

car = cv2.imread('image\\car.jpg')median_blur_car = cv2.medianBlur(car, 3)combine = np.hstack((car, median_blur_car))cv2.imshow('original image on the left, smooth image on the right', combine)cv2.waitKey(0)cv2.destroyAllWindows()

运行结果如下:

6.5. 自定义内核锐化图像

我们也可以利用卷积核锐化图像,其实现依赖于cv2.filter2D()函数。

import cv2import numpy as np

car = cv2.imread('image\\car.jpg')# 创建锐化内核(归一化处理,保持亮度不变)kernel1 = np.array([[0, -1, 0],

                    [-1, 3, 0],

                    [0, 0, 0]])# 使用filter2D()函数执行线性滤波操作sharp_car = cv2.filter2D(car, ddepth=-1, kernel=kernel1)combine = np.hstack((car, sharp_car))# 显示原始图像和经过自定义内核锐化后的图像cv2.imshow('original image on the left, smooth image on the right', combine)cv2.waitKey(0)cv2.imwrite('image\\sharp_car.jpg', sharp_car)cv2.destroyAllWindows()

运行结果如下:

6.6. 图像的双边滤波

通常,单一使用图像模糊或锐化是达不到预期效果的,此时我们可以选择使用双边滤波方法让二者共同作用在图像中。双边滤波本质上是对图像应用2D高斯模糊,且考虑了相邻像素强度的变化(用以降低边缘区域的高斯权重)。
让我们先来探讨下双边滤波的工作过程。假如我们正在过滤图像中靠近边缘的区域,普通的高斯模糊会根据实际权重模糊边缘,但是双边滤波器可以通过感知边缘(通过像素强度的差异)主动降低边缘区域的权重,从而减少高斯模糊对边缘的影响。因此,像素强度更均匀的区域更加模糊,边缘区域几乎无模糊。
图像中的过滤像素的最终值是由空间和强度权重决定,因此

  • 相似且接近过滤像素的像素将产生不同程度的影响(根据高斯权重);
  • 远离过滤像素的像素影响较小(由于高斯权重);
  • 过滤像素附近的像素强度差异越大影响越小,即使很靠近过滤像素。

opencv中利用cv2.bilateraFilter()函数实现双边过滤。
语法:cv2.bilateralFilter(src, d, sigmaColor, sigmaSpace[, dst[, borderType]])--->dst
参数:
src---源图像。
d---定义用于过滤的像素邻域的直径。
sigmaColor---颜色(像素)强度标准差,定义一维高斯分布,该分布指定允许的像素强度变化的程度。
sigmaSpace---x和y轴组成的空间的标准差。
borderType---制作图形边界。

import cv2import numpy as np

car = cv2.imread('image\\car.jpg')bilateral_filter_car = cv2.bilateralFilter(car, d=5, sigmaColor=40, sigmaSpace=50)combine = np.hstack((car, bilateral_filter_car))# 显示原始图像和经过自定义内核锐化后的图像cv2.imshow('original image on the left, smooth image on the right', combine)cv2.waitKey(0)cv2.destroyAllWindows()

运行结果如下:

7. 图像阈值处理

阈值又叫临界值,是指一个效应能够产生的最低值或最高值。图像的阈值是指灰度阈值,是针对灰度图像的。
阈值处理是指剔除图像内像素值高于阈值或低于阈值的像素点。利用图像阈值可以实现图像分割,即阈值分割法。根据阈值分割规则的作用区域可分为全局阈值分割和局部阈值分割两种。全局阈值分割方法包括直方图计数法、熵算法、Otsu算法;局部阈值分割是指自适应阈值法。
语法:cv2.threshold(src, thresh, maxValue, type[, dst])--->retval, dst
参数:
src---源图像。thresh---阈值。
maxValue---与cv2.THRESH_BINARYcv2.THRESH_BINARY_INV阈值类型一起使用的最大值。
type---阈值类型。cv2.THRESH_BINARYcv2.THRESH_BINARY_INYcv2.THRESH_TRUNCcv2.THRESH_TOZEROcv2.THRESH_TOZERO_INVcv2.THRESH_MASKcv2.THRESH_OTSUcv2.THRESH_TRIANGLE
返回值:retval---阈值。dst---目标图像。
这里只简单的演示使用cv2.THRESH_BINARY阈值类型的示例,仅作为入门了解。

import cv2import numpy as np

# 需要对图像做预处理——转化为灰度图src = cv2.imread('image\\cat.jpg', cv2.IMREAD_GRAYSCALE)retval, dst = cv2.threshold(src, 100, 255, cv2.THRESH_BINARY)print(retval)combine = np.hstack((src, dst))cv2.imshow('original image on the left, smooth image on the right', combine)cv2.waitKey()cv2.destroyAllWindows()

运行结果如下:

8. Blob检测

Blob,译文是“(颜色)的一小片,斑点”。它是图像中拥有共同属性(例如灰度值)的连通域,上图中的Blob就是灰色的连通域。Blob检测就是为了识别并标记这些连通域。
就上图而言,使用Blob检测可以达到如下效果:

Blob检测是通过cv2.SimpleBlobDeterctor算子实现的。

8.1. Blob检测是如何工作的?

  1. 阈值化(Thresholding):通过对源图像进行阈值化,将源图像转换为若干二值图像。阈值从minThreshold开始,以thresholdStep为间隔递增,直到maxThreshold。
  2. 分组(Grouping):对每份二值图像提取连通域。
  3. 合并(Merging):计算每个连通域的中心,并合并距离小于minDistBetweenBlobs的连通域。
  4. 返回(Returning):计算并返回 新合并的连通域的中心和半径。

8.2. 按颜色、大小和形状筛选Blob

  1. 按颜色:首先,需要明确需要颜色参数筛选Blob,即设置filterByColor=1(或True)。其次设置颜色倾向,blobColor=0以选择较暗的blob,blobColor=255以选择较浅的区域。
  2. 按大小:首先,需要明确需要大小参数筛选Blob,即设置filterByArea=1(或True)。其次设置minArea和maxArea的适当值。例如,设置minArea=50,maxArea=234,将滤除所有少于50和多于234个像素点的blob。
  3. 按形状:在opencv中形状由三个参数控制,圆度(Circularity)、凸度(Convexity)、惯性比(Inertia Ratio)。
  4. 圆度:Blob与圆的接近程度,其计算公式为

image.png

。例如正六边形的圆度大于正方形的。圆的圆度为1,正方形的为0.785。首先设置filterByCircularity=1(或True),然后设置适当的minCircularitymaxCircularity

  1. 凸度:Blob面积/凸包面积。首先设置filterByConvexity=1(或True),然后设置适当的minConvexitymaxConvexity(两值在[0, 1]范围)。
  2. 惯性比:对于一个圆,该值是1,对于椭圆它是0和1之间,而对于线是0。首先设置filterByInertia=1(或True),然后设置适当的minInertiRatiomaxInertiRatio(两值在[0, 1]范围)。

8.3. 设置cv2.SimpleBlobDeterctor参数

# 导入库import cv2import numpy as np

# 读取图像im = cv2.imread("image\\blob.jpg", cv2.IMREAD_GRAYSCALE)

# 设置SimpleBlobDetector参数params = cv2.SimpleBlobDetector_Params()

# 改变阈值params.minThreshold = 10params.maxThreshold = 200

# 根据面积过滤params.filterByArea = Trueparams.minArea = 1500

# 根据Circularity过滤params.filterByCircularity = Trueparams.minCircularity = 0.1

# 根据Convexity过滤params.filterByConvexity = Trueparams.minConvexity = 0.87

# 根据Inertia过滤params.filterByInertia = Trueparams.minInertiaRatio = 0.01

# 创建一个带有参数的检测器# opencv3.0前的版本使用cv2.SimpleBlobDetector()创建检测器# 3.0之后的版本使用cv2.SimpleBlobDetector_create()创建ver = cv2.__version__.split('.')if int(ver[0]) < 3:

    detector = cv2.SimpleBlobDetector(params)else:

    detector = cv2.SimpleBlobDetector_create(params)

# 检测blobskeypoints = detector.detect(im)

# 用红色圆圈画出检测到的blobs# cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS 确保圆的大小对应于blob的大小im_with_keypoints = cv2.drawKeypoints(im, keypoints, np.array([]), (0, 0, 255),

                                      cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)

# 结果显示cv2.imshow("Keypoints", im_with_keypoints)cv2.waitKey(0)

运行结果如下:


这里需要补充一个函数,cv2.drawKeypoints(),即绘制特征关键点函数。
语法:cv2.drawKeypoints(image, keypoints, outImage[, color[, flags]])-->outImage
参数:
image---源图像。
keypoints---特征点向量,向量内每个元素是一个keypoint对象,它包含了特征点的各种属性信息。
outImage---特征点绘制的画布对象,可以是原始图像。
color---绘制的特征点的颜色信息,默认绘制的是随机彩色。
flags---特征点的绘制模式,有以下几种模式可选。

DRAW_MATCHES_FLAGS_DEFAULT,只绘制特征点的坐标点,显示在图像上就是一个个小圆点,每个圆点的坐标都是特征点的坐标。②DRAW_MATCHES_FLAGS_DRAW_OVER_OUTIMG,函数不创建输出的图像,而是直接在输出图像变量空间绘制,要求本身输出图像变量就是一个初始化好了的,size与type都是已经初始化好的变量。③DRAW_MATCHES_FLAGS_NOT_DRAW_SINGLE_POINTS ,单点的特征点不被绘制。④DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS,绘制特征点的时候绘制的是一个个带有方向的圆,这种方法同时显示图像的坐标,size和方向,是最能显示特征的一种绘制方式。

9. 边缘检测

边缘检测是一种重要的图像处理技术,用于确定图片中物体的边界(边缘)或区域。边缘检测算法广泛应用于生产工作中的计算机视觉算法处理流水线中。
在进行检测前需要进行预处理,①边缘检测主要是依靠图像的像素强度来判别是否为边缘,因此需要将图像转为灰度图。②在对图像进行边缘检测之前需要进行平滑处理,以减少图像中的噪点。这是因为在边缘检测中,需要计算像素强度的数值导数,而噪点会产生不必要的干扰。
这里我们介绍两种重要的边缘检测算法:Sobel边缘检测和Canny边缘检测。

9.1. Sobel边缘检测

关于Sobel检测的原理,可参看Edge Detection Using OpenCV | LearnOpenCV #
语法:cv2.Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]])--->dst
参数:
src---源图像。ddepth---输出图像的位深度。
dx---x方向的求导阶数。dy---y方向的求导阶数。
ksize---Sobel卷积核的尺寸,必须是奇数。

import cv2

image = cv2.imread('image\\tiger.png', cv2.IMREAD_GRAYSCALE)# 平滑图像image_blur = cv2.GaussianBlur(image, (3, 3), sigmaX=1, sigmaY=0)# Sobel算子1,dx=1,dy=0sobel_x = cv2.Sobel(image_blur, cv2.CV_64F, dx=1, dy=0, ksize=3)# Sobel算子2,dx=0,dy=1sobel_y = cv2.Sobel(image_blur, cv2.CV_64F, dx=0, dy=1, ksize=3)# Sobel算子3,dx=1,dy=1sobel_xy = cv2.Sobel(image_blur, cv2.CV_64F, dx=1, dy=1, ksize=3)

cv2.imshow('Sobel X', cv2.resize(sobel_x, None, fx=0.6, fy=0.6))cv2.waitKey(0)

cv2.imshow('Sobel Y', cv2.resize(sobel_y, None, fx=0.6, fy=0.6))cv2.waitKey(0)

cv2.imshow('Sobel XY', cv2.resize(sobel_xy, None, fx=0.6, fy=0.6))cv2.waitKey(0)cv2.destroyAllWindows()

运行结果如下:

9.2. Canny边缘检测

关于Canny检测的原理,可参看Edge Detection Using OpenCV | LearnOpenCV #
语法:cv2.Canny(image, threshold1, threshold2[, edges[, apertureSie[, L2gradient]]]])--->dst
参数:
image---8位的图像。
threshold1---阈值1,强度小于此阈值的像素将被排除。
threshold2---阈值2,强度大于此阈值的像素将于实体边相关联。

import cv2

image = cv2.imread('image\\tiger.png', 0)# 平滑图像image_blur = cv2.GaussianBlur(image, (3, 3), sigmaX=1, sigmaY=0)edges = cv2.Canny(image_blur, 75, 200)cv2.imshow('Canny Edge Detection', edges)cv2.waitKey(0)cv2.destroyAllWindows()

运行结果如下:

10. 轮廓检测

轮廓检测可以检测物体的边界,并在图像中轻松定位它们。它可以被应用到图像前景提取、简单图像分割、检测和识别等工作中。
等高线:指具有相同颜色和强度的边界像素点连接而成的闭合线路,也就是轮廓。
在opencv中,我们可以使用cv2.findContours()cv2.drawContours()在图像中查找和绘制轮廓。在查找轮廓时,常用两种检测算法:cv2.CHAIN_APPROX_NONEcv2.CHAIN_APPROX_SIMPLE

10.1. 轮廓检测的步骤

  1. 读取图像并将其转换为灰度格式。这是由于轮廓检测是以阈值检测为基础的,而阈值又是针对灰度图而言的。
  2. 应用二值图像。利用阈值检测或Canny检测将灰度图转化为二值图,并将此二值图输入到查找轮廓算法中。
  3. 查找轮廓。使用cv2.findContours()函数检测图像轮廓。
  4. 绘制轮廓。使用cv2.drawContours()函数绘制图像轮廓。

10.2. 轮廓检测的实现

首先我们先来了解两个函数,cv2.findContours()cv2.drawContours()

cv2.findContours()
语法:cv2.findContours(image, mode, method[, contours[, hierarchy[, offset]]])--->contours, hierarchy
参数:
image---二值图像。
mode---轮廓检索模式。包括cv2.RETR_EXTERNALcv2.RETR_LISTcv2.RETR_CCOMPcv2.RETR_TREE。示例中,我们将使用cv2.RETR_TREE,这意味着将从二值图像中检索所有可能的轮廓。
method---轮廓近似方法。常用cv2.CHAIN_APPROX_NONEcv2.CHAIN_APPROX_SIMPLEcv2.CHAIN_APPROX_NONE存储所有的等高点;cv2.CHAIN_APPROX_SIMPLE压缩水平、垂直和对角线段,仅保留其端点。例如,举行轮廓使用4个角点进行编码。
hierarchy---层次结构。
offset---偏移。
返回:contours---轮廓线。hierarchy---层次结构。

cv2.drawContours()
语法:cv2.drawContours(image, contours, contourIdx, color[, thickness[, lineType[, hierarchy[, maxLevel[, offset]]]]])--->image
参数:
image---要在其上绘制轮廓的图像。
contours---从cv2.findContours()函数获得的轮廓线。
contoursIdx---等值线点的像素坐标列在获得的等值线中。使用此参数,可以指定此列表中的索引位置,以准确指示要绘制的轮廓点。提供负值将绘制所有等值线点。
color---要绘制的轮廓线的颜色。
thickness---要绘制的轮廓线的粗细,若为负数则填充轮廓线内部。

import cv2

apple = cv2.imread('image\\apple.jpg')image = cv2.cvtColor(apple, cv2.COLOR_BGR2GRAY)image_copy = apple.copy()

retval, dst = cv2.threshold(image, 100, 255, cv2.THRESH_BINARY)contours, hierarchy = cv2.findContours(dst, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)cv2.drawContours(image=image_copy, contours=contours, contourIdx=-1, color=(0, 255, 0), thickness=2)

cv2.imshow('111', image_copy)cv2.waitKey(0)cv2.destroyAllWindows()

运行结果如下:

import cv2

apple = cv2.imread('image\\apple.jpg')image = cv2.cvtColor(apple, cv2.COLOR_BGR2GRAY)image_copy = apple.copy()

retval, dst = cv2.threshold(image, 100, 255, cv2.THRESH_BINARY)contours, hierarchy = cv2.findContours(dst, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)cv2.drawContours(image=image_copy, contours=contours, contourIdx=-1, color=(0, 255, 0), thickness=2)

cv2.imshow('111', image_copy)cv2.waitKey(0)cv2.destroyAllWindows()

运行结果如下:

可以看到cv2.CHAIN_APPROX_NONEcv2.CHAIN_APPROX_SIMPLE的输出之间几乎没有差异。这是为什么呢?
这归功于 cv2.drawContours()函数。尽管cv2.CHAIN_APPROX_SIMPLE方法通常生成的点较少,但 cv2.drawContours()函数会自动连接相邻的点,即使它们不在轮廓列表中,也会连接它们。

10.3. 轮廓的层次结构

层次结构(hierarchy)表示等高线(轮廓线)之间的父子关系,在轮廓检测中轮廓检索模式mode对生成的轮廓层次结构产生影响。
为什么要引入层次结构概念呢?这是因为,我们既需要描绘图像中单个对象的轮廓,也需要描绘对象和其内部的轮廓。

通过上图中的几个形状和线条我们可以很好的了解层次结构,不同的数字显示了不同形状之间的层次结构。

  • 根据等高线层次结构和父子关系,所有单独的数字(即 1、2、3 和 4)都是单独的对象。
  • 我们可以说3a是3的孩子。请注意,3a 表示轮廓 3 的内部部分。
  • 等高线 1、2 和 4 都是父形状,没有任何关联的子形状,因此它们的编号是任意的。换句话说,轮廓 2 可以标记为 1,反之亦然。

cv2.findContours()函数返回的hierarchy(层次结构)是一个三维数组,该数组中的每个层次结构包含了四个值,分别是Next, Previous, First_Child, Parent。
Next---表示当前轮廓的下一个处于同一层次结构级别的轮廓索引。上图中,等高线1与2的结构级别处于同一层次结构级别上,因此1 Next---> 2;没有与等高线3处于同一层次结构级别的等高线,因此3 Next---> -1。
Previous---表示当前轮廓的上一个处于同一层次结构级别的轮廓索引。
First_Child---表示当前轮廓的第一个子轮廓索引。上图中等高线3的子轮廓是3a,因此3 First_Child---> 3a。
Parent---表示当前轮廓的父等高线的索引。

不同轮廓检索模式下的轮廓层次结构和轮廓线:
mode = cv2.RETR_LIST
RETR_LIST模式不会在提取的轮廓之间创建任何父子关系,因此,对于检测到的所有等高线的First_Child与Parent的值都是-1。

import cv2

image2 = cv2.imread('image\\shape.jpg')image = cv2.cvtColor(image2, cv2.COLOR_BGR2GRAY)

retval, thresh2 = cv2.threshold(image, 150, 255, cv2.THRESH_BINARY)contours5, hierarchy5 = cv2.findContours(thresh2, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)image_copy6 = image2.copy()cv2.drawContours(image_copy6, contours5, -1, (0, 255, 0), 2, cv2.LINE_AA)

cv2.imshow('RETR_LIST', image_copy6)print(f"RETR_LIST: \n{hierarchy5}")cv2.waitKey(0)cv2.imwrite('image\\contours_retr_list.jpg_LIST.jpg', image_copy6)cv2.destroyAllWindows()'''RETR_LIST: [[[ 1 -1 -1 -1]  [ 2  0 -1 -1]  [ 3  1 -1 -1]  [ 4  2 -1 -1]  [-1  3 -1 -1]]]'''

生成图如下:

mode = cv2.RETR_EXTERNAL
RETR_EXTERNAL模式仅检测父轮廓,并忽略任何子轮廓,因此所有的内部轮廓(如 3a 和 4)都不会被绘制。

import cv2

image2 = cv2.imread('image\\shape.jpg')image = cv2.cvtColor(image2, cv2.COLOR_BGR2GRAY)

retval, thresh2 = cv2.threshold(image, 150, 255, cv2.THRESH_BINARY)contours5, hierarchy5 = cv2.findContours(thresh2, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)image_copy6 = image2.copy()cv2.drawContours(image_copy6, contours5, -1, (0, 255, 0), 2, cv2.LINE_AA)

cv2.imshow('RETR_EXTERNAL', image_copy6)print(f"RETR_EXTERNAL: \n{hierarchy5}")cv2.waitKey(0)cv2.imwrite('image\\contours_retr_external.jpg', image_copy6)cv2.destroyAllWindows()'''RETR_EXTERNAL: [[[ 1 -1 -1 -1]  [ 2  0 -1 -1]  [-1  1 -1 -1]]]'''

生成图如下:

mode = cv2.RETR_CCOMP
RETR_CCOMP模式建立了两级层次结构,即轮廓线不是第一级层次结构就是第二级层次结构;若有嵌套的轮廓线,则2N+1(N是非负整数)是第一级层次结构。如下图所示,

import cv2

image2 = cv2.imread('image\\shape.jpg')image = cv2.cvtColor(image2, cv2.COLOR_BGR2GRAY)

retval, thresh2 = cv2.threshold(image, 150, 255, cv2.THRESH_BINARY)contours5, hierarchy5 = cv2.findContours(thresh2, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE)image_copy6 = image2.copy()cv2.drawContours(image_copy6, contours5, -1, (0, 255, 0), 2, cv2.LINE_AA)

cv2.imshow('RETR_CCOMP', image_copy6)print(f"RETR_CCOMP: \n{hierarchy5}")cv2.waitKey(0)cv2.imwrite('image\\contours_retr_ccomp.jpg', image_copy6)cv2.destroyAllWindows()'''RETR_CCOMP: [[[ 1 -1 -1 -1]  [ 3  0  2 -1]  [-1 -1 -1  1]  [ 4  1 -1 -1]  [-1  3 -1 -1]]]'''

生成图如下:

mode = cv2.RETR_TREE
RETR_TREE模式创建完整的层次结构,级别不限于两级,其视情况而定。

import cv2

image2 = cv2.imread('image\\shape.jpg')image = cv2.cvtColor(image2, cv2.COLOR_BGR2GRAY)

retval, thresh2 = cv2.threshold(image, 150, 255, cv2.THRESH_BINARY)contours5, hierarchy5 = cv2.findContours(thresh2, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)image_copy6 = image2.copy()cv2.drawContours(image_copy6, contours5, -1, (0, 255, 0), 2, cv2.LINE_AA)

cv2.imshow('RETR_TREE', image_copy6)print(f"RETR_TREE: \n{hierarchy5}")cv2.waitKey(0)cv2.imwrite('image\\contours_retr_tree.jpg', image_copy6)cv2.destroyAllWindows()'''RETR_TREE: [[[ 3 -1  1 -1]  [-1 -1  2  0]  [-1 -1 -1  1]  [ 4  0 -1 -1]  [-1  3 -1 -1]]]'''

生成图如下:

不同轮廓检索方式的运行时间比较:

轮廓检索方式

运行时间 (s)

cv2.RETR_LIST

0.000382

cv2.RETR_EXTERNAL

0.000554

cv2.RETR_CCOMP

0.001845

cv2.RETR_TREE

0.005594

  • cv2.RETR_LISTcv2.RETR_EXTERNAL执行所需的时间最少,因为cv2.RETR_LIST没有定义任何层次结构,cv2.RETR_EXTERNAL只检索父轮廓
  • cv2.RETR_CCOMP执行时间第次之,它检索所有轮廓并定义两级层次结构。
  • cv2.RETR_TREE执行所需的时间最长,因为它会检索所有轮廓,并为每个父子关系定义独立的层次结构级别。

11. 参考内容

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1269655.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PHP微信UI在线聊天系统源码 客服私有即时通讯系统 附安装教程

DuckChat是一套完整的私有即时通讯解决方案&#xff0c;包含服务器端程序和各种客户端程序&#xff08;包括iOS、Android、PC等&#xff09;。通过DuckChat&#xff0c;站点管理员可以快速在自己的服务器上建立私有的即时通讯服务&#xff0c;用户可以使用客户端连接至此服务器…

CAN网络出现错误帧从哪些方面去分析解决

标题&#xff1a;CAN网络出现错误帧从哪些方面去分析 实例1&#xff1a; 断电重启后&#xff0c;会有错误帧产生。 检查方案&#xff1a; 查看收发模块的初始化、使能是否在发送CAN报文之前完成&#xff1f; 实例2&#xff1a; 周期性报文&#xff0c;有时会冒出一帧错误帧&…

四则计算机实现(C++)(堆栈的应用)

算法要求&#xff1a; 输入一个数学表达式(假定表达式输入格式合法)&#xff0c;计算表达式结果并输出。数学表达式由单个数字和运算符“”、“-”、“*”、“/”、“(、) ”构成&#xff0c;例如 2 3 * ( 4 5 ) - 6 / 4。变量、输出采用整数&#xff0c;只舍不入。 图解算…

MySQL InnoDB Cluster

MySQL InnoDB Cluster 一、InnoDB Cluster 基本概述 MySQL InnoDB Cluster 为 MySQL 提供了一个完整的高可用解决方案。通过使用 MySQL Shell 提供的 AdminAPI,你可以轻松地配置和管理一组至少由3个MySQL服务器实例组成的 InnoDB 集群。 InnoDB 集群中的每个 MySQL 服务器实例…

linux无网络 无ip,显示网络未连接

标题:linux无网络 无ip&#xff0c;显示网络未连接 参考blog&#xff1a;Linux无网络连接问题排查 首先我们发现ens33没有ip地址&#xff0c;说明这个接口并没有被分到ip&#xff1b; 我们可以通过手动方式来给ens33获得网络ip sudo dhclient ens33&#xff0c;之后再输入ifc…

大数据Hadoop-HDFS_元数据持久化

大数据Hadoop-HDFS_元数据持久化 &#xff08;1&#xff09;在HDFS第一次格式化后&#xff0c;NameNode&#xff08;即图中的主NameNode&#xff09;就会生成fsimage和editslog两个文件&#xff1b; &#xff08;2&#xff09;备用NameNode&#xff08;即图中的备NameNode&…

ChatGPT Plus/GPT4高级数据分析和插件功能详解

ChatGPT 在论文写作与编程方面也具备强大的能力。无论是进行代码生成、错误调试还是解决编程难题&#xff0c;ChatGPT都能为您提供实用且高质量的建议和指导&#xff0c;提高编程效率和准确性。此外&#xff0c;ChatGPT是一位出色的合作伙伴&#xff0c;可以为您提供论文写作的…

微服务实战系列之Redis(cache)

前言 云淡天高&#xff0c;落木萧萧&#xff0c;一阵西北风掠过&#xff0c;似寒刀。冬天渐渐变得更名副其实了。“暖冬”的说法有点言过其实了。——碎碎念 微服务实战系列之Cache微服务实战系列之Nginx&#xff08;技巧篇&#xff09;微服务实战系列之Nginx微服务实战系列之F…

Structured Streaming: Apache Spark的流处理引擎

欢迎来到我们的技术博客&#xff01;今天&#xff0c;我们要探讨的主题是Apache Spark的一个核心组件——Structured Streaming。作为一个可扩展且容错的流处理引擎&#xff0c;Structured Streaming使得处理实时数据流变得更加高效和简便。 什么是Structured Streaming&#…

数据结构中的二分查找(折半查找)

二分法&#xff1a;顾名思义&#xff0c;把问题一分为2的处理&#xff0c;是一种常见的搜索算法&#xff0c;用于在有序数组或这有序列表中查找指定元素的位置&#xff0c;它的思想是将待搜索的区间不断二分&#xff0c;然后比较目标值与中间元素的大小关系&#xff0c;然后确定…

Failed to connect to gitee.com port 443: Time out 连接超时提示【Bug已完美解决-鸿蒙开发】

文章目录 项目场景:问题描述原因分析:解决方案:解决方案1解决方案2:解决方案3:此Bug解决方案总结解决方案总结**心得体会:解决连接超时问题的三种方案**项目场景: 导入Sample时遇到导入失败的情况,并提示“Failed to connect to gitee.com port 443: Time out”连接超…

实现一个通讯录

目录 项目要求及功能实现 通讯录的代码实现 contact.h: contanct.c main.c 项目要求及功能实现 1、可以存放100个人的信息 2、每个人的信息 名字 性别 年龄 电话 地址 3、增加联系人 4、删除指定联系人 5、查找指定联系人 6、修改指定联系人 7、显示联系人信息 8、排…

运维笔记111

运维笔记 Navicat中查询指定字段名所在的表名tomcat设置JVM的初始堆内存修改catalina.sh文件修改完保存并关闭tomcat启动tomcat 查询数据库连接数查询是否存在死锁 Navicat中查询指定字段名所在的表名 SELECT * FROM information_schema.COLUMNS WHERE COLUMN_NAME‘替换成你要…

Redis-Redis 高级数据结构 HyperLogLog与事务

Redis 高级数据结构 HyperLogLog HyperLogLog(Hyper [ˈhaɪpə(r)] ) 并不是一种新的数据结构 ( 实际类型为字符串类 型) &#xff0c;而是一种基数算法 , 通过 HyperLogLog 可以利用极小的内存空间完成独立总数的统计&#xff0c;数据集可以是 IP 、 Email 、 ID 等。 如…

【brew】Mac上安装vue3

先安装node。 这里我从其他博客找的方案&#xff0c;原始脚本下载太慢了。 cnpm的安装&#xff1a; 让npm更快一点。 npm install -g cnpm --registryhttps://registry.npm.taobao.org安装vue脚手架 2.0版本&#xff1a;sudo npm install -g vue-cli 3.0版本&#xff1a; sud…

对话式数据需求激增,景联文科技提供高质量多轮对话数据定制采集标注服务

大模型的快速发展使得数据服务需求激增&#xff0c;产品整体处于供不应求状态。对话式数据集成为当下需求热点&#xff0c;人们对于更复杂、更真实的多轮对话数据需求不断增加&#xff0c;定制化服务占据市场需求主流。 通过对多轮对话数据的训练&#xff0c;模型可以更好地理解…

解析Top-K问题及堆排序算法

Top-K问题是在海量数据中找到最大或最小的K个元素&#xff0c;它在实际应用中非常常见&#xff0c;例如专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。在面对大规模数据时&#xff0c;直接对数据进行排序可能效率低下&#xff0c;因为排序的时间复杂度通常为O(n lo…

Notepad++批量添加引号

工作中经常会遇到这样情景&#xff1a;业务给到一批订单号&#xff0c;需要查询这批订单的某些字段信息。在where条件中需要传入这些订单号的数组&#xff0c;并且订单号用引号引起&#xff0c;用引号隔开。 字符串之间长度相同 可以按住CtrlAlt和鼠标左键选中区域&#xff0…

IDEA如何配置Git 遇到问题的解决

新建项目 点击 会变红 会生成.git隐藏文件 配置远程仓库路径&#xff1a;点击Manage Remotes&#xff1a;将远程仓库的链接放到这里&#xff1a; 得到如下样式&#xff1a; 此时提交到本地仓库 点击add&#xff0c;添加到暂存文件&#xff1a; 此时文件变绿&#xf…

力扣日记11.28-【二叉树篇】二叉树的最小深度

力扣日记&#xff1a;【二叉树篇】二叉树的最小深度 日期&#xff1a;2023.11.28 参考&#xff1a;代码随想录、力扣 111. 二叉树的最小深度 题目描述 难度&#xff1a;简单 给定一个二叉树&#xff0c;找出其最小深度。 最小深度是从根节点到最近叶子节点的最短路径上的节点…