zoukankan      html  css  js  c++  java
  • 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()
    

    运行结果:

  • 相关阅读:
    用html自己开发自己的串口TCP通讯调试软件
    推荐模型PNN: 原理介绍与TensorFlow2.0实现
    推荐模型NeuralCF:原理介绍与TensorFlow2.0实现
    推荐模型DeepCrossing: 原理介绍与TensorFlow2.0实现
    推荐模型AutoRec:原理介绍与TensorFlow2.0实现
    ffmpeg命令的简单使用
    X264的交叉编译
    FDK_AAC交叉编译
    编译lame静态库
    iOS安全清单
  • 原文地址:https://www.cnblogs.com/ericling/p/15508044.html
Copyright © 2011-2022 走看看