opencv图像处理常用操作一


读取显示图像

# 读取并显示图像
import cv2

path_to_image = r'pby.jpg'
"""
第二个参数
1 读取彩色,默认
0 读取灰度图
-1 加载图像,包括alpha通道
"""
original_image = cv2.imread(path_to_image, 1)
cv2.imshow('original image', original_image)
cv2.waitKey(0)
cv2.destroyAllWindows()

cv2.namedWindow('image', cv2.WINDOW_NORMAL)
cv2.imshow('image', original_image)
cv2.waitKey(0)
cv2.destroyAllWindows()

保存图像

# 保存图像
import cv2

img = cv2.imread('pby.jpg', 0)
cv2.imshow('image', img)
k = cv2.waitKey(0)
if k == 27:  # 等待ESC退出
    cv2.destroyAllWindows()
elif k == ord('s'):  # 等待关键字,保存和退出
    cv2.imwrite('gray.png', img)
    cv2.destroyAllWindows()

使用matplotlib

本文章不深入讲解matplotlib的使用,后续更新matplotlib的详细使用教程

"""
OpenCV加载的彩色图像处于BGR模式。但是Matplotlib以RGB模式显示。
"""
import cv2 as cv
from matplotlib import pyplot as plt

img = cv.imread('pby.jpg', 0)
plt.imshow(img, cmap='gray', interpolation='bicubic')
plt.xticks([]), plt.yticks([])  # 隐藏 x 轴和 y 轴上的刻度值
plt.show()

访问摄像头

import cv2 as cv

cap = cv.VideoCapture(0) # 参数可以是0-3,0表示默认摄像头
print("width is %s" % cap.get(cv.CAP_PROP_FRAME_WIDTH))
print("height is %s" % cap.get(cv.CAP_PROP_FRAME_HEIGHT))
if cap.set(cv.CAP_PROP_FRAME_WIDTH, 320):
    print('width now sets to 320')
if cap.set(cv.CAP_PROP_FRAME_HEIGHT, 240):
    print('height now sets to 240')

if not cap.isOpened():
    print("Cannot open camera")
    exit()
while True:
    # 逐帧捕获
    ret, frame = cap.read()
    # 如果正确读取帧,ret为True
    if not ret:
        print("Can't receive frame (stream end?). Exiting ...")
        break
    gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
    # 显示结果帧
    cv.imshow('frame', gray)
    if cv.waitKey(1) == ord('q'):
        break
# 完成所有操作后,释放捕获器
cap.release()
cv.destroyAllWindows()

读取视频文件

import cv2 as cv

cap = cv.VideoCapture('output.avi') # 传入视频路径
while cap.isOpened():
    ret, frame = cap.read()
    # 如果正确读取帧,ret为True
    if not ret:
        print("Can't receive frame (stream end?). Exiting ...")
        break
    gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
    cv.imshow('frame', gray)
    if cv.waitKey(25) == ord('q'):
        break
cap.release()
cv.destroyAllWindows()

写视频文件

import cv2 as cv

cap = cv.VideoCapture(0)
# if cap.set(cv.CAP_PROP_FRAME_WIDTH, 320):
#     print('width now sets to 320')
# if cap.set(cv.CAP_PROP_FRAME_HEIGHT, 240):
#     print('height now sets to 240')

fourcc = cv.VideoWriter_fourcc(*'XVID')  # 定义编解码器并创建VideoWriter对象
out = cv.VideoWriter('output.avi', fourcc, 20.0, (640, 480))
while cap.isOpened():
    ret, frame = cap.read()
    if not ret:
        print("Can't receive frame (stream end?). Exiting ...")
        break
    # gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
    out.write(frame)
    cv.imshow('frame', frame)
    if cv.waitKey(1) == ord('q'):
        break
# 完成工作后释放所有内容
cap.release()
out.release()
cv.destroyAllWindows()

在图像上画形状、文字

import cv2 as cv
import numpy as np

# 创建黑色的图像
img = np.zeros((512, 512, 3), np.uint8)
# 绘制一条厚度为5的蓝色对角线
cv.line(img, (0, 0), (511, 511), (255, 0, 0), 5)

# 画矩形
cv.rectangle(img, (384, 0), (510, 128), (0, 255, 0), 3)

# 画圆圈
cv.circle(img, (447, 63), 63, (0, 0, 255), -1)

# 画椭圆
cv.ellipse(img, (256, 256), (100, 50), 0, 0, 180, 255, -1)

# 画多边形
pts = np.array([[10, 5], [20, 30], [70, 20], [50, 10]], np.int32)
pts = pts.reshape((-1, 1, 2))

pts2 = np.array([[255, 240], [198, 189], [99, 345]], np.int32)
pts2 = pts2.reshape(-1, 1, 2)

cv.polylines(img, [pts], True, (0, 255, 255))  # 闭合多边形
cv.polylines(img, [pts2], False, (0, 255, 255))  # 只连接不闭合

# 向图片中添加文本
font = cv.FONT_HERSHEY_SIMPLEX
cv.putText(img, 'OpenCV', (10, 500), font, 4, (255, 255, 255), 2, cv.LINE_AA)

cv.imshow('img', img)
cv.waitKey(0)
cv.destroyAllWindows()

用鼠标画

import cv2 as cv
import numpy as np
import random


def main():
    # 鼠标回调函数
    def draw_circle(event, x, y, flags, param):
        if event == cv.EVENT_LBUTTONDBLCLK:
            cv.circle(img, (x, y), random.randint(50, 200), (255, 0, 0), -1)

    # 创建一个黑色的图像,一个窗口,并绑定到窗口的功能
    img = np.zeros((512, 512, 3), np.uint8)
    cv.namedWindow('image')
    cv.setMouseCallback('image', draw_circle)
    while True:
        cv.imshow('image', img)
        if cv.waitKey(20) & 0xFF == 27:  # 按esc退出
            break
    cv.destroyAllWindows()


if __name__ == '__main__':
    main()

轨迹栏的使用

import cv2 as cv
import numpy as np


def nothing(x):
    pass


# 创建一个黑色的图像
img = np.zeros((300, 512, 3), np.uint8)
cv.namedWindow('image')
# 创建颜色变化的轨迹栏
cv.createTrackbar('R', 'image', 0, 255, nothing)
cv.createTrackbar('G', 'image', 0, 255, nothing)
cv.createTrackbar('B', 'image', 0, 255, nothing)
# 为 ON/OFF 功能创建开关
switch = 'OFF/ON'
cv.createTrackbar(switch, 'image', 0, 1, nothing)
while True:
    cv.imshow('image', img)
    k = cv.waitKey(1) & 0xFF
    if k == 27:
        break
    # 得到四条轨迹的当前位置
    r = cv.getTrackbarPos('R', 'image')
    g = cv.getTrackbarPos('G', 'image')
    b = cv.getTrackbarPos('B', 'image')
    s = cv.getTrackbarPos(switch, 'image')
    if s == 0:
        img[:] = 0
    else:
        img[:] = [b, g, r]
cv.destroyAllWindows()

访问像素点

# 访问像素点
def access_px():
    img = cv.imread('pby.jpg')  # 加载彩色图像,默认是彩色图像
    px = img[100, 100]  # 通过行和列坐标来访问像素值
    print(px)  # [37 61 73]
    # 仅访问蓝色像素
    blue = img[100, 100, 0]  # opencv读取图片是BGR格式
    print(blue)  # 37
    img[100, 100] = [255, 255, 255]  # 修改该坐标对应的像素值
    print(img[100, 100])  # [255 255 255]
    # 上面的方法通常用于选择数组的区域,例如前5行和后3列。对于单个像素访问,Numpy数组方法array.item()和array.itemset())被认为更好,但是它们始终返回标量。
    # 如果要访问所有B,G,R值,则需要分别调用所有的array.item()
    # 访问 RED 值
    print(img.item(10, 10, 2))  # 60
    # 修改 RED 值
    img.itemset((10, 10, 2), 100)
    print(img.item(10, 10, 2))  # 100

访问图像属性

# 访问图像属性
def access_properties():
    img = cv.imread('pby.jpg')
    print(img.shape)  # 访问图片的形状,返回行,列,通道数(如果读取的是彩色图片)如果图像是灰度的,则返回的元组仅包含行数和列数
    print(img.size)  # 获取图片像素总数
    print(img.dtype)  # 获取图像数据类型

裁剪感兴趣区域

# 图像感兴趣区域
def roi():
    img = cv.imread('pby.jpg')
    ball = img[280:340, 330:390]
    cv.imshow('ball', ball)
    cv.waitKey(3000)
    cv.destroyAllWindows()

拆分合并通道

# 拆分/合并 通道
def split_merge():
    img = cv.imread('pby.jpg')
    b, g, r = cv.split(img)  # 拆分图像通道,此操作比较耗时
    b2 = img[:, :, 0]  # 前面两个切片划定区域,第三个数取0,1,2 指定通道
    cv.imshow('b2', b2)
    img[:, :, 2] = 0  # 指定红色通道,并修改所有像素值为0
    cv.imshow('changed every red px into 0', img)
    # cv.imshow('b', b)
    # cv.imshow('g', g)
    # cv.imshow('r', r)
    img_merge = cv.merge((b, g, r))  # 合并图像通道
    cv.imshow('merged image', img_merge)
    cv.waitKey(0)
    cv.destroyAllWindows()

添加边框

def board():
    BLUE = [255, 0, 0]
    img1 = cv.imread('opencv-logo.png')
    replicate = cv.copyMakeBorder(img1, 10, 10, 10, 10, cv.BORDER_REPLICATE)
    reflect = cv.copyMakeBorder(img1, 10, 10, 10, 10, cv.BORDER_REFLECT)
    reflect101 = cv.copyMakeBorder(img1, 10, 10, 10, 10, cv.BORDER_REFLECT_101)
    wrap = cv.copyMakeBorder(img1, 10, 10, 10, 10, cv.BORDER_WRAP)
    constant = cv.copyMakeBorder(img1, 10, 10, 10, 10, cv.BORDER_CONSTANT, value=BLUE)
    plt.subplot(231), plt.imshow(img1, 'gray'), plt.title('ORIGINAL')
    plt.subplot(232), plt.imshow(replicate, 'gray'), plt.title('REPLICATE')
    plt.subplot(233), plt.imshow(reflect, 'gray'), plt.title('REFLECT')
    plt.subplot(234), plt.imshow(reflect101, 'gray'), plt.title('REFLECT_101')
    plt.subplot(235), plt.imshow(wrap, 'gray'), plt.title('WRAP')
    # 图像由matplotlib显示 因此红色和蓝色通道将互换
    plt.subplot(236), plt.imshow(constant, 'gray'), plt.title('CONSTANT')
    plt.show()

执行结果:

图像加法&图像融合

def add_func():
    x = np.uint8([250])
    y = np.uint8([10])
    print(x)  # [250]
    print(y)  # [10]
    print(cv.add(x, y))  # [[255]] # 250+10 = 260 => 255 OpenCV加法是饱和运算
    print(x + y)  # [4] Numpy加法是模运算

# 图像融合 也是图像加法,但是对图像赋予不同的权重,以使其具有融合或透明的感觉。
def addWeighted_func():
    img1 = cv.imread('1.jpg')
    img2 = cv.imread('2.jpg')
    cv.imshow('img1', img1)
    cv.imshow('img2', img2)
    dst = cv.addWeighted(img1, 0.5, img2, 0.5, 0)
    cv.imshow('dst', dst)
    cv.waitKey(0)
    cv.destroyAllWindows()   

addWeighted_func的执行结果:

位操作&掩码

def bit_operation():
    img1 = cv.imread('1.jpg')
    img2 = cv.imread('opencv-logo.png')
    rows, cols, channels = img2.shape
    roi = img1[0:rows, 0:cols]
    # 现在创建logo的掩码,并同时创建其相反掩码
    img2gray = cv.cvtColor(img2, cv.COLOR_BGR2GRAY)
    ret, mask = cv.threshold(img2gray, 10, 255, cv.THRESH_BINARY)
    cv.imshow('img2gray', img2gray)
    cv.imshow('mask', mask)
    mask_inv = cv.bitwise_not(mask)
    cv.imshow('mask_inv', mask_inv)
    # 现在将ROI中logo的区域涂黑
    img1_bg = cv.bitwise_and(roi, roi, mask=mask_inv)
    # 仅从logo图像中提取logo区域
    img2_fg = cv.bitwise_and(img2, img2, mask=mask)
    # 将logo放入ROI并修改主图像
    dst = cv.add(img1_bg, img2_fg)
    img1[0:rows, 0:cols] = dst
    cv.imshow('res', img1)
    cv.waitKey(0)
    cv.destroyAllWindows()

执行结果:

以下图像依次为

灰度图

掩码一

掩码二

图像加法

使用opencv衡量代码性能

import cv2 as cv

# 使用opencv衡量代码性能
img1 = cv.imread('pby.jpg')
e1 = cv.getTickCount()
for i in range(5, 49, 2):
    img1 = cv.medianBlur(img1, i)
e2 = cv.getTickCount()
t = (e2 - e1) / cv.getTickFrequency()
print(t)  # 3.6950288

改变颜色空间

OpenCV中有超过150种颜色空间转换方法。但是我们将研究只有两个最广泛使用的,
BGR ↔ 灰色 和 BGR↔HSV。
对于颜色转换,我们使用cv函数。cvtColor(input_image, flag),其中flag决定转换的类型。
对于BGR→灰度转换,我们使用标志cv.COLOR_BGR2GRAY。
类似地,对于BGR→HSV,我们使用标志cv.COLOR_BGR2HSV。
要获取其他标记,只需在Python终端中运行以下命令

import cv2 as cv
# OpenCV中有超过150种颜色空间转换方法。但是我们将研究只有两个最广泛使用的,
# BGR ↔ 灰色 和 BGR↔HSV。
# 对于颜色转换,我们使用cv函数。cvtColor(input_image, flag),其中flag决定转换的类型。
# 对于BGR→灰度转换,我们使用标志cv.COLOR_BGR2GRAY。
# 类似地,对于BGR→HSV,我们使用标志cv.COLOR_BGR2HSV。
# 要获取其他标记,只需在Python终端中运行以下命令
flags = [i for i in dir(cv) if i.startswith('COLOR_')]
print(flags)  # ['COLOR_BAYER_BG2BGR', 'COLOR_BAYER_BG2BGRA', ....,'COLOR_YUV420sp2RGBA', 'COLOR_mRGBA2RGBA']

HSV的色相范围为[0,179],饱和度范围为[0,255],值范围为[0,255]。不同的软件使用不同的规模。因此,如果你要将OpenCV值和它们比较,你需要将这些范围标准化。

HSV颜色模型

HSV(Hue, Saturation, Value)是根据颜色的直观特性由A. R. Smith在1978年创建的一种颜色空间, 也称六角锥体模型(Hexcone Model)。、这个模型中颜色的参数分别是:色调(H),饱和度(S),亮度(V)。

色调H:用角度度量,取值范围为0°~360°,从红色开始按逆时针方向计算,红色为0°,绿色为120°,蓝色为240°。它们的补色是:黄色为60°,青色为180°,品红为300°;

饱和度S:取值范围为0.0~1.0;

亮度V:取值范围为0.0(黑色)~1.0(白色)。

RGB和CMY颜色模型都是面向硬件的,而HSV(Hue Saturation Value)颜色模型是面向用户的。

HSV模型的三维表示从RGB立方体演化而来。设想从RGB沿立方体对角线的白色顶点向黑色顶点观察,就可以看到立方体的六边形外形。六边形边界表示色彩,水平轴表示纯度,明度沿垂直轴测量。

HSV颜色分量范围

一般对颜色空间的图像进行有效处理都是在HSV空间进行的,然后对于基本色中对应的HSV分量需要给定一个严格的范围,下面是通过实验计算的模糊范围(准确的范围在网上都没有给出)。

H: 0— 180

S: 0— 255

V: 0— 255

此处把部分红色归为紫色范围:

HSV六棱锥

H参数表示色彩信息,即所处的光谱颜色的位置。该参数用一角度量来表示,红、绿、蓝分别纯度S为一比例值,范围从0到1,它表示成所选颜色的纯度和该颜色最大的纯度之间的比率。S=0时,只有灰度。相隔120度。互补色分别相差180度。

V表示色彩的明亮程度,范围从0到1。有一点要注意:它和光强度之间并没有直接的联系。

HSV对用户来说是一种直观的颜色模型。我们可以从一种纯色彩开始,即指定色彩角H,并让V=S=1,然后我们可以通过向其中加入黑色和白色来得到我们需要的颜色。增加黑色可以减小V而S不变,同样增加白色可以减小S而V不变。例如,要得到深蓝色,V=0.4 S=1 H=240度。要得到淡蓝色,V=1 S=0.4 H=240度。

一般说来,人眼最大能区分128种不同的色彩,130种色饱和度,23种明暗度。如果我们用16Bit表示HSV的话,可以用7位存放H,4位存放S,5位存放V,即745或者655就可以满足我们的需要了。由于HSV是一种比较直观的颜色模型,所以在许多图像编辑工具中应用比较广泛,如Photoshop(在Photoshop中叫HSB)等等,但这也决定了它不适合使用在光照模型中,许多光线混合运算、光强运算等都无法直接使用HSV来实现。

追踪指定颜色的物体

def track_color():
    cap = cv.VideoCapture(0)
    while True:
        # 读取帧
        _, frame = cap.read()
        # 转换颜色空间 BGR 到 HSV
        hsv = cv.cvtColor(frame, cv.COLOR_BGR2HSV)
        # 定义HSV中蓝色的范围
        lower_blue = np.array([110, 50, 50])
        upper_blue = np.array([130, 255, 255])
        # 设置HSV的阈值使得只取蓝色
        mask = cv.inRange(hsv, lower_blue, upper_blue)
        # 将掩膜和图像逐像素相加
        res = cv.bitwise_and(frame, frame, mask=mask)
        cv.imshow('frame', frame)
        cv.imshow('mask', mask)
        cv.imshow('res', res)
        k = cv.waitKey(5) & 0xFF
        if k == 27:
            break
    cv.destroyAllWindows()

执行结果如下:

可以看到还是有不少噪点的,这里是用电脑的前置摄像头拍摄,手机纯蓝色背景,受光照影响,显示效果不好

找到指定颜色的HSV值

def get_hsv_value():
    # 绿色的图片
    green = np.uint8([[[0, 255, 0]]])
    # 获取绿色的hsv值
    hsv_green = cv.cvtColor(green, cv.COLOR_BGR2HSV)
    print(hsv_green)  # [[[60 255 255]]]

现在把 [H- 10,100,100] 和 [H+ 10,255, 255] 分别作为下界和上界即可追踪指定颜色了

图像几何变换

1.缩放

# 缩放图片
def resize():
    img = cv.imread('pby.jpg')
    res1 = cv.resize(img, None, fx=0.25, fy=0.25, interpolation=cv.INTER_CUBIC)
    cv.imshow('res1', res1)
    # 或者
    height, width = img.shape[:2]
    res2 = cv.resize(img, (int(0.25 * width), int(0.25 * height)), interpolation=cv.INTER_CUBIC)
    cv.imshow('res2', res2)
    cv.waitKey(0)
    cv.destroyAllWindows()

2.平移

# 平移
def translate():
    img = cv.imread('pby.jpg', 0)
    rows, cols = img.shape
    M = np.float32([[1, 0, 100], [0, 1, 50]])
    dst = cv.warpAffine(img, M, (cols, rows))
    cv.imshow('img', dst)
    cv.waitKey(0)
    cv.destroyAllWindows()

3.旋转

# 旋转
def rotate():
    img = cv.imread('pby.jpg', 0)
    rows, cols = img.shape
    # cols-1 和 rows-1 是坐标限制
    M = cv.getRotationMatrix2D(((cols - 1) / 2.0, (rows - 1) / 2.0), 90, 1)
    print(M)
    # [[ 6.12323400e-17  1.00000000e+00 -1.13686838e-13]
    #  [-1.00000000e+00  6.12323400e-17  1.07900000e+03]]
    dst = cv.warpAffine(img, M, (cols, rows))
    cv.imshow('img', dst)
    cv.waitKey(0)
    cv.destroyAllWindows()

4.仿射变换

# 仿射变换
# 在仿射变换中,原始图像中的所有平行线在输出图像中仍将平行。为了找到变换矩阵,我们需要输入图像中的三个点及其在输出图像中的对应位置。
# 然后cv.getAffineTransform将创建一个2x3矩阵,该矩阵将传递给cv.warpAffine
def affine():
    img = cv.imread('drawing.png')
    rows, cols, ch = img.shape
    pts1 = np.float32([[50, 50], [200, 50], [50, 200]])
    pts2 = np.float32([[10, 100], [200, 50], [100, 250]])
    M = cv.getAffineTransform(pts1, pts2)
    print(M)
    dst = cv.warpAffine(img, M, (cols, rows))
    cv.imshow('input', img)
    cv.imshow('output', dst)
    cv.waitKey(0)
    cv.destroyAllWindows()

5.透视变换

# 透视变换
# 对于透视变换,您需要3x3变换矩阵。即使在转换后,直线也将保持直线。要找到此变换矩阵,需要在输入图像上有4个点,在输出图像上需要相应的点。
# 在这四个点中,其中三个不应共线。然后通过函数cv.getPerspectiveTransform找到变换矩阵。然后将cv.warpPerspective应用于此3x3转换矩阵
def perspective_transform():
    img = cv.imread('sudoku.png')
    # rows, cols, ch = img.shape
    pts1 = np.float32([[56, 65], [368, 52], [28, 387], [389, 390]])
    pts2 = np.float32([[0, 0], [300, 0], [0, 300], [300, 300]])
    M = cv.getPerspectiveTransform(pts1, pts2)
    dst = cv.warpPerspective(img, M, (300, 300))
    cv.imshow('img', img)
    cv.imshow('dst', dst)
    cv.waitKey(0)
    cv.destroyAllWindows()
    # plt.subplot(121), plt.imshow(img), plt.title('Input')
    # plt.subplot(122), plt.imshow(dst), plt.title('Output')
    # plt.show()

图像阈值处理

简单阈值处理

threshold函数用于应用阈值。其参数分别为:

1.源图像(必须为灰度图)

2.阈值

3.超出阈值时所分配的最大值

4.阈值类型

阈值类型主要有以下几种:

import cv2 as cv
from matplotlib import pyplot as plt


# 简单阈值,对于每个像素,应用相同的阈值。如果像素值小于阈值,则将其设置为0,否则将其设置为最大值。
def simple_threshold():
    img = cv.imread('pby.jpg')  # 读取图片
    img_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)  # 转成灰度图
    # threshold函数 用于应用阈值。其参数分别为:1.源图像(必须为灰度图)2.阈值 3.超出阈值时所分配的最大值4.阈值类型
    ret, thresh1 = cv.threshold(img_gray, 127, 255, cv.THRESH_BINARY)
    ret, thresh2 = cv.threshold(img_gray, 127, 255, cv.THRESH_BINARY_INV)
    ret, thresh3 = cv.threshold(img_gray, 127, 255, cv.THRESH_TRUNC)
    ret, thresh4 = cv.threshold(img_gray, 127, 255, cv.THRESH_TOZERO)
    ret, thresh5 = cv.threshold(img_gray, 127, 255, cv.THRESH_TOZERO_INV)
    titles = ['Original Image', 'gray image', 'BINARY', 'BINARY_INV', 'TRUNC', 'TOZERO', 'TOZERO_INV']
    img_rgb = cv.cvtColor(img, cv.COLOR_BGR2RGB)
    images = [img_rgb, img_gray, thresh1, thresh2, thresh3, thresh4, thresh5]
    for i in range(7):
        plt.subplot(2, 4, i + 1), plt.imshow(images[i], 'gray')
        plt.title(titles[i])
        plt.xticks([]), plt.yticks([])
    plt.show()

运行结果如下:

自适应阈值处理

# 当同一幅图像上的不同部分具有不同亮度时。这种情况下我们需要采用自适应阈值。此时的阈值是根据图像上的每一个小区域计算与其对应的阈值。
# 因此在同一幅图像上的不同区域采用的是不同的阈值,从而使我们能在亮度不同的情况下得到更好的结果。
def adaptive_threshold():
    img = cv.imread('pby.jpg')
    img_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
    # 中值滤波
    img_gray = cv.medianBlur(img_gray, 5)
    ret, th1 = cv.threshold(img_gray, 127, 255, cv.THRESH_BINARY)
    # 11 为 Block size,即邻域大小,用于计算阈值的窗口大小, 2 为 常数,可以理解为偏移量
    th2 = cv.adaptiveThreshold(img_gray, 255, cv.ADAPTIVE_THRESH_MEAN_C, cv.THRESH_BINARY, 11, 2)
    th3 = cv.adaptiveThreshold(img_gray, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY, 11, 2)
    titles = ['Original Image', 'Global Thresholding (v = 127)', 'Adaptive Mean Thresholding',
              'Adaptive Gaussian Thresholding']
    img_rgb = cv.cvtColor(img, cv.COLOR_BGR2RGB)
    images = [img_rgb, 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()

运行结果:

中值滤波

这里介绍一下中值滤波的概念

无论是直接获取的灰度图像,还是由彩色图像转换得到的灰度图像,里面都有噪声的存在,噪声对图像质量有很大的影响。进行中值滤波不仅可以去除孤点噪声,而且可以保持图像的边缘特性,不会使图像产生显著的模糊,比较适合于实验中的人脸图像。

中值滤波是一种非线性的信号处理方法,因此中值滤波器也就是一种非线性的滤波器。在一定条件下,其可以克服线性滤波器处理图像细节模糊的问题,而且它对滤除脉冲干扰和图像扫描噪声非常有效,但是,对点、线、尖顶等细节较多的图像,则会引起图像信息的丢失。中值滤波器最先被应用于一维信号的处理中,后来被人们引用到二维图像的处理中来。

中值滤波是对一个滑动窗口内的诸像素灰度值排序,用其中值代替窗口中心像素的原来灰度值,它是一种非线性的图像平滑法,它对脉冲干扰级椒盐噪声的抑制效果好,在抑制随机噪声的同时能有效保护边缘少受模糊。

中值滤波可以过滤尖峰脉冲。目的在于我们对于滤波后的数据更感兴趣。滤波后的数据保留的原图像的变化趋势,同时去除了尖峰脉冲对分析造成的影响。

以一维信号的中值滤波举例。对灰度序列80、120、90、200、100、110、70,如果按大小顺序排列,其结果为70、80、90、10O、110、120、200,其中间位置上的灰度值为10O,则该灰度序列的中值即为100。一维信号中值滤波实际上就是用中值代替规定位置(一般指原始信号序列中心位置)的信号值。对前面所举的序列而言,中值滤波的结果是用中值100替代序列80、120、90、200、100、110、70中的信号序列中心位置值200,得到的滤波序列就是80、120、90、100、100、110、70。如果在此序列中200是一个噪声信号,则用此方法即可去除这个噪声点。

二维中值滤波算法是对于一幅图像的像素矩阵,取以目标像素为中心的一个子矩阵窗口,这个窗口可以是33 ,55 等根据需要选取,对窗口内的像素灰度排序,取中间一个值作为目标像素的新灰度值。窗口示例如ooooxoooo上面x为目标像素,和周围o组成3*3矩阵Array,然后对这9个元素的灰度进行排序,以排序后的中间元素Array[4]为x的新灰度值,如此就完成对像素x的中值滤波,再迭代对其他需要的像素进行滤波即可。

中值滤波的基本思想是,把局部区域的像素按灰度等级进行排序,取该领域中灰度的中值作为当前像素的灰度值。

中值滤波的步骤为:

  • 将滤波模板(含有若干个点的滑动窗口)在图像中漫游,并将模板中心与图中某个像素位置重合;

  • 读取模板中各对应像素的灰度值;

  • 将这些灰度值从小到大排列;

  • 取这一列数据的中间数据,将其赋给对应模板中心位置的像素。如果窗口中有奇数个元素,中值取元素按灰度值大小排序后的中间元素灰度值。如果窗口中有偶数个元素,中值取元素按灰度值大小排序后,中间两个元素灰度的平均值。

因为图像为二维信号,中值滤波的窗口形状和尺寸对滤波器效果影响很大,不同图像内容和不同应用要求往往选用不同的窗口形状和尺寸。

由以上步骤,可以看出,中值滤波对孤立的噪声像素即椒盐噪声、脉冲噪声具有良好的滤波效果。由于其并不是简单的取均值,所以,它产生的模糊也就相对比较少。

二值化

# otsu 二值化
# 在使用全局阈值时,我们就是随便给了一个数来做阈值,那我们怎么知道我们选取的这个数的好坏呢?答案就是不停的尝试。
# 如果是一副双峰图像(简单来说双峰图像是指图像直方图中存在两个峰)呢?我们岂不是应该在两个峰之间的峰谷选一个值作为阈值?
# 这就是 Otsu 二值化要做的。简单来说就是对一副双峰图像自动根据其直方图计算出一个阈值。(对于非双峰图像,这种方法得到的结果可能会不理想)。
# 这里用到的函数还是 cv2.threshold(),但是需要多传入一个参数(flag):cv2.THRESH_OTSU。这时要把阈值设为 0。然后算法会找到最优阈值,
# 这个最优阈值就是返回值 retVal。如果不使用 Otsu 二值化,返回的retVal 值与设定的阈值相等。
# 下面的例子中,输入图像是一副带有噪声的图像。
# 第一种方法,我们设127 为全局阈值。
# 第二种方法,我们直接使用 Otsu 二值化。
# 第三种方法,我们首先使用一个 5x5 的高斯核除去噪音,然后再使用 Otsu 二值化。
def otsu():
    img = cv.imread('noisy.png')
    img_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
    # global thresholding
    ret1, th1 = cv.threshold(img_gray, 127, 255, cv.THRESH_BINARY)
    # Otsu's thresholding
    ret2, th2 = cv.threshold(img_gray, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)
    # Otsu's thresholding after Gaussian filtering
    # (5,5)为高斯核的大小,0 为标准差
    blur = cv.GaussianBlur(img_gray, (5, 5), 0)
    # 阈值一定要设为 0!
    ret3, th3 = cv.threshold(blur, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)
    # plot all the images and their histograms
    images = [img_gray, 0, th1,
              img_gray, 0, th2,
              blur, 0, th3]
    titles = ['Original Noisy Image', 'Histogram', 'Global Thresholding (v=127)',
              'Original Noisy Image', 'Histogram', "Otsu's Thresholding",
              'Gaussian filtered Image', 'Histogram', "Otsu's Thresholding"]
    # 这里使用了 pyplot 中画直方图的方法,plt.hist, 要注意的是它的参数是一维数组
    # 所以这里使用了(numpy)ravel 方法,将多维数组转换成一维,也可以使用 flatten 方法
    # ndarray.flat 1-D iterator over an array.
    # ndarray.flatten 1-D array copy of the elements of an array in row-major order.
    for i in range(3):
        plt.subplot(3, 3, i * 3 + 1), plt.imshow(images[i * 3], 'gray')
        plt.title(titles[i * 3]), plt.xticks([]), plt.yticks([])
        plt.subplot(3, 3, i * 3 + 2), plt.hist(images[i * 3].ravel(), 256)
        plt.title(titles[i * 3 + 1]), plt.xticks([]), plt.yticks([])
        plt.subplot(3, 3, i * 3 + 3), plt.imshow(images[i * 3 + 2], 'gray')
        plt.title(titles[i * 3 + 2]), plt.xticks([]), plt.yticks([])
    plt.show()

执行结果:

otsu二值化工作原理

# 手动计算阈值
def calculate_otsu():
    img = cv.imread('noisy.png')
    img_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
    blur = cv.GaussianBlur(img_gray, (5, 5), 0)
    # find normalized_histogram, and its cumulative distribution function
    # 计算归一化直方图
    # CalcHist(image, accumulate=0, mask=NULL)
    hist = cv.calcHist([blur], [0], None, [256], [0, 256])
    hist_norm = hist.ravel() / hist.max()
    Q = hist_norm.cumsum()
    bins = np.arange(256)
    fn_min = np.inf
    thresh = -1
    for i in range(1, 256):
        p1, p2 = np.hsplit(hist_norm, [i])  # probabilities
        q1, q2 = Q[i], Q[255] - Q[i]  # cum sum of classes
        b1, b2 = np.hsplit(bins, [i])  # weights
        # finding means and variances
        m1, m2 = np.sum(p1 * b1) / q1, np.sum(p2 * b2) / q2
        v1, v2 = np.sum(((b1 - m1) ** 2) * p1) / q1, np.sum(((b2 - m2) ** 2) * p2) / q2
        # calculates the minimization function
        fn = v1 * q1 + v2 * q2
        if fn < fn_min:
            fn_min = fn
            thresh = i
    # find otsu's threshold value with OpenCV function
    ret, otsu, = cv.threshold(blur, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)
    print(thresh)
    print(ret)

图像平滑处理

自定义滤波器进行卷积

# 在介绍四种模糊(平滑)滤波器之前,先使用自定义滤波器对图像进行卷积处理
def custom_filter():
    img = cv2.imread('opencv-logo.png')
    kernel = np.ones((5, 5), np.float32) / 25
    print(kernel)
    # cv.Filter2D(src, dst, kernel, anchor=(-1, -1))
    # depth –desired depth of the destination image;
    # if it is negative, it will be the same as src.depth();
    # the following combinations of src.depth() and depth are supported:
    # src.depth() = CV_8U, depth = -1/CV_16S/CV_32F/CV_64F
    # src.depth() = CV_16U/CV_16S, depth = -1/CV_32F/CV_64F
    # src.depth() = CV_32F, depth = -1/CV_32F/CV_64F
    # src.depth() = CV_64F, depth = -1/CV_64F
    # when depth=-1, the output image will have the same depth as the source.
    dst = cv2.filter2D(img, -1, kernel)
    plt.subplot(121), plt.imshow(img), plt.title('Original'), plt.xticks([]), plt.yticks([])
    plt.subplot(122), plt.imshow(dst), plt.title('Averaging'), plt.xticks([]), plt.yticks([])
    plt.show()

执行结果

打印输出

[[0.04 0.04 0.04 0.04 0.04]
 [0.04 0.04 0.04 0.04 0.04]
 [0.04 0.04 0.04 0.04 0.04]
 [0.04 0.04 0.04 0.04 0.04]
 [0.04 0.04 0.04 0.04 0.04]]

均值滤波器

# 用卷积框覆盖区域所有像素的平均值来代替中心元素。
def average_filter():
    img = cv2.imread('opencv-logo.png')
    blur = cv2.blur(img, (5, 5))
    plt.subplot(121), plt.imshow(img), plt.title('Original'), plt.xticks([]), plt.yticks([])
    plt.subplot(122), plt.imshow(blur), plt.title('Blurred'), plt.xticks([]), plt.yticks([])
    plt.show()

高斯滤波器

# 高斯核(简单来说,方框不变,将原来每个方框的值是相等的,现在里面的值是符合高斯分布的,方框中心的值最大,其余方框根据
# 距离中心元素的距离递减,构成一个高斯小山包。原来的求平均数现在变成求加权平均数,权就是方框里的值)
# 高斯滤波器是求中心点邻近区域像素的高斯加权平均值。这种高斯滤波器只考虑像素之间的空间关系,而不会考虑像素值之间的关系(像素的相似度)。
# 所以这种方法不会考虑一个像素是否位于边界。因此边界也会模糊掉
def gaussian_blur():
    img = cv2.imread('opencv-logo.png')
    blur = cv2.GaussianBlur(img, (5, 5), 0, 0)
    plt.subplot(121), plt.imshow(img), plt.title('Original'), plt.xticks([]), plt.yticks([])
    plt.subplot(122), plt.imshow(blur), plt.title('Blurred'), plt.xticks([]), plt.yticks([])
    plt.show()

中值滤波器

# 用与卷积框对应像素的中值来替代中心像素的值。这个滤波器经常用来去除椒盐噪声。前面的滤波器都是用计算得到的一个新值来取代中
# 心像素的值,而中值滤波是用中心像素周围(也可以使他本身)的值来取代他。他能有效的去除噪声。卷积核的大小也应该是一个奇数。
def median_blur():
    img = cv2.imread('opencv-logo.png')
    blur = cv2.medianBlur(img, (5, 5))
    plt.subplot(121), plt.imshow(img), plt.title('Original'), plt.xticks([]), plt.yticks([])
    plt.subplot(122), plt.imshow(blur), plt.title('Blurred'), plt.xticks([]), plt.yticks([])
    plt.show()

双边滤波器

# 双边滤波在同时使用空间高斯权重和灰度值相似性高斯权重。空间高斯函数确保只有邻近区域的像素对中心点有影响,灰度值相似性高斯函数确保只有
# 与中心像素灰度值相近的才会被用来做模糊运算。所以这种方法会确保边界不会被模糊掉,因为边界处的灰度值变化比较大。
# 通常用于处理图片纹理,同时保留边界
def bilateral_blur():
    img = cv2.imread('opencv-logo.png')
    # cv2.bilateralFilter(src, d, sigmaColor, sigmaSpace)
    # d – Diameter of each pixel neighborhood that is used during filtering.
    # If it is non-positive, it is computed from sigmaSpace
    # 9 邻域直径,两个 75 分别是空间高斯函数标准差,灰度值相似性高斯函数标准差
    blur = cv2.bilateralFilter(img, 9, 75, 75)
    plt.subplot(121), plt.imshow(img), plt.title('Original'), plt.xticks([]), plt.yticks([])
    plt.subplot(122), plt.imshow(blur), plt.title('Blurred'), plt.xticks([]), plt.yticks([])
    plt.show()

形态学操作 腐蚀&膨胀&开运算&闭运算&形态学梯度&礼帽&黑帽

腐蚀和膨胀

def erode_dilate():
    img = cv2.imread('j.png', cv2.IMREAD_COLOR)
    kernel = np.ones((5, 5), np.uint8)
    erosion = cv2.erode(img, kernel, iterations=1) # 腐蚀
    dilation = cv2.dilate(img, kernel, iterations=1) # 膨胀
    titles = ['original image', 'eroded image', 'dilated image']
    images = [img, erosion, dilation]
    for i in range(3):
        plt.subplot(1, 3, i + 1), plt.imshow(images[i], 'gray'), plt.title(titles[i])
        plt.xticks([]), plt.yticks([])
    plt.show()

执行结果:

开运算【先腐蚀再膨胀】

# 先进行腐蚀再进行膨胀就叫做开运算,常用来去除噪声
def morphologyEx_open():
    img = cv2.imread('j with noise.png', cv2.IMREAD_COLOR)
    kernel = np.ones((5, 5), np.uint8)
    opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
    titles = ['original image', 'opening image']
    images = [img, opening]
    for i in range(2):
        plt.subplot(1, 2, i + 1), plt.imshow(images[i], 'gray'), plt.title(titles[i])
        plt.xticks([]), plt.yticks([])
    plt.show()

运行结果

闭运算【先膨胀再腐蚀】

# 先膨胀再腐蚀。它经常被用来填充前景物体中的小洞,或者前景物体上的小黑点。
def morphologyEx_close():
    img = cv2.imread('j with noise2.png', cv2.IMREAD_COLOR)
    kernel = np.ones((5, 5), np.uint8)
    closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
    titles = ['original image', 'closing image']
    images = [img, closing]
    for i in range(2):
        plt.subplot(1, 2, i + 1), plt.imshow(images[i], 'gray'), plt.title(titles[i]),
        plt.xticks([]), plt.yticks([])
    plt.show()

运行结果:

形态学梯度

# 一幅图像膨胀与腐蚀的差,看上去就像是前景物体的轮廓
def morphologyEx_gradient():
    img = cv2.imread('j.png', cv2.IMREAD_COLOR)
    kernel = np.ones((5, 5), np.uint8)
    gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel)
    titles = ['original image', 'gradient image']
    images = [img, gradient]
    for i in range(2):
        plt.subplot(1, 2, i + 1), plt.imshow(images[i], 'gray'), plt.title(titles[i]),
        plt.xticks([]), plt.yticks([])
    plt.show()

运行结果:

礼帽&黑帽

# 原始图像与进行开运算之后得到的图像的差。
# tophat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)
# 进行闭运算之后得到的图像与原始图像的差
# blackhat = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel)
def morphologyEx_tophat_blackhat():
    img = cv2.imread('j.png', cv2.IMREAD_COLOR)
    kernel = np.ones((5, 5), np.uint8)
    tophat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)  # 礼帽
    blackhat = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel)  # 黑帽
    titles = ['original image', 'tophat image', 'blackhat image']
    images = [img, tophat, blackhat]
    for i in range(3):
        plt.subplot(1, 3, i + 1), plt.imshow(images[i], 'gray'), plt.title(titles[i]),
        plt.xticks([]), plt.yticks([])
    plt.show()

运行结果:

形态学操作之间的关系

  • opening:

    dst = open(src,element) = dilate(erode(src,element),element)

  • closing:

    dst = close(src,element) = erode(dilate(src,element),element)

  • morphological gradient:

    dst = morph_grad(src,element) = dilate(src,element) - erode(src,element)

  • tophat:

    dst = tophat(src,element) = src - open(src,element)

  • blackhat:

    dst = blackhat(src,element) = close(src,element) - src

结构化元素

在前面的例子中我们使用 Numpy 构建了结构化元素,它是正方形的。但有时我们需要构建一个椭圆形/圆形的核。为了实现这种要求,提供了 OpenCV 函数 cv2.getStructuringElement()。你只需要告诉他你需要的核的形状 和大小。

# Rectangular Kernel
>>> cv2.getStructuringElement(cv2.MORPH_RECT,(5,5))
array(
[[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1]], dtype=uint8)

# Elliptical Kernel
>>> cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(5,5))
array([
[0, 0, 1, 0, 0],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[0, 0, 1, 0, 0]], dtype=uint8)

# Cross-shaped Kernel
>>> cv2.getStructuringElement(cv2.MORPH_CROSS,(5,5))
array(
[[0, 0, 1, 0, 0],
[0, 0, 1, 0, 0],
[1, 1, 1, 1, 1],
[0, 0, 1, 0, 0],
[0, 0, 1, 0, 0]], dtype=uint8)

图像梯度

梯度简单来说就是求导。 OpenCV 提供了三种不同的梯度滤波器,或者说高通滤波器:Sobel, Scharr 和 Laplacian。Sobel,Scharr 其实就是求一阶或二阶导数。Scharr 是对 Sobel(使用小的卷积核求解梯度角度时)的优化。Laplacian 是求二阶导数。

Sobel算子和Scharr算子

Sobel 算子是高斯平滑与微分操作的结合体,所以它的抗噪声能力很好。 你可以设定求导的方向(xorder 或 yorder)。还可以设定使用的卷积核的大 小(ksize)。如果 ksize=-1,会使用 3x3 的 Scharr 滤波器,它的效果要 比 3x3 的 Sobel 滤波器好(而且速度相同,所以在使用 3x3 滤波器时应该尽量使用 Scharr 滤波器)。3x3 的 Scharr 滤波器卷积核如下

Laplacian 算子

拉普拉斯算子可以使用二阶导数的形式定义,可假设其离散实现类似于二阶 Sobel 导数,事实上,OpenCV 在计算拉普拉斯算子时直接调用 Sobel 算子。计算公式如下:

拉普拉斯滤波器使用的卷积核:

def sobel_laplacian():
    img = cv2.imread('dave.png', cv2.IMREAD_GRAYSCALE)
    # cv2.CV_64F 输出图像的深度(数据类型),可以使用-1, 与原图像保持一致 np.uint8
    laplacian = cv2.Laplacian(img, cv2.CV_64F)
    # 参数 1,0 为只在 x 方向求一阶导数,最大可以求 2 阶导数。
    sobel_x = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=5)
    # 参数 0,1 为只在 y 方向求一阶导数,最大可以求 2 阶导数。
    sobel_y = 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(sobel_x, cmap='gray'), plt.title('Sobel X'), plt.xticks([]), plt.yticks([])
    plt.subplot(2, 2, 4), plt.imshow(sobel_y, cmap='gray'), plt.title('Sobel Y'), plt.xticks([]), plt.yticks([])
    plt.show()

运行结果:

在查看上面这个例子的注释时不知道你有没有注意到:当我们可以通过参数 -1 来设定输出图像的深度(数据类型)与原图像保持一致,但是我们在代码中使用的却是 cv2.CV_64F。这是为什么呢?想象一下一个从黑到白的边界 的导数是整数,而一个从白到黑的边界点导数却是负数。如果原图像的深度是 np.int8 时,所有的负值都会被截断变成 0,换句话说就是把边界丢失掉。 所以如果这两种边界你都想检测到,最好的办法就是将输出的数据类型设置的更高,比如 cv2.CV_16S,cv2.CV_64F 等。取绝对值然后再把它转回到 cv2.CV_8U。下面的示例演示了输出图片的深度不同造成的不同效果。

def depth_of_output():
    img = cv2.imread('boxs.png', cv2.IMREAD_GRAYSCALE)
    # Output dtype = cv2.CV_8U
    sobel_x8u = cv2.Sobel(img, cv2.CV_8U, 1, 0, ksize=5)
    # 也可以将参数设为-1
    # sobel_x8u = cv2.Sobel(img,-1,1,0,ksize=5)
    # Output dtype = cv2.CV_64F. Then take its absolute and convert to cv2.CV_8U
    sobel_x64f = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=5)
    abs_sobel64f = np.absolute(sobel_x64f)
    sobel_8u = np.uint8(abs_sobel64f)
    plt.subplot(1, 3, 1), plt.imshow(img, cmap='gray'), plt.title('Original'), plt.xticks([]), plt.yticks([])
    plt.subplot(1, 3, 2), plt.imshow(sobel_x8u, cmap='gray'), plt.title('Sobel CV_8U'), plt.xticks([]), plt.yticks([])
    plt.subplot(1, 3, 3), plt.imshow(sobel_8u, cmap='gray'), plt.title('Sobel abs(CV_64F)'), plt.xticks([]), plt.yticks([])
    plt.show()

运行结果:

Canny边缘检测

Canny 边缘检测是一种非常流行的边缘检测算法,是 John F.Canny 在 1986 年提出的。它是一个有很多步构成的算法。

1.噪声去除

由于边缘检测很容易受到噪声影响,所以第一步是使用 5x5 的高斯滤波器去除噪声

2.计算图像梯度

对平滑后的图像使用 Sobel 算子计算水平方向和竖直方向的一阶导数(图像梯度)(Gx 和 Gy)。根据得到的这两幅梯度图(Gx 和 Gy)找到边界的梯度和方向,公式如下

梯度的方向一般总是与边界垂直。梯度方向被归为四类:垂直,水平,和 两个对角线

3.非极大值抑制

在获得梯度的方向和大小之后,应该对整幅图像做一个扫描,去除那些非边界上的点。对每一个像素进行检查,看这个点的梯度是不是周围具有相同梯度方向的点中最大的。

现在你得到的是一个包含“窄边界”的二值图像

4.滞后阈值

现在要确定那些边界才是真正的边界。这时我们需要设置两个阈值: minVal 和 maxVal。

当图像的灰度梯度高于 maxVal 时被认为是真的边界, 那些低于 minVal 的边界会被抛弃

如果介于两者之间的话,就要看这个点是否与某个被确定为真正的边界点相连,如果是就认为它也是边界点,如果不是就抛弃。

A 高于阈值 maxVal 所以是真正的边界点,C 虽然低于 maxVal 但高于 minVal 并且与 A 相连,所以也被认为是真正的边界点。而 B 就会被抛弃,因为他不仅低于 maxVal 而且不与真正的边界点相连。所以选择合适的 maxVal 和 minVal 对于能否得到好的结果非常重要。 在这一步,一些小的噪声点也会被除去,因为我们假设边界都是一些长的线段。

def canny():
    img = cv2.imread('tree.jpg', cv2.IMREAD_GRAYSCALE)
    edges = cv2.Canny(img, 80, 100)
    plt.subplot(121), plt.imshow(img, cmap='gray'), plt.title('Original Image'), plt.xticks([]), plt.yticks([])
    plt.subplot(122), plt.imshow(edges, cmap='gray'), plt.title('Edge Image'), plt.xticks([]), plt.yticks([])
    plt.show()

运行结果:


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM