Opencv图像处理(全)

文章目录


博主精品专栏导航




00、环境配置

conda create --name opencv_learning -y
conda activate opencv_learning

conda install python==3.9
pip install opencv-python==4.5.1.48 -i http://mirrors.aliyun.com/pypi/simple --trusted-host mirrors.aliyun.com
pip install opencv-contrib-python==4.5.1.48 -i http://mirrors.aliyun.com/pypi/simple --trusted-host mirrors.aliyun.com

11、项目实战

(一)银行卡号识别 —— sort_contours()、resize()

银行卡号识别步骤

  • (1)提取模板图像中每个数字(0~9)
    • 11、读取模板图像、灰度图、二值图
    • 22、轮廓检测、绘制轮廓、轮廓排序
    • 33、提取模板图像的所有轮廓(每一个数字)

在这里插入图片描述

  • (2)提取银行卡的所有轮廓
    • 11、读取卡图像、灰度图、顶帽运算、sobel算子、闭运算、二值化、二次膨胀+腐蚀
    • 22、轮廓检测、绘制轮廓

在这里插入图片描述

  • (3)提取银行卡《四个数字一组》轮廓,然后每个轮廓与模板的每一个数字进行匹配,得到最大匹配结果,并在原图上绘制结果
    • 11、在所有轮廓中,识别出《四个数字一组》的轮廓(共有四个),并进行阈值化、轮廓检测和轮廓排序
    • 22、在《四个数字一组》中,提取每个数字的轮廓以及坐标,并进行模板匹配得到最大匹配结果
    • 33、在原图像上,用矩形画出《四个数字一组》,并在原图上显示所有的匹配结果

在这里插入图片描述

import cv2                          # opencv    读取图像默认为BGR
import matplotlib.pyplot as plt     # matplotlib显示图像默认为RGB
import numpy as np


def sort_contours(cnt_s, method="left-to-right"):
    reverse = False
    ii_myutils = 0
    if method == "right-to-left" or method == "bottom-to-top":
        reverse = True
    if method == "top-to-bottom" or method == "bottom-to-top":
        ii_myutils = 1
    bounding_boxes = [cv2.boundingRect(cc_myutils) for cc_myutils in cnt_s]  # 用一个最小的矩形,把找到的形状包起来x,y,h,w
    (cnt_s, bounding_boxes) = zip(*sorted(zip(cnt_s, bounding_boxes), key=lambda b: b[1][ii_myutils], reverse=reverse))
    return cnt_s, bounding_boxes


def resize(image, width=None, height=None, inter=cv2.INTER_AREA):
    dim_myutils = None
    (h_myutils, w_myutils) = image.shape[:2]
    if width is None and height is None:
        return image
    if width is None:
        r_myutils = height / float(h_myutils)
        dim_myutils = (int(w_myutils * r_myutils), height)
    else:
        r_myutils = width / float(w_myutils)
        dim_myutils = (width, int(h_myutils * r_myutils))
    resized = cv2.resize(image, dim_myutils, interpolation=inter)
    return resized


def extract_template(image):
    """(1)提取模板图像中每个数字(0~9)"""
    ref_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)  # 转换成灰度图
    ref_BINARY = cv2.threshold(ref_gray, 10, 255, cv2.THRESH_BINARY_INV)[1]  # 转换成二值图像
    refCnts, hierarchy = cv2.findContours(ref_BINARY.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    img_Contours = image.copy()
    cv2.drawContours(img_Contours, refCnts, -1, (0, 0, 255), 3)

    # 绘制图像
    plt.subplot(221), plt.imshow(image, 'gray'), plt.title('(0)ref')
    plt.subplot(222), plt.imshow(ref_gray, 'gray'), plt.title('(1)ref_gray')
    plt.subplot(223), plt.imshow(ref_BINARY, 'gray'), plt.title('(2)ref_BINARY')
    plt.subplot(224), plt.imshow(img_Contours, 'gray'), plt.title('(3)img_Contours')
    plt.show()

    # 排序所有轮廓(从左到右,从上到下)
    refCnts = sort_contours(refCnts, method="left-to-right")[0]

    # 提取所有轮廓
    digits = {}  # 保存每个模板的数字
    for (index, region) in enumerate(refCnts):
        (x, y, width, height) = cv2.boundingRect(region)  # 得到轮廓(数字)的外接矩形的左上角的(x, y)坐标、宽度和长度
        roi = ref_BINARY[y:y + height, x:x + width]  # 获得外接矩形的坐标
        roi = cv2.resize(roi, (57, 88))  # 将感兴趣区域的图像(数字)resize相同的大小
        digits[index] = roi

    """#################################################################################################################
    # 函数功能:用于在二值图像中轮廓检测
    # 函数说明:contours, hierarchy = cv2.findContours(image, mode, method)
    # 参数说明:
    #         image:        输入的二值图像,通常为灰度图像或者是经过阈值处理后的图像。
    #         mode:         轮廓检索模式,指定轮廓的层次结构。
    #                             cv2.RETR_EXTERNAL:    仅检测外部轮廓。
    #                             cv2.RETR_LIST:        检测所有轮廓,不建立轮廓层次。
    #                             cv2.RETR_CCOMP:       检测所有轮廓,将轮廓分为两级层次结构。
    #                             cv2.RETR_TREE:        检测所有轮廓,建立完整的轮廓层次结构。
    #         method:       轮廓逼近方法,指定轮廓的近似方式。
    #                             cv2.CHAIN_APPROX_NONE:        存储所有的轮廓点,相邻的点之间不进行抽稀。
    #                             cv2.CHAIN_APPROX_SIMPLE:      仅存储轮廓的端点,相邻的点之间进行抽稀,以减少存储容量。
    #                             cv2.CHAIN_APPROX_TC89_L1:     使用 Teh-Chin 链逼近算法中的 L1 范数,以减少存储容量。
    #                             cv2.CHAIN_APPROX_TC89_KCOS:   使用 Teh-Chin 链逼近算法中的 KCOS 范数,以减少存储容量。
    # 输出参数:
    #         contours:     检测到的轮廓列表,每个轮廓是一个点的列表。
    #         hierarchy:    轮廓的层次结构,一般用不到,可以忽略。
    #
    # 备注1:轮廓就是将连续的点(连着边界)连在一起的曲线,具有相同的颜色或者灰度。轮廓在形状分析和物体的检测和识别中很有用。
    # 备注2:函数在opencv2只返回两个值:contours, hierarchy。
    # 备注3:函数在opencv3会返回三个值:image, countours, hierarchy
    #################################################################################################################"""

    """#################################################################################################################
    # 函数功能:用于在图像上绘制轮廓。
    # 函数说明:cv2.drawContours(image, contours, contourIdx, color, thickness=1, lineType=cv2.LINE_8, hierarchy=None, maxLevel=None, offset=None)
    # 参数说明:
    #         image:        要绘制轮廓的图像,可以是单通道或多通道的图像。
    #         contours:     要绘制的轮廓列表,通常通过 cv2.findContours 函数获取。
    #         contourIdx:   要绘制的轮廓索引,如果为负数则绘制所有轮廓。
    #         color:        轮廓的颜色,通常是一个元组,如 (0, 255, 0) 表示绿色。
    #         thickness:    轮廓线的粗细,如果为负数或 cv2.FILLED 则填充轮廓区域。
    #         lineType:     线的类型
    #                             cv2.LINE_4:4连接线。
    #                             cv2.LINE_8:8连接线。
    #                             cv2.LINE_AA:抗锯齿线。
    #         hierarchy:    轮廓的层级结构,一般由 cv2.findContours 函数返回,可选参数。
    #         maxLevel:     要绘制的轮廓的最大层级,一般由 cv2.findContours 函数返回,可选参数。
    #         offset:       轮廓偏移量,一般不需要设置,可选参数。
    #
    # 备注:使用copy()复制图像后绘制, 否则原图会同时改变。
    #################################################################################################################"""
    return digits


def extract_card(image):
    """(2)提取银行卡的所有轮廓"""
    rect_Kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 3))
    square_Kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
    """#################################################################################################################
    # 函数功能:用于创建指定形状和大小的结构元素(structuring element) ———— 常用于图像形态学处理中的腐蚀、膨胀、开运算、闭运算等操作。
    # 函数说明:element = cv2.getStructuringElement(shape, ksize)
    # 参数说明:		
    #         shape:    结构元素的形状:
    #                         cv2.MORPH_RECT:   矩形结构元素。
    #                         cv2.MORPH_CROSS:  十字形结构元素。
    #                         cv2.MORPH_ELLIPSE:椭圆形结构元素。
    #         ksize:    结构元素的卷积核大小,通常是一个元组 (width, height)。如:(3, 3)表示3*3的卷积核
    #################################################################################################################"""

    image_tophat = cv2.morphologyEx(image_gray, cv2.MORPH_TOPHAT, rect_Kernel)  # 礼帽运算 ———— 用于突出亮区域
    """#################################################################################################################
    # 函数功能:用于对图像进行形态学操作,如腐蚀、膨胀、开运算、闭运算等。
    # 函数说明:dst = cv2.morphologyEx(src, op, kernel)
    # 参数说明:
    #         src:      输入图像,通常是灰度图像或二值图像。
    #         op:       形态学操作类型,可以是以下几种之一:
    #                             cv2.MORPH_ERODE:      腐蚀操作。
    #                             cv2.MORPH_DILATE:     膨胀操作。
    #                             cv2.MORPH_OPEN:       开运算(先腐蚀后膨胀)。            开运算可以用来消除小黑点。
    #                             cv2.MORPH_CLOSE:      闭运算(先膨胀后腐蚀)。            闭运算可以用来突出边缘特征。
    #                             cv2.MORPH_GRADIENT:   形态学梯度(膨胀图像减去腐蚀图像)。  突出团块(blob)的边缘,保留物体的边缘轮廓。
    #                             cv2.MORPH_TOPHAT:     顶帽运算(原始图像减去开运算图像)。  突出比原轮廓亮的部分。
    #                             cv2.MORPH_BLACKHAT:   黑帽运算(闭运算图像减去原始图像)。  突出比原轮廓暗的部分。
    #         kernel:   结构元素,用于指定形态学操作的形状和大小。
    #################################################################################################################"""

    """#################################################################################################################
    # 函数功能:Sobel算子是一种常用的边缘检测算子 ———— 对噪声具有平滑作用,提供较为精确的边缘方向信息,但是边缘定位精度不够高。
    # 操作步骤:    
    #             备注1:分别计算x和y,再求和(效果好),若同时对x和y进行求导,会导致部分信息丢失。(不建议)
    #             备注2:对于二维图像,Sobel算子在x,y两个方向求导,故有不同的两个卷积核(Gx, Gy),且Gx的转置等于Gy。分别反映每个像素在水平和在垂直方向上的亮度变换情况.
    #             边缘即灰度像素值快速变化的地方。如:黑到白的边界
    # 
    # 函数说明:dst = cv2.Sobel(src, ddepth, dx, dy, ksize=3, scale=1, delta=0, borderType=cv2.BORDER_DEFAULT)
    # 参数说明:	
    #             src:        输入图像,通常是单通道的灰度图像。通常情况下,输入图像的深度是 cv2.CV_8U(8 位无符号整型)
    #             ddepth:     输出图像的深度,通常为-1表示与输入图像相同。支持以下类型:cv2.CV_16S、cv2.CV_32F、cv2.CV_64F
    #             dx:          x 方向上的导数阶数,0 表示不对 x 方向求导数。
    #             dy:          y 方向上的导数阶数,0 表示不对 y 方向求导数。
    #             ksize:       滤波器的内核大小。必须是 -1、1、3、5 或 7,表示内核的大小是 1x1、3x3、5x5 或 7x7。 
    #                                       -1表示使用 Scharr 滤波器。Scharr 滤波器是 Sobel 滤波器的改进版本,具有更好的性能。
    #             scale:       (可选)表示计算结果的缩放因子。
    #             delta:       (可选)表示输出图像的偏移值。
    #             borderType:  (可选)表示边界处理方式。
    # 返回参数:
    #             滤波后图像
    #################################################################################################################"""

    """#################################################################################################################
    # 函数功能:用于将输入数组的每个元素乘以缩放因子 alpha,然后加上偏移量 beta,并取结果的绝对值后转换为无符号8位整数类型。
    # 函数说明:dst = cv2.convertScaleAbs(src, alpha=1, beta=0)
    # 参数说明:
    #         src:      输入数组,可以是任意类型的数组。
    #         alpha:    缩放因子
    #         beta:     偏移量
    # 返回参数:
    #         dst       转换后数组(与输入数组相同大小和类型的数组,数据类型为无符号8位整数。)
    #################################################################################################################"""
    image_gradx = cv2.Sobel(image_tophat, ddepth=cv2.CV_32F, dx=1, dy=0, ksize=-1)
    image_gradx = np.absolute(image_gradx)  # 求绝对值
    (minVal, maxVal) = (np.min(image_gradx), np.max(image_gradx))  # 计算最大最小边界差值
    image_gradx = (255 * ((image_gradx - minVal) / (maxVal - minVal)))  # 归一化处理(0~1)
    image_gradx = image_gradx.astype("uint8")  # 数据类型转换

    """
    # 分别计算x和y,再求和(效果好)
    sobel_Gx1 = cv2.Sobel(image_tophat, ddepth=cv2.CV_32F, dx=1, dy=0, ksize=3)
    sobel_Gx_Abs1 = cv2.convertScaleAbs(sobel_Gx1)  # (1)左边减右边(2)白到黑是正数,黑到白就是负数,且所有的负数会被截断成0,所以要取绝对值。
    sobel_Gy1 = cv2.Sobel(image_tophat, cv2.CV_64F, 0, 1, ksize=3)
    sobel_Gy_Abs1 = cv2.convertScaleAbs(sobel_Gy1)
    sobel_Gx_Gy_Abs1 = cv2.addWeighted(sobel_Gx_Abs1, 0.5, sobel_Gy_Abs1, 0.5, 0)   # 权重值x + 权重值y +偏置b
    """

    """#################################################################################################################
    # 函数说明:全局阈值分割 ———— 根据阈值将图像的像素分为两个类别:高于阈值的像素和低于阈值的像素。
    # 函数说明:ret, dst = cv2.threshold(src, thresh, max_val, type)
    # 参数说明:    	
    #         src:   	    输入灰度图像
    #         thresh: 	    阈值
    #         max_val:    	阈值上限。通常为255(8-bit)。
    #         type:   	    二值化操作的类型,包含以下5种类型:
    #                               (1) cv2.THRESH_BINARY             超过阈值部分取max_val(最大值),否则取0
    #                               (2) cv2.THRESH_BINARY_INV         THRESH_BINARY的反转
    #                               (3) cv2.THRESH_TRUNC              大于阈值部分设为阈值,否则不变
    #                               (4) cv2.THRESH_TOZERO             大于阈值部分不改变,否则设为0
    #                               (5) cv2.THRESH_TOZERO_INV         THRESH_TOZERO的反转
    # 返回参数:     
    #         ret           浮点数,表示最终使用的阈值。
    #         dst   	    经过阈值分割操作后的二值图像
    #################################################################################################################"""
    # 闭运算(先膨胀,再腐蚀)———— 将银行卡分成四个部分,每个部分的四个数字连在一起
    image_CLOSE = cv2.morphologyEx(image_gradx, cv2.MORPH_CLOSE, square_Kernel)
    image_thresh = cv2.threshold(image_CLOSE, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]  # 阈值分割
    # (二次)闭运算 ———— 将四个连在一起的数字进行填充形成一个整体。
    image_2_dilate = cv2.dilate(image_thresh, square_Kernel, iterations=2)  # 膨胀(迭代次数2次)
    image_1_erode = cv2.erode(image_2_dilate, square_Kernel, iterations=1)  # 腐蚀(迭代次数1次)
    image_2_CLOSE = image_1_erode
    threshCnts, hierarchy = cv2.findContours(image_2_CLOSE.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)  # 轮廓检测
    image_Contours = image_resize.copy()
    cv2.drawContours(image_Contours, threshCnts, -1, (0, 0, 255), 3)  # 绘制轮廓
    # 绘制图像
    plt.subplot(241), plt.imshow(image, 'gray'), plt.title('(0)image_card')
    plt.subplot(242), plt.imshow(image_gray, 'gray'), plt.title('(1)image_gray')
    plt.subplot(243), plt.imshow(image_tophat, 'gray'), plt.title('(2)image_tophat')
    plt.subplot(244), plt.imshow(image_gradx, 'gray'), plt.title('(3)image_gradx')
    plt.subplot(245), plt.imshow(image_CLOSE, 'gray'), plt.title('(4)image_CLOSE')
    plt.subplot(246), plt.imshow(image_thresh, 'gray'), plt.title('(5)image_thresh')
    plt.subplot(247), plt.imshow(image_2_CLOSE, 'gray'), plt.title('(6)image_2_CLOSE')
    plt.subplot(248), plt.imshow(image_Contours, 'gray'), plt.title('(7)image_Contours')
    plt.show()

    return threshCnts


def extract_digits(image_gray, threshCnts, digits):
    """3311、识别出四个数字一组的所有轮廓(理论上是四个)"""
    locs = []  # 保存四个数字一组的轮廓坐标
    # 遍历轮廓
    for (index, region) in enumerate(threshCnts):
        (x, y, wight, height) = cv2.boundingRect(region)  # 计算矩形
        ar = wight / float(height)  # (四个数字一组)的长宽比
        # 匹配(四个数字为一组)轮廓的大小 ———— 根据实际图像调整
        if 2.0 < ar < 4.0:
            if (35 < wight < 60) and (10 < height < 20):
                locs.append((x, y, wight, height))
    locs = sorted(locs, key=lambda x: x[0])  # 排序(从左到右)

    """3322、在四个数字一组中,提取每个数字的轮廓坐标并进行模板匹配"""
    output_all_group = []  # 保存(所有组)匹配结果
    for (ii, (gX, gY, gW, gH)) in enumerate(locs):
        """遍历银行卡的每个组(理论上是四组)"""
        image_group = image_gray[gY - 5:gY + gH + 5, gX - 5:gX + gW + 5]  # 坐标索引每个组图像,并将每个轮廓的结果放大一些,避免信息丢失
        image_group_threshold = cv2.threshold(image_group, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]  # 二值化处理
        digitCnts, hierarchy = cv2.findContours(image_group_threshold, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)  # 获取轮廓
        digitCnts_sort = sort_contours(digitCnts, method="left-to-right")[0]  # 排序

        """遍历每个组中的每个数字(理论上每个组是四个数字)"""
        output_group = []  # 保存(每个组)匹配结果
        for jj in digitCnts_sort:
            (x, y, wight, height) = cv2.boundingRect(jj)  # 获取数字的轮廓
            image_digit = image_group[y:y + height, x:x + wight]  # 获取数字的坐标
            image_digit = cv2.resize(image_digit, (57, 88))  # 图像缩放(保持与模板图像中的数字图像一致)
            cv2.imshow("Image", image_digit)
            cv2.waitKey(200)  # 延迟200ms

            """遍历模板图像的每个数字,并计算(轮廓数字)和(模板数字)匹配分数(理论上模板的十个数字))"""
            scores = []  # 计算匹配分数:(轮廓中的数字)和(模板中的数字)
            for (kk, digitROI) in digits.items():
                result = cv2.matchTemplate(image_digit, digitROI, cv2.TM_CCOEFF)
                (_, max_score, _, _) = cv2.minMaxLoc(result)  # max_score表示最大值
                scores.append(max_score)
            output_group.append(str(np.argmax(scores)))  # 将最大匹配分数对应的数字保存下来

            """#########################################################################################################
            # 函数功能:模板匹配 ———— 在输入图像上滑动目标模板,并计算目标模板与图像的匹配程度来实现目标检测。
            # 函数说明:cv2.matchTemplate(image, template, method)
            # 参数说明:
            #         image:      输入图像,需要在其中搜索目标模板。
            #         templ:      目标模板,需要在输入图像中匹配的部分。
            #         method:     匹配方法
            #                             cv2.TM_SQDIFF:         计算平方差。         计算结果越接近0,越相关
            #                             cv2.TM_CCORR:          计算相关性。          计算结果越大,越相关
            #                             cv2.TM_CCOEFF:         计算相关系数。         计算结果越大,越相关
            #                             cv2.TM_SQDIFF_NORMED:  计算(归一化)平方差。   计算结果越接近0,越相关
            #                             cv2.TM_CCORR_NORMED:   计算(归一化)相关性。   计算结果越接近1,越相关
            #                             cv2.TM_CCOEFF_NORMED:  计算(归一化)相关系数。  计算结果越接近1,越相关
            #                      备注:归一化效果更好
            #########################################################################################################"""

            """#########################################################################################################
            # 函数功能:用于找到数组中的最小值和最大值,以及它们的位置。
            # 函数说明:min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(ret)
            # 输入参数:
            #         ret       cv2.matchTemplate函数返回的矩阵,即匹配结果图像。
            # 返回参数:
            #         min_val:匹配结果图像中的最小值。
            #         max_val:匹配结果图像中的最大值。
            #         min_loc:最小值对应的位置(x,y)。
            #         max_loc:最大值对应的位置(x,y)。
            #
            # 备注:如果模板匹配方法是平方差或者归一化平方差,则使用min_loc; 否则使用max_loc
            #########################################################################################################"""

        # 在绘制矩形 ———— 在原图上,用矩形将" 四个数字一组 "画出来(理论上共有四个矩形,对应四个组)
        cv2.rectangle(image_resize, (gX - 5, gY - 5), (gX + gW + 5, gY + gH + 5), (0, 0, 255), 1)
        cv2.putText(image_resize, "".join(output_group), (gX, gY - 15), cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 255), 2)
        output_all_group.extend(output_group)
        """#########################################################################################################
        # 函数功能:用于在图像上绘制文本。———— 支持在图像的指定位置添加文字,并设置文字的字体、大小、颜色等属性。
        # 函数说明:cv2.putText(img, text, org, fontFace, fontScale, color, thickness=1, lineType=cv2.LINE_AA, bottomLeftOrigin=False)
        # 参数说明:
        #         img:      要绘制文本的图像。
        #         text:     要绘制的文本内容。
        #         org:      文本的起始坐标,即左下角的坐标。
        #         fontFace: 字体类型,可以是以下几种之一:
        #                     cv2.FONT_HERSHEY_SIMPLEX:正常尺寸的简单字体。
        #                     cv2.FONT_HERSHEY_PLAIN:小号字体。
        #                     cv2.FONT_HERSHEY_DUPLEX:正常尺寸的复杂字体。
        #                     cv2.FONT_HERSHEY_COMPLEX:正常尺寸的复杂字体。
        #                     cv2.FONT_HERSHEY_TRIPLEX:双倍大小的复杂字体。
        #                     cv2.FONT_HERSHEY_COMPLEX_SMALL:小号复杂字体。
        #                     cv2.FONT_HERSHEY_SCRIPT_SIMPLEX:手写风格的简单字体。
        #                     cv2.FONT_HERSHEY_SCRIPT_COMPLEX:手写风格的复杂字体。
        #         fontScale:字体大小缩放比例。
        #         color:    文本颜色,通常是一个元组,如 (255, 0, 0) 表示蓝色。
        #         thickness:文本轮廓线的粗细,默认为 1。
        #         lineType: 文本的线型,可以是以下几种之一:
        #                     cv2.LINE_AA:抗锯齿线。
        #                     cv2.LINE_4:4连接线。
        #                     cv2.LINE_8:8连接线。
        #         bottomLeftOrigin:可选参数,表示坐标原点是否在左下角,默认为 False,即左上角。
        # 返回参数:
        #         无,直接在输入图像上绘制了文本。
        #########################################################################################################"""
    cv2.imshow("Image", image_resize)
    cv2.waitKey(0)


if __name__ == "__main__":
    """11、提取模板图像中每个数字模板(0~9)"""
    image_template = cv2.imread(r'template.png')
    digits = extract_template(image_template)

    """22、提取信用卡的所有轮廓(需要根据实际情况调整)"""
    image_card = cv2.imread(r'card.png')
    image_resize = resize(image_card, width=300)
    image_gray = cv2.cvtColor(image_resize, cv2.COLOR_BGR2GRAY)
    threshCnts = extract_card(image_card)

    """33、提取银行卡" 四个数字一组 "轮廓,然后每个轮廓与模板的每一个数字进行匹配,得到最大匹配结果,并在原图上绘制结果"""
    extract_digits(image_gray, threshCnts, digits)

深究 Pycharm shadows name ‘xxxx’ from outer scope 警告

(二)文档扫描OCR识别 —— cv2.getPerspectiveTransform()、cv2.warpPerspective()、np.argmin()、np.argmax()、np.diff()

计算轮廓的长度:cv2.arcLength(curve, closed)
找出轮廓的多边形拟合曲线:approxPolyDP(contourMat, 10, true)
求最小值对应的索引:np.argmin()
求最大值对应的索引:np.argmax()
求(同一行)列与列之间的差值:np.diff()
在这里插入图片描述

import numpy as np
import cv2                          # opencv    读取图像默认为BGR
import matplotlib.pyplot as plt     # matplotlib显示图像默认为RGB
"""######################################################################
# 计算齐次变换矩阵:cv2.getPerspectiveTransform(rect, dst)
# 输入参数		rect输入图像的四个点(四个角)
# 				dst输出图像的四个点(方方正正的图像对应的四个角)
######################################################################
# 仿射变换:cv2.warpPerspective(src, M, dsize, dst=None, flags=None, borderMode=None, borderValue=None)
# 透视变换:cv2.warpAffine(src, M, dsize, dst=None, flags=None, borderMode=None, borderValue=None)
# 				src:输入图像     dst:输出图像
# 				M:2×3的变换矩阵
# 				dsize:变换后输出图像尺寸
# 				flag:插值方法
# 				borderMode:边界像素外扩方式
# 				borderValue:边界像素插值,默认用0填充
#
# (Affine Transformation)可实现旋转,平移,缩放,变换后的平行线依旧平行。
# (Perspective Transformation)即以不同视角的同一物体,在像素坐标系中的变换,可保持直线不变形,但是平行线可能不再平行。
#
# 备注:cv2.warpAffine需要与cv2.getPerspectiveTransform搭配使用。
######################################################################"""


def order_points(pts):
	rect = np.zeros((4, 2), dtype="float32")			# 一共4个坐标点
	# 按顺序找到对应坐标0123分别是 左上,右上,右下,左下
	# 计算左上,右下
	s = pts.sum(axis=1)
	rect[0] = pts[np.argmin(s)]			# np.argmin()	求最小值对应的索引
	rect[2] = pts[np.argmax(s)]			# np.argmax()	求最大值对应的索引
	# 计算右上和左下
	diff = np.diff(pts, axis=1)			# np.diff 	求(同一行)列与列之间的差值
	rect[1] = pts[np.argmin(diff)]
	rect[3] = pts[np.argmax(diff)]
	return rect


def four_point_transform(image, pts):
	rect = order_points(pts)		# 获取输入坐标点
	(tl, tr, br, bl) = rect			# 获取四边形的四个点,每个点有两个值,对应(x, y)坐标
	# 计算输入的w和h值
	widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))
	widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))
	maxWidth = max(int(widthA), int(widthB))			# 取四边形上下两边中,最大的宽度

	heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))
	heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))
	maxHeight = max(int(heightA), int(heightB))			# 取四边形左右两边中,最大的高度

	# 变换后对应坐标位置
	dst = np.array([[0, 0], [maxWidth - 1, 0], [maxWidth - 1, maxHeight - 1], [0, maxHeight - 1]], dtype="float32")
	"""###############################################################################
	# 计算齐次变换矩阵:cv2.getPerspectiveTransform(rect, dst)
	###############################################################################"""
	M = cv2.getPerspectiveTransform(rect, dst)
	
	"""###############################################################################
	# 透视变换(将输入矩形乘以(齐次变换矩阵),得到输出矩阵)
	###############################################################################"""
	warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))
	return warped


def resize(image, width=None, height=None, inter=cv2.INTER_AREA):
	dim = None
	(h, w) = image.shape[:2]
	if width is None and height is None:
		return image
	if width is None:
		r = height / float(h)
		dim = (int(w * r), height)
	else:
		r = width / float(w)
		dim = (width, int(h * r))
	resized = cv2.resize(image, dim, interpolation=inter)
	return resized


##############################################
image = cv2.imread(r'images\receipt.jpg')
ratio = image.shape[0] / 500.0						# resize之后坐标也会相同变化,故记录图像的比率
orig = image.copy()
image = resize(orig, height=500)
##############################################
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)		# 转换为灰度图
gray = cv2.GaussianBlur(gray, (5, 5), 0)			# 高斯滤波操作
edged = cv2.Canny(gray, 75, 200)					# Canny算法(边缘检测)
##############################################
print("STEP 1: 边缘检测")
cv2.imshow("Image", image)
cv2.imshow("Edged", edged)
cv2.waitKey(0)
cv2.destroyAllWindows()
##############################################
# 轮廓检测
cnts, hierarchy = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)		# 轮廓检测
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)[:5]			# 选定所有轮廓中前五个轮廓,并进行排序
for c in cnts:
	peri = cv2.arcLength(c, True)									# 计算轮廓近似
	approx = cv2.approxPolyDP(c, 0.02 * peri, True)					# 找出轮廓的多边形拟合曲线
	if len(approx) == 4:				# 如果当前轮廓是四个点(矩形),表示当前轮廓是所需求目标
		screenCnt = approx
		break
##############################################
print("STEP 2: 获取轮廓")
cv2.drawContours(image, [screenCnt], -1, (0, 255, 0), 2)			# 在原图上画出检测得到的轮廓
cv2.imshow("Outline", image)
cv2.waitKey(0)
cv2.destroyAllWindows()
##############################################
# 透视变换
warped = four_point_transform(orig, screenCnt.reshape(4, 2) * ratio)		# 得到的轮廓要乘以图像的缩放尺寸
warped = cv2.cvtColor(warped, cv2.COLOR_BGR2GRAY)							# 转换为灰度图
ref = cv2.threshold(warped, 100, 255, cv2.THRESH_BINARY)[1]					# 二值化处理
ref = resize(ref, height=500)
##############################################
print("STEP 3: 齐次变换")
cv2.imshow("Scanned", ref)
cv2.waitKey(0)
cv2.destroyAllWindows()

##############################################
# 轮廓点绘制的颜色通道是BGR;  但是Matplotlib是RGB;  故在绘图时,(0, 0, 255)会由BGR转换为RGB(红 - 蓝)
orig = cv2.cvtColor(orig, cv2.COLOR_BGR2RGB)			# BGR转换为RGB格式
edged = cv2.cvtColor(edged, cv2.COLOR_BGR2RGB)
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
ref = cv2.cvtColor(ref, cv2.COLOR_BGR2RGB)

plt.subplot(2, 2, 1),    plt.imshow(orig),      plt.title('orig')
plt.subplot(2, 2, 2),    plt.imshow(edged),     plt.title('edged')
plt.subplot(2, 2, 3),    plt.imshow(image),     plt.title('contour')
plt.subplot(2, 2, 4),    plt.imshow(ref),       plt.title('rectangle')
plt.show()

"""######################################################################
# 计算轮廓的长度:retval = cv2.arcLength(curve, closed)
# 输入参数:      curve              轮廓(曲线)。
#                closed             若为true,表示轮廓是封闭的;若为false,则表示打开的。(布尔类型)
# 输出参数:      retval             轮廓的长度(周长)。
######################################################################
# 找出轮廓的多边形拟合曲线:approxCurve = approxPolyDP(contourMat, 10, true)
# 输入参数:     contourMat:              轮廓点矩阵(集合)
#               epsilon:                 (double类型)指定的精度, 即原始曲线与近似曲线之间的最大距离。
#               closed:                  (bool类型)若为true, 则说明近似曲线是闭合的; 反之, 若为false, 则断开。
# 输出参数:     approxCurve:             轮廓点矩阵(集合);当前点集是能最小包容指定点集的。画出来即是一个多边形;
######################################################################"""

(三)全景拼接 —— detectAndDescribe()、matchKeypoints()、cv2.findHomography()、cv2.warpPerspective()、drawMatches()

函数功能:利用sift算法,实现全景拼接算法,将给定的两幅图片拼接为一幅.
   11:从输入的两张图片里检测关键点、提取(sift)局部不变特征。
   22:匹配的两幅图像之间的特征(Lowe’s算法:比较最近邻距离与次近邻距离)
   33:使用RANSAC算法(随机抽样一致算法),利用匹配特征向量估计单映射变换矩阵(homography:单应性)。
  44:利用33得到的单映矩阵应用透视变换。
在这里插入图片描述在这里插入图片描述

import cv2
import numpy as np
"""#########################################################
# 预定义框架说明
# 定义一个Stitcher类:stitch()、detectAndDescribe()、matchKeypoints()、drawMatches()
#           stitch()                拼接函数
#           detectAndDescribe()     检测图像的SIFT关键特征点,并计算特征描述子
#           matchKeypoints()        匹配两张图片的所有特征点
#           cv2.findHomography()    计算单映射变换矩阵
#           cv2.warpPerspective()   透视变换(作用:缝合图像)
#           drawMatches()           建立直线关键点的匹配可视化
#
# 备注:cv2.warpPerspective()需要与cv2.findHomography()搭配使用。
#########################################################"""


class Stitcher:
    ##################################################################################
    def stitch(self, images, ratio=0.75, reprojThresh=4.0, showMatches=False):
        (imageB, imageA) = images                               # 获取输入图片
        (kpsA, featuresA) = self.detectAndDescribe(imageA)      # 检测A、B图片的SIFT关键特征点,并计算特征描述子
        (kpsB, featuresB) = self.detectAndDescribe(imageB)
        M = self.matchKeypoints(kpsA, kpsB, featuresA, featuresB, ratio, reprojThresh)    # 匹配两张图片的所有特征点,返回匹配结果。

        if M is None:       # 如果返回结果为空,没有匹配成功的特征点,退出算法
            return None

        # 否则,提取匹配结果 #
        (matches, H, status) = M     # H是3x3视角变换矩阵
        result = cv2.warpPerspective(imageA, H, (imageA.shape[1] + imageB.shape[1], imageA.shape[0]))   # 将图片A进行视角变换,result是变换后图片
        result[0:imageB.shape[0], 0:imageB.shape[1]] = imageB    # 将图片B传入result图片最左端

        if showMatches:     # 检测是否需要显示图片匹配
            vis = self.drawMatches(imageA, imageB, kpsA, kpsB, matches, status)     # 生成匹配图片
            return (result, vis)

        return result

    ##################################################################################
    def detectAndDescribe(self, image):
        # gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)            # 将彩色图片转换成灰度图
        descriptor = cv2.xfeatures2d.SIFT_create()                  # 建立SIFT生成器
        """#####################################################
        # 如果是OpenCV3.X,则用cv2.xfeatures2d.SIFT_create方法来实现DoG关键点检测和SIFT特征提取。
        # 如果是OpenCV2.4,则用cv2.FeatureDetector_create方法来实现关键点的检测(DoG)。
        #####################################################"""
        (kps, features) = descriptor.detectAndCompute(image, None)  # 检测SIFT特征点,并计算描述子
        kps = np.float32([kp.pt for kp in kps])                     # 将结果转换成NumPy数组

        return (kps, features)      # 返回特征点集,及对应的描述特征

    ##################################################################################
    def matchKeypoints(self, kpsA, kpsB, featuresA, featuresB, ratio, reprojThresh):
        matcher = cv2.BFMatcher()                                   # 建立暴力匹配器
        rawMatches = matcher.knnMatch(featuresA, featuresB, 2)      # 使用KNN检测来自A、B图的SIFT特征匹配对,K=2

        matches = []
        for m in rawMatches:
            if len(m) == 2 and m[0].distance < m[1].distance * ratio:   # 当最近距离跟次近距离的比值小于ratio值时,保留此匹配对
                matches.append((m[0].trainIdx, m[0].queryIdx))          # 存储两个点在featuresA, featuresB中的索引值

        if len(matches) > 4:        # 当筛选后的匹配对大于4时,计算视角变换矩阵
            # 投影变换矩阵:3*3。有八个参数对应八个方程,其中一个为1用于归一化。对应四对,每对(x, y)
            ptsA = np.float32([kpsA[i] for (_, i) in matches])          # 获取匹配对的点坐标
            ptsB = np.float32([kpsB[i] for (i, _) in matches])
            (H, status) = cv2.findHomography(ptsA, ptsB, cv2.RANSAC, reprojThresh)      # 使用RANSAC算法利用匹配特征向量估计单映矩阵(homography:单应性)
            return (matches, H, status)

        return None     # 如果匹配对小于4时,返回None

    ##################################################################################
    def drawMatches(self, imageA, imageB, kpsA, kpsB, matches, status):
        (hA, wA) = imageA.shape[:2]
        (hB, wB) = imageB.shape[:2]
        vis = np.zeros((max(hA, hB), wA + wB, 3), dtype="uint8")
        vis[0:hA, 0:wA] = imageA        # 将A、B图左右连接到一起
        vis[0:hB, wA:] = imageB

        for ((trainIdx, queryIdx), s) in zip(matches, status):
            if s == 1:      # 当点对匹配成功时,画到可视化图上
                ptA = (int(kpsA[queryIdx][0]), int(kpsA[queryIdx][1]))
                ptB = (int(kpsB[trainIdx][0]) + wA, int(kpsB[trainIdx][1]))
                cv2.line(vis, ptA, ptB, (0, 255, 0), 1)

        return vis      # 返回可视化结果


##################################################################################
if __name__ == '__main__':
    # 读取拼接图片
    imageA = cv2.imread("left_01.png")
    imageB = cv2.imread("right_01.png")

    # 把图片拼接成全景图
    stitcher = Stitcher()       # 调用拼接函数
    (result, vis) = stitcher.stitch([imageA, imageB], showMatches=True)

    # 显示所有图片
    cv2.imshow("Image A", imageA)
    cv2.imshow("Image B", imageB)
    cv2.imshow("Keypoint Matches", vis)
    cv2.imshow("Result", result)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

基于OpenCV全景拼接(Python)SIFT/SURF

(四)停车场车位检测(基于Keras的CNN分类) —— pickle.dump()、pickle.load()、cv2.fillPoly()、cv2.bitwise_and()、cv2.circle()、cv2.HoughLinesP()、cv2.line()

该项目共分为三个py文件:Parking.py(定义所有的功能函数)、train.py(训练神经网络)、park_test.py(开始检测停车位状态)
 在这里插入图片描述
在这里插入图片描述

(1)Parking.py

#####################################################
# Parking.py
#####################################################
import cv2                          # opencv    读取图像默认为BGR
import matplotlib.pyplot as plt     # matplotlib显示图像默认为RGB
import os
import glob
import numpy as np


class Parking:
    
    def show_images(self, images, cmap=None):
        cols = 2
        rows = (len(images)+1)//cols
        
        plt.figure(figsize=(15, 12))
        for i, image in enumerate(images):
            plt.subplot(rows, cols, i+1)
            cmap = 'gray' if len(image.shape) == 2 else cmap
            plt.imshow(image, cmap=cmap)
            plt.xticks([])
            plt.yticks([])
        plt.tight_layout(pad=0, h_pad=0, w_pad=0)
        plt.show()
    
    def cv_show(self, name, img):
        cv2.imshow(name, img)
        cv2.waitKey(0)
        cv2.destroyAllWindows()

    def select_rgb_white_yellow(self, image):
        # 图像背景信息过滤(即截取图像中,指定范围的颜色)
        lower = np.uint8([120, 120, 120])
        upper = np.uint8([255, 255, 255])
        # (1)lower_red和高于upper_red的部分分别变成0
        # (2)lower_red~upper_red之间的值变成255
        white_mask = cv2.inRange(image, lower, upper)
        self.cv_show('white_mask', white_mask)
        
        masked = cv2.bitwise_and(image, image, mask=white_mask)
        self.cv_show('masked', masked)
        return masked

    def convert_gray_scale(self, image):
        return cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)

    def detect_edges(self, image, low_threshold=50, high_threshold=200):
        return cv2.Canny(image, low_threshold, high_threshold)
    
    def filter_region(self, image, vertices):
        # 功能:剔除掉不需要的地方,提取出多边形(所有车位的位置),纯白显示
        #####################################################
        # zeros_like(array, dtype=float, order='C')
        # 作用:返回一个给定形状和类型的数据,填充全为0。
        # 输入参数         (1)array:输入数据
        #                (2)dtype:返回数组的数据类型(可选参数,默认float)
        #                (3)order:C代表行优先;F代表列优先(可选参数)
        #####################################################
        mask = np.zeros_like(image)             # 新建模板(0:纯黑色)
        if len(mask.shape) == 2:
            #####################################################
            # 填充任意多边形:cv2.fillPoly(img, ppt, Scalar);
            # 输入参数          (1)img       在该图像上绘图。
            #                 (2)ppt        多边形的顶点集
            #                 (5)Scarlar    填充多边形的颜色(255, 255, 255),即RGB的值为白色
            #####################################################
            cv2.fillPoly(mask, vertices, 255)   # 填充多边形(255:纯白色)
            self.cv_show('mask', mask)
            #####################################################
            # cv2.bitwise_and()  # 按位与
            # cv2.bitwise_or()  # 或
            # cv2.bitwise_not()  # 非
            # cv2.bitwise_xor()  # 异或
            ###################################
            # dst = cv2.bitwise_and(src1, src2, mask=mask)
            #           src1/src2是相同类型和大小的图像
            #           mask=mask表示要提取的区域(可选参数)
            # (1&1=1,1&0=0,0&1=0,0&0=0)
            #####################################################
        return cv2.bitwise_and(image, mask)     # 按位与操作

    def select_region(self, image):
        # 功能:(手动)选择区域 —— 在原图中,筛选出所有车位的位置,并框成一个多边形。
        # 多边形的关键点位置由自定义设置,详细如下:
        rows, cols = image.shape[:2]        # 获得图像的长和宽
        pt_1 = [cols*0.05, rows*0.90]       # 位置点1
        pt_2 = [cols*0.05, rows*0.70]       # 位置点2
        pt_3 = [cols*0.30, rows*0.55]       # 位置点3
        pt_4 = [cols*0.6, rows*0.15]        # 位置点4
        pt_5 = [cols*0.90, rows*0.15]       # 位置点5
        pt_6 = [cols*0.90, rows*0.90]       # 位置点6

        vertices = np.array([[pt_1, pt_2, pt_3, pt_4, pt_5, pt_6]], dtype=np.int32)         # 将数据转换为numpy数组
        point_img = image.copy()       
        point_img = cv2.cvtColor(point_img, cv2.COLOR_GRAY2RGB)                 # 转换为灰度图
        for point in vertices[0]:           # 遍历所有的位置点
            """#####################################################
            # 绘制圆型:cv2.circle(图像, 圆心, 半径, 颜色, 厚度)
            # 输入参数         (1)图像:在该图像上绘图。
            #                (2)圆心:圆的中心坐标。坐标表示为两个值的元组, 即(X坐标值, Y坐标值)。
            #                (3)半径:圆的半径。
            #                (4)颜色:圆的边界线颜色。对于BGR, 我们传递一个元组。例如:(255, 0, 0)为蓝色。
            #                (5)厚度:正数表示线的粗细。其中:-1表示实心圆。
            #####################################################"""
            cv2.circle(point_img, (point[0], point[1]), 10, (0, 0, 255), 4)     # 在每一个位置点绘制一个空心圆
        self.cv_show('point_img', point_img)

        return self.filter_region(image, vertices)

    def hough_lines(self, image):
        """#####################################################
        # 检测图像中所有的线:cv2.HoughLinesP(image, rho=0.1, theta=np.pi / 10, threshold=15, minLineLength=9, maxLineGap=4)
        # image             输入的图像需要是边缘检测后的结果
        # minLineLengh      (线的最短长度,比这个短的都被忽略)
        # MaxLineCap        (两条直线之间的最大间隔,小于此值,认为是一条直线)
        # rho               距离精度
        # theta             角度精度
        # threshod          超过设定阈值才被检测出线段
        #####################################################"""
        return cv2.HoughLinesP(image, rho=0.1, theta=np.pi/10, threshold=15, minLineLength=9, maxLineGap=4)
        
    def draw_lines(self, image, lines, color=[255, 0, 0], thickness=2, make_copy=True):
        # 功能:在原图上画出满足条件的所有线
        if make_copy:
            image = np.copy(image) 
        cleaned = []
        for line in lines:
            for x1, y1, x2, y2 in line:     # 一条线由两个点组成,每个点的坐标为(x, y)
                # abs(y2-y1) <= 1 ———— 图像中都是直线,斜率趋近于0
                # abs(x2-x1) >= 25 and abs(x2-x1) <= 55 ———— 线段自定义筛选(依据实际情况设置)
                if abs(y2-y1) <= 1 and abs(x2-x1) >= 25 and abs(x2-x1) <= 55:
                    cleaned.append((x1, y1, x2, y2))        # 保存满足条件的所有线
                    cv2.line(image, (x1, y1), (x2, y2), color, thickness)       # 在原图上,画出满足条件的所有线
        print(" No lines detected: ", len(cleaned))
        return image

    def identify_blocks(self, image, lines, make_copy=True):
        #####################################################
        # 功能:识别出所有的车位
        #           Step 1: 过滤部分直线,提取有效的线(即对应于车位的线)
        #           Step 2: 对直线进行排序
        #           Step 3: 找到多个列,每一列对应一排车
        #           Step 4: 得到每一列矩形的坐标
        #           Step 5: 把列矩形画出来
        #####################################################
        if make_copy:
            new_image = np.copy(image)

        # Step 1: 过滤部分直线,提取有效的线(即对应于车位的线)
        cleaned = []
        for line in lines:
            for x1, y1, x2, y2 in line:
                if abs(y2-y1) <=1 and abs(x2-x1) >=25 and abs(x2-x1) <= 55:
                    cleaned.append((x1, y1, x2, y2))        # 保存满足条件的所有线

        # Step 2: 对直线进行排序
        import operator
        list1 = sorted(cleaned, key=operator.itemgetter(0, 1))      # 给所有的线标记顺序(排序:从上到下,从左到右)

        # Step 3: 找到多个列,每一列对应一排车
        clusters = {}       # 找到同一列的所有线
        dIndex = 0
        clus_dist = 10      # 列与最近一列的距离(依据实际情况设置)
        for i in range(len(list1) - 1):
            distance = abs(list1[i+1][0] - list1[i][0])
            if distance <= clus_dist:
                if not dIndex in clusters.keys(): clusters[dIndex] = []
                clusters[dIndex].append(list1[i])
                clusters[dIndex].append(list1[i + 1]) 
            else:
                dIndex += 1     # 同一列的线就汇总,否则跳过

        # Step 4: 得到每一列矩形的坐标
        rects = {}
        i = 0
        for key in clusters:        # 12列
            all_list = clusters[key]
            cleaned = list(set(all_list))
            if len(cleaned) > 5:        # 如果个数大于5,则定义为一列
                cleaned = sorted(cleaned, key=lambda tup: tup[1])
                avg_y1 = cleaned[0][1]      # 提取每一列第一条线。        [0]表示第一条线
                avg_y2 = cleaned[-1][1]     # 提取每一列最后一条线        [-1]表示最后一条线
                avg_x1 = 0                  # 由于不同线的 x 层次不齐,故取均值
                avg_x2 = 0
                for tup in cleaned:
                    avg_x1 += tup[0]
                    avg_x2 += tup[2]
                avg_x1 = avg_x1/len(cleaned)    # x1是矩形的起始点
                avg_x2 = avg_x2/len(cleaned)    # x2是矩形的终止点
                rects[i] = (avg_x1, avg_y1, avg_x2, avg_y2)     # 得到每一列矩形的四点坐标
                i += 1
        print("Num Parking Lanes: ", len(rects))        # 共有12个矩形
        # Step 5: 把列矩形画出来
        buff = 7
        for key in rects:       # key表示第几列
            tup_topLeft = (int(rects[key][0] - buff), int(rects[key][1]))
            tup_botRight = (int(rects[key][2] + buff), int(rects[key][3]))
            cv2.rectangle(new_image, tup_topLeft, tup_botRight, (0, 255, 0), 3)
        return new_image, rects
    
    def draw_parking(self, image, rects, make_copy=True, color=[255, 0, 0], thickness=2, save=True):
        if make_copy:
            new_image = np.copy(image)
        gap = 15.5      # 固定每两个停车位之间的距离间隔(y轴)
        spot_dict = {}  # 字典:一个车位对应一个位置
        tot_spots = 0
        # 微调 ——— 由于检测得到的矩形具有一定的误差,所以进行人为操作,达到精度化
        adj_y1 = {0: 20, 1: -10, 2: 0, 3: -11, 4: 28, 5: 5, 6: -15, 7: -15, 8: -10, 9: -30, 10: 9, 11: -32}
        adj_y2 = {0: 30, 1: 50, 2: 15, 3: 10, 4: -15, 5: 15, 6: 15, 7: -20, 8: 15, 9: 15, 10: 0, 11: 30}
        
        adj_x1 = {0: -8, 1: -15, 2: -15, 3: -15, 4: -15, 5: -15, 6: -15, 7: -15, 8: -10, 9: -10, 10: -10, 11: 0}
        adj_x2 = {0: 0, 1: 15, 2: 15, 3: 15, 4: 15, 5: 15, 6: 15, 7: 15, 8: 10, 9: 10, 10: 10, 11: 0}
        for key in rects:       # key表示第几列
            tup = rects[key]
            x1 = int(tup[0] + adj_x1[key])
            x2 = int(tup[2] + adj_x2[key])
            y1 = int(tup[1] + adj_y1[key])
            y2 = int(tup[3] + adj_y2[key])
            cv2.rectangle(new_image, (x1, y1), (x2, y2), (0, 255, 0), 2)        # 在图像上画出微调后的矩形

            num_splits = int(abs(y2-y1)//gap)       # 计算每一列可以平均停放多少辆车(由于误差不能识别出精确的停车位,故进行平均计算估计)
            for i in range(0, num_splits+1):        # 切六刀,则有七个停车位
                y = int(y1 + i*gap)
                #####################################################
                # 画直线段:cv2.line(img, pt1, pt2, color, thickness)
                # 输入参数        img               要划的线所在的图像;
                #                pt1               直线起点
                #                pt2               直线终点
                #                color             直线的颜色
                #                thickness=1       线条粗细
                #####################################################
                # 画出所有停车位的横线
                cv2.line(new_image, (x1, y), (x2, y), color, thickness)
            if 0 < key < len(rects) - 1:        # 第一列与最后一列都是单排停车位,其余都是双排停车位(依据实际情况设置)
                # 画出双排停车位之间的竖直线
                x = int((x1 + x2)/2)            # 两个位置点的中点就是对应的竖直线坐标
                cv2.line(new_image, (x, y1), (x, y2), color, thickness)

            # 计算数量
            if key == 0 or key == (len(rects) - 1):     # 如果是单排停车位,直接+1
                tot_spots += num_splits + 1
            else:                                       # 如果是双排停车位,就要+1,再乘以2
                tot_spots += 2*(num_splits + 1)

            # 用字典将每个停车位进行键值对一一对应
            if key == 0 or key == (len(rects) - 1):     # 第一列或最后一列(单排停车位)
                for i in range(0, num_splits+1):
                    cur_len = len(spot_dict)
                    y = int(y1 + i*gap)
                    spot_dict[(x1, y, x2, y+gap)] = cur_len + 1     # 第一列和最后一列停车位的坐标
            else:                                       # 双排停车位
                for i in range(0, num_splits+1):
                    cur_len = len(spot_dict)
                    y = int(y1 + i*gap)
                    x = int((x1 + x2)/2)                # 双排停车位对应的中点位置
                    spot_dict[(x1, y, x, y+gap)] = cur_len + 1      # 双排停车位的左边一排坐标
                    spot_dict[(x, y, x2, y+gap)] = cur_len + 2      # 双排停车位的右边一排坐标
        print("total parking spaces: ", tot_spots, cur_len)
        if save:
            filename = 'with_parking.jpg'
            cv2.imwrite(filename, new_image)
        return new_image, spot_dict
    
    def assign_spots_map(self, image, spot_dict, make_copy=True, color=[255, 0, 0], thickness=2):
        if make_copy:
            new_image = np.copy(image)
        for spot in spot_dict.keys():
            (x1, y1, x2, y2) = spot
            cv2.rectangle(new_image, (int(x1), int(y1)), (int(x2), int(y2)), color, thickness)
        return new_image
    
    def save_images_for_cnn(self, image, spot_dict, folder_name='cnn_data'):
        # 功能:裁剪停车位得到所有停车位对应的图像,并保存到指定的文件夹路径下。(提供数据给CNN训练。训练前需人工筛选图像,分为两类:车位是否被占用。)
        for spot in spot_dict.keys():           # 遍历所有停车位 —— 字典的键进行索引
            (x1, y1, x2, y2) = spot
            (x1, y1, x2, y2) = (int(x1), int(y1), int(x2), int(y2))     # 坐标值取整

            spot_img = image[y1:y2, x1:x2]      # 裁剪停车位
            spot_img = cv2.resize(spot_img, (0, 0), fx=2.0, fy=2.0)     # 图像裁剪(原图像是在太mini)
            spot_id = spot_dict[spot]           # 停车位坐标 —— 键对应的值
            
            filename = 'spot' + str(spot_id) + '.jpg'                   # 每个停车位用键来命名(以此可以后续索引需求)
            print(spot_img.shape, filename, (x1, x2, y1, y2))
            cv2.imwrite(os.path.join(folder_name, filename), spot_img)  # 保存停车位图像到指定的文件夹(cnn_data)路径下

    def make_prediction(self, image, model, class_dictionary):
        # 预处理
        img = image/255.
        # 转换成4D tensor
        image = np.expand_dims(img, axis=0)
        # 用训练好的模型进行训练
        class_predicted = model.predict(image)
        inID = np.argmax(class_predicted[0])
        label = class_dictionary[inID]
        return label

    def predict_on_image(self,image, spot_dict , model,class_dictionary,make_copy=True, color=[0, 255, 0], alpha=0.5):
        if make_copy:
            new_image = np.copy(image)
            overlay = np.copy(image)
        self.cv_show('new_image', new_image)
        cnt_empty = 0
        all_spots = 0
        for spot in spot_dict.keys():
            all_spots += 1
            (x1, y1, x2, y2) = spot
            (x1, y1, x2, y2) = (int(x1), int(y1), int(x2), int(y2))
            spot_img = image[y1:y2, x1:x2]
            spot_img = cv2.resize(spot_img, (48, 48)) 
            
            label = self.make_prediction(spot_img,model,class_dictionary)       # 预测图像(停车位)是否被占用
            if label == 'empty':
                cv2.rectangle(overlay, (int(x1), int(y1)), (int(x2), int(y2)), color, -1)
                cnt_empty += 1
        cv2.addWeighted(overlay, alpha, new_image, 1 - alpha, 0, new_image)     # 图像融合
        cv2.putText(new_image, "Available: %d spots" % cnt_empty, (30, 95), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
        cv2.putText(new_image, "Total: %d spots" % all_spots, (30, 125), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
        save = False
        
        if save:
            filename = 'with_marking.jpg'
            cv2.imwrite(filename, new_image)
        self.cv_show('new_image', new_image)
        return new_image
        
    def predict_on_video(self, video_name, final_spot_dict, model, class_dictionary, ret=True):
        cap = cv2.VideoCapture(video_name)
        count = 0
        while ret:
            ret, image = cap.read()
            count += 1
            if count == 5:      # 每四帧图像判断一次
                count = 0

                new_image = np.copy(image)
                overlay = np.copy(image)
                cnt_empty = 0
                all_spots = 0
                color = [0, 255, 0] 
                alpha = 0.5
                for spot in final_spot_dict.keys():
                    all_spots += 1
                    (x1, y1, x2, y2) = spot
                    (x1, y1, x2, y2) = (int(x1), int(y1), int(x2), int(y2))
                    spot_img = image[y1:y2, x1:x2]
                    spot_img = cv2.resize(spot_img, (48,48)) 
    
                    label = self.make_prediction(spot_img, model, class_dictionary)     # 预测帧图像(停车位)是否被占用
                    if label == 'empty':
                        cv2.rectangle(overlay, (int(x1), int(y1)), (int(x2), int(y2)), color, -1)
                        cnt_empty += 1
                cv2.addWeighted(overlay, alpha, new_image, 1 - alpha, 0, new_image)
                cv2.putText(new_image, "Available: %d spots" % cnt_empty, (30, 95), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
                cv2.putText(new_image, "Total: %d spots" % all_spots, (30, 125), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
                cv2.imshow('frame', new_image)
                if cv2.waitKey(10) & 0xFF == ord('q'):
                    break
        cv2.destroyAllWindows()
        cap.release()

(2)train.py

#####################################################
# train.py
#####################################################
import numpy
import os
from keras import applications
from keras.preprocessing.image import ImageDataGenerator
from keras import optimizers
from keras.models import Sequential, Model
from keras.layers import Dropout, Flatten, Dense, GlobalAveragePooling2D
from keras import backend as k
from keras.callbacks import ModelCheckpoint, LearningRateScheduler, TensorBoard, EarlyStopping
from keras.models import Sequential
from keras.layers.normalization import BatchNormalization
from keras.layers.convolutional import Conv2D
from keras.layers.convolutional import MaxPooling2D
from keras.initializers import TruncatedNormal
from keras.layers.core import Activation
from keras.layers.core import Flatten
from keras.layers.core import Dropout
from keras.layers.core import Dense

files_train = 0
files_validation = 0
########################################
cwd = os.getcwd()       # 获取当前工作路径
folder = 'train_data/train'     # 训练数据
for sub_folder in os.listdir(folder):
    path, dirs, files = next(os.walk(os.path.join(folder, sub_folder)))     # 读取数据
    files_train += len(files)
########################################
folder = 'train_data/test'      # 测试数据
for sub_folder in os.listdir(folder):
    path, dirs, files = next(os.walk(os.path.join(folder, sub_folder)))     # 读取数据
    files_validation += len(files)
########################################
print(files_train, files_validation)
########################################
# CNN训练参数指定
img_width, img_height = 48, 48
train_data_dir = "train_data/train"
validation_data_dir = "train_data/test"
nb_train_samples = files_train
nb_validation_samples = files_validation
batch_size = 32
epochs = 15
num_classes = 2
# 调用kera框架中的applications中的VGG16网络。里面封装了好几个网络模型(VGG16、VGG19、Resnet50、MobileNet等等)
# weights='imagenet'        表示直接调用imagenet训练好的权重(当前数据太少)
model = applications.VGG16(weights='imagenet', include_top=False, input_shape=(img_width, img_height, 3))
########################################
# 将前10层网络" 冻起来 "
for layer in model.layers[:10]:
    layer.trainable = False
x = model.output
x = Flatten()(x)

predictions = Dense(num_classes, activation="softmax")(x)
model_final = Model(input=model.input, output=predictions)
model_final.compile(loss="categorical_crossentropy", optimizer = optimizers.SGD(lr=0.0001, momentum=0.9), metrics=["accuracy"])
########################################
# 数据增强
train_datagen = ImageDataGenerator(rescale=1./255, horizontal_flip=True, fill_mode="nearest",
                                   zoom_range=0.1, width_shift_range=0.1, height_shift_range=0.1, rotation_range=5)
test_datagen = ImageDataGenerator(rescale=1./255, horizontal_flip=True, fill_mode="nearest",
                                  zoom_range=0.1, width_shift_range=0.1, height_shift_range=0.1, rotation_range=5)
train_generator = train_datagen.flow_from_directory(train_data_dir, target_size=(img_height, img_width), batch_size=batch_size, class_mode="categorical")
validation_generator = test_datagen.flow_from_directory(validation_data_dir, target_size=(img_height, img_width), class_mode="categorical")
########################################
checkpoint = ModelCheckpoint("car1.h5", monitor='val_acc', verbose=1, save_best_only=True, save_weights_only=False, mode='auto', period=1)
early = EarlyStopping(monitor='val_acc', min_delta=0, patience=10, verbose=1, mode='auto')
history_object = model_final.fit_generator(train_generator, samples_per_epoch=nb_train_samples, epochs=epochs,
                                           validation_data=validation_generator, nb_val_samples=nb_validation_samples, callbacks=[checkpoint, early])
# 训练完之后会自动生成文件:car1.h5
# 备注:最终的训练结果只有百分之九十。(1)数据预处理还有进步空间(2)神经网络模型可优化

(3)park_test.py

#####################################################
# park_test.py
#####################################################
from __future__ import division
import matplotlib.pyplot as plt
import cv2
import os
import glob
import numpy as np

from keras.applications.imagenet_utils import preprocess_input
from keras.models import load_model
from keras.preprocessing import image
from PIL import Image
# Pillow(PIL)是Python中较为基础的图像处理库,主要用于图像的基本处理,比如裁剪图像、调整图像大小和图像颜色处理等。
# 与Pillow相比,OpenCV和Scikit-image的功能更为丰富,所以使用起来也更为复杂,主要应用于机器视觉、图像分析等领域,比如众所周知的“人脸识别”应用 。
import pickle

from Parking import Parking     # 导入自定义库
cwd = os.getcwd()


def img_process(test_images, park):     # park:实例化的类
    white_yellow_images = list(map(park.select_rgb_white_yellow, test_images))      # select_rgb_white_yellow():图像背景信息过滤
    park.show_images(white_yellow_images)
    ########################################################################################################
    gray_images = list(map(park.convert_gray_scale, white_yellow_images))           # convert_gray_scale():转换为灰度图
    park.show_images(gray_images)
    ########################################################################################################
    edge_images = list(map(lambda image: park.detect_edges(image), gray_images))    # detect_edges():使用cv2.Canny算法进行边缘检测
    park.show_images(edge_images)
    ########################################################################################################
    roi_images = list(map(park.select_region, edge_images))     # select_region():筛选出多边形(停车场的位置),去除图像中冗余的区域
    park.show_images(roi_images)
    ########################################################################################################
    list_of_lines = list(map(park.hough_lines, roi_images))     # hough_lines():检测图像中所有的线
    ########################################################################################################
    line_images = []
    for image, lines in zip(test_images, list_of_lines):
        line_images.append(park.draw_lines(image, lines))       # draw_lines():画出图像中所有满足条件的线(即停车线)
    park.show_images(line_images)
    ########################################################################################################
    rect_images = []        # new_image 绘制有所有矩形的图像
    rect_coords = []        # rects 每个矩形对应的四点坐标
    for image, lines in zip(test_images, list_of_lines):
        new_image, rects = park.identify_blocks(image, lines)   # identify_blocks(): 画出每一列的矩形
        rect_images.append(new_image)
        rect_coords.append(rects)
    park.show_images(rect_images)       # 绘制(矩形)图像
    ########################################################################################################
    delineated = []         # new_image 绘制有微调后的所有矩形、以及所有停车位的图像
    spot_pos = []           # spot_dict 每个停车位的坐标(字典 - 数据结构)
    for image, rects in zip(test_images, rect_coords):
        new_image, spot_dict = park.draw_parking(image, rects)  # draw_parking:画出停车位
        delineated.append(new_image)
        spot_pos.append(spot_dict)
    park.show_images(delineated)        # 绘制(矩形+停车位)图像
    final_spot_dict = spot_pos[1]       # 取出字典(键值对)的所有值(每个值对应一个停车位的坐标位置)。[1]:值
    print(len(final_spot_dict))         # 打印所有的停车位数
    ########################################################################################################
    with open('spot_dict.pickle', 'wb') as handle:      # 打开文件(open),且自动关闭(with)
        """#####################################################
        # Python中的pickle模块实现了基本的数据序列与反序列化。序列化对象可以在磁盘上保存对象,并在需要的时候读取出来。任何对象都可以执行序列化操作。
        #####################################################
        # (1)序列化-存档:pickle.dump(obj, file, protocol)
        # 输入参数           对象:就是你要存的东西,类型可以是list、string以及其他任何类型
        #                   文件:就是要将对象存储的目标文件
        #                   使用协议:有3种,索引0为ASCII(默认值),1是旧式2进制,2是新式2进制协议
        #       fw = open("pickleFileName.txt", "wb")
        #       pickle.dump("try", fw)
        #####################################################
        # (2)反序列化-读档:pickle.load(file)
        #       fr = open("pickleFileName.txt", "rb")
        #       result = pickle.load(fr)
        #####################################################"""
        pickle.dump(final_spot_dict, handle, protocol=pickle.HIGHEST_PROTOCOL)      # 将当前预处理的结果保存下来,以后直接调用即可。
    ########################################################################################################
    park.save_images_for_cnn(test_images[0], final_spot_dict)   # save_images_for_cnn(): 调用CNN神经网络对车位是否被占用进行识别
    return final_spot_dict
    ########################################################################################################


def keras_model(weights_path):
    # from keras.models import load_model
    model = load_model(weights_path)        # load_model():可实现直接读取文件: car1.h5(神经网络训练后生成的模型)
    return model


def img_test(test_images, final_spot_dict, model, class_dictionary):
    for i in range(len(test_images)):
        predicted_images = park.predict_on_image(test_images[i], final_spot_dict, model, class_dictionary)
        # predict_on_image():


def video_test(video_name, final_spot_dict, model, class_dictionary):
    name = video_name
    cap = cv2.VideoCapture(name)
    park.predict_on_video(name, final_spot_dict, model, class_dictionary, ret=True)
    
    
if __name__ == '__main__':
    # 文件夹名:test_images
    test_images = [plt.imread(path) for path in glob.glob('test_images/*.jpg')]
    weights_path = 'car1.h5'
    video_name = 'parking_video.mp4'
    # class_dictionary = {0: 'empty', 1: 'occupied'}  # 车位占用状态
    class_dictionary = {}               # 车位占用状态
    class_dictionary[0] = 'empty'       # 车位为空
    class_dictionary[1] = 'occupied'    # 车位已占用

    park = Parking()                    # 类的实例化
    park.show_images(test_images)

    final_spot_dict = img_process(test_images, park)        # 图像预处理
    model = keras_model(weights_path)                       # 加载神经网络训练好的模型

    img_test(test_images, final_spot_dict, model, class_dictionary)
    video_test(video_name, final_spot_dict, model, class_dictionary)
    

8种主流深度学习框架介绍
Python 中的 PIL 库

(五)答题卡识别与判卷 —— cv2.putText()、cv2.countNonZero()

在这里插入图片描述

import numpy as np
import cv2                          # opencv    读取图像默认为BGR
import matplotlib.pyplot as plt     # matplotlib显示图像默认为RGB


def order_points(pts):
	# 一共4个坐标点
	rect = np.zeros((4, 2), dtype="float32")
	# 按顺序找到对应坐标0123分别是 左上,右上,右下,左下
	# 计算左上,右下
	s = pts.sum(axis=1)
	rect[0] = pts[np.argmin(s)]
	rect[2] = pts[np.argmax(s)]
	# 计算右上和左下
	diff = np.diff(pts, axis=1)
	rect[1] = pts[np.argmin(diff)]
	rect[3] = pts[np.argmax(diff)]
	return rect


def four_point_transform(image, pts):
	# 获取输入坐标点
	rect = order_points(pts)
	(tl, tr, br, bl) = rect
	# 计算输入的w和h值
	widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))
	widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))
	maxWidth = max(int(widthA), int(widthB))

	heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))
	heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))
	maxHeight = max(int(heightA), int(heightB))
	# 变换后对应坐标位置
	dst = np.array([[0, 0], [maxWidth - 1, 0], [maxWidth - 1, maxHeight - 1], [0, maxHeight - 1]], dtype="float32")
	# 计算变换矩阵
	M = cv2.getPerspectiveTransform(rect, dst)							# 计算齐次变换矩阵:cv2.getPerspectiveTransform(rect, dst)
	warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))		# 透视变换:(将输入矩形乘以(齐次变换矩阵),得到输出矩阵)
	return warped


def sort_contours(cnts, method="left-to-right"):
	reverse = False
	i = 0
	if method == "right-to-left" or method == "bottom-to-top":
		reverse = True
	if method == "top-to-bottom" or method == "bottom-to-top":
		i = 1
	boundingBoxes = [cv2.boundingRect(c) for c in cnts]
	(cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes), key=lambda b: b[1][i], reverse=reverse))
	return cnts, boundingBoxes


"""#############################################################
# if __name__ == '__main__':
# (1)“__name__”是Python的内置变量,用于指代当前模块。
# (2)当哪个模块被直接执行时,该模块“__name__”的值就是“__main__”。
# (3)当被导入另一模块时,“__name__”的值就是模块的真实名称。
#############################################################"""
# 需给定每张图像对应选项的正确答案(字典:键对应行,值对应每行的答案)
ANSWER_KEY = {0: 1, 1: 4, 2: 0, 3: 3, 4: 1}

# 图像预处理
image = cv2.imread(r"images/test_01.png")
contours_img = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)		# 转换为灰度图
blurred = cv2.GaussianBlur(gray, (5, 5), 0)			# 高斯滤波-去除噪音
edged = cv2.Canny(blurred, 75, 200)					# Canny算子边缘检测
cnts, hierarchy = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)		# 轮廓检测
cv2.drawContours(contours_img, cnts, -1, (0, 0, 255), 3)											# 画出轮廓(答题卡)
###################################################################
# 提取答题卡并进行透视变化
docCnt = None
if len(cnts) > 0:
	cnts = sorted(cnts, key=cv2.contourArea, reverse=True)		# 根据轮廓大小进行排序
	for c in cnts:		# 遍历每一个轮廓
		peri = cv2.arcLength(c, True)							# 计算轮廓的长度
		approx = cv2.approxPolyDP(c, 0.02*peri, True)			# 找出轮廓的多边形拟合曲线
		if len(approx) == 4:		# 找到的轮廓是四边形(对应四个顶点)
			docCnt = approx
			break

warped = four_point_transform(gray, docCnt.reshape(4, 2))											# 透视变换(齐次变换矩阵)
warped1 = warped.copy()
thresh = cv2.threshold(warped, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]					# 0:表示系统自动判断;THRESH_OTSU:自适应阈值设置
###############################
thresh_Contours = thresh.copy()
cnts, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)		# 找到每一个圆圈轮廓
cv2.drawContours(thresh_Contours, cnts, -1, (0, 0, 255), 3)											# 画出所有轮廓
###################################################################
# 提取答题卡中所有的有效选项(圆圈)
questionCnts = []		# 提取每个选项的轮廓
for c in cnts:
	(x, y, w, h) = cv2.boundingRect(c)		# 获取轮廓的尺寸
	ar = w / float(h)						# 计算比例
	if w >= 20 and h >= 20 and 0.9 <= ar <= 1.1:		# 自定义设置大小(根据实际情况)
		questionCnts.append(c)
questionCnts = sort_contours(questionCnts, method="top-to-bottom")[0]		# 按照从上到下对所有的选项进行排序
###############################
correct = 0
for (q, i) in enumerate(np.arange(0, len(questionCnts), 5)):		# 每排有5个选项
	cnts = sort_contours(questionCnts[i:i + 5])[0]					# 对每一排进行排序
	bubbled = None
	for (j, c) in enumerate(cnts):									# 遍历每一排对应的五个结果
		mask = np.zeros(thresh.shape, dtype="uint8")				# 使用mask来判断结果(全黑:0)表示涂写答案正确
		cv2.drawContours(mask, [c], -1, 255, -1) 					# -1表示填充
		# cv_show('mask', mask)		# 展示每个选项
		mask = cv2.bitwise_and(thresh, thresh, mask=mask)			# mask=mask表示要提取的区域(可选参数)
		total = cv2.countNonZero(mask)								# 通过计算非零点数量来算是否选择这个答案
		if bubbled is None or total > bubbled[0]:		# 记录最大数
			bubbled = (total, j)
	color = (0, 0, 255)				# 对比正确答案
	k = ANSWER_KEY[q]
	# 判断正确
	if k == bubbled[1]:
		color = (0, 255, 0)
		correct += 1
	cv2.drawContours(warped, [cnts[k]], -1, color, 3)		# 画出轮廓
###################################################################
# 展示结果
score = (correct / 5.0) * 100		# 计算总得分
print("[INFO] score: {:.2f}%".format(score))
"""###################################################################
# 在图像上添加文本内容: cv2.putText(img, str(i), (123,456), cv2.FONT_HERSHEY_PLAIN, 2, (0,255,0), 3)
# 各参数依次是:图片,添加的文字,左上角坐标,字体类型,字体大小,颜色,字体粗细
# 添加的字体:"{:.2f}%".format(score) ———— 表示添加score字符串。并且保留全部的整数位,小数点位保留两位。
###################################################################"""
cv2.putText(warped, "{:.1f}%".format(score), (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 2)

image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)						# opencv读取的格式是BGR,Matplotlib是RGB
contours_img = cv2.cvtColor(contours_img, cv2.COLOR_BGR2RGB)
plt.subplot(241),       plt.imshow(image, cmap='gray'),         	plt.axis('off'),    plt.title('image')
plt.subplot(242),       plt.imshow(blurred, cmap='gray'),       	plt.axis('off'),    plt.title('cv2.GaussianBlur')
plt.subplot(243),       plt.imshow(edged, cmap='gray'),         	plt.axis('off'),    plt.title('cv2.Canny')
plt.subplot(244),       plt.imshow(contours_img, cmap='gray'),      plt.axis('off'),    plt.title('cv2.findContours')
plt.subplot(245),       plt.imshow(warped1, cmap='gray'),        	plt.axis('off'),    plt.title('cv2.warpPerspective')
plt.subplot(246),       plt.imshow(thresh_Contours, cmap='gray'),   plt.axis('off'),    plt.title('cv2.findContours')
plt.subplot(247),       plt.imshow(warped, cmap='gray'),        	plt.axis('off'),    plt.title('cv2.warpPerspective')
plt.show()

(六)背景建模(动态目标识别) —— cv2.getStructuringElement()、cv2.createBackgroundSubtractorMOG2()

在这里插入图片描述
在这里插入图片描述

"""########################################################
# 背景建模(检测动态目标)
# 方法一:帧差法
#       介绍:由于场景中的目标在运动,目标的影像在不同图像帧中的位置不同。
#            (1)该类算法对时间上连续的两帧图像进行差分运算,不同帧对应的像素点相减,判断灰度差的绝对值。
#            (2)当绝对值超过一定阈值时,即可判断为运动目标,从而实现目标的检测功能。
#       优缺点:帧差法非常简单,但是会引入噪音和空洞问题
# 方法二:混合高斯模型
#       介绍:(1)背景训练,对图像中每个背景采用一个【混合高斯模型】进行模拟,每个背景的混合高斯的个数可以自适应。
#            (2)测试阶段,对新来的像素进行GMM匹配,如果该像素值能够匹配其中一个高斯,则认为是背景,否则认为是前景。
#       特点1:由于整个过程GMM模型在不断更新学习中,所以对动态背景有一定的鲁棒性。
#       特点2:在视频中对于像素点的变化情况应当是符合高斯分布,背景的实际分布应当是多个高斯分布混合在一起,每个高斯模型也可以带有权重。
# 混合高斯模型学习方法
#       1.首先初始化每个高斯模型矩阵参数。
#       2.取视频中T帧图像数据用来训练高斯混合模型,并将第一个像素当做第一个高斯分布。
#       3.其后的像素值与前一个高斯分布的均值进行比较,如果两者差值在3倍方差以内,则属于同一个高斯分布,并对其进行参数更新。否则用此像素创建一个新的高斯分布。
# 混合高斯模型测试方法
#       在测试阶段,对新来像素点的值与混合高斯模型中的每一个均值进行比较,如果其差值在2倍的方差之间的话,则认为是背景,否则认为是前景(动态目标)。
#       将前景赋值为255,背景赋值为0。这样就形成了一副前景二值图。
########################################################"""

import cv2
cap = cv2.VideoCapture('test.avi')  # 捕获摄像头
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))  # 形态学操作需要使用
fgbg = cv2.createBackgroundSubtractorMOG2()  # 创建一个混合高斯模型,用于背景建模
while True:
    ret, frame = cap.read()  # 读取帧图像
    # 移动的物体会被标记为白色,背景会被标记为黑色的
    fgmask = fgbg.apply(frame)  # 将混合高斯模型应用于所有的帧图像,得到前景的掩模(白色)。
    fgmask = cv2.morphologyEx(fgmask, cv2.MORPH_OPEN, kernel)  # 形态学(开运算)去噪点
    contours, hierarchy = cv2.findContours(fgmask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)  # 寻找视频中的轮廓
    for c in contours:
        perimeter = cv2.arcLength(c, True)  # 计算轮廓的长度
        if perimeter > 188:  # "人"在图像中的尺寸(根据实际的检测目标设置)
            x, y, w, h = cv2.boundingRect(c)  # 获取矩形框的左上角位置点以及长宽
            cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2)  # 绘制矩形边框(在当前帧图像)
    cv2.imshow('frame', frame)  # 当前帧图像
    cv2.imshow('fgmask', fgmask)  # 当前运动目标的轮廓
    k = cv2.waitKey(10) & 0xff
    if k == 27:  # 退出键
        break
cap.release()
cv2.destroyAllWindows()

mog2算法
opencv 8 --背景减除 – BackgroundSubtractorMOG2
OpenCV 中 getStructuringElement() 与 morphologyEx() 函数用法

(七)光流估计(轨迹点跟踪)—— cv2.goodFeaturesToTrack()、cv2.calcOpticalFlowPyrLK()

在这里插入图片描述

"""##########################################################################
# 光流是空间运动物体在观测成像平面上的像素运动的“瞬时速度”,是利用图像序列中像素在时间域上的变化以及相邻帧之间的相关性来找到上一帧跟当前帧之间存在的相应关系,从而实现目标跟踪。
# 三要素(必要条件)      (1)亮度恒定:同一点随着时间的变化(在连续帧之间),其亮度(像素强度)不会发生改变。
#                      (2)小运动:相邻像素具有相似的运动。
#                           因为只有小运动情况下,才能用前后帧之间单位位置变化引起的灰度变化去近似灰度对位置的偏导数。
#                      (3)空间一致:一个场景上邻近的点投影到图像上也是邻近点,且邻近点速度一致。
#                           因为光流法基本方程约束只有一个,而要求x,y方向的速度,有两个未知变量。所以需要连立n多个方程求解。
# - cv2.goodFeaturesToTrack() 确定要追踪的特征点
# - cv2.calcOpticalFlowPyrLK() 追踪视频中的特征点
##########################################################################"""
# 如果跟踪图像中的目标丢失或被遮掩,则后续图像将始终不再现实轨迹角点(待优化)。
import numpy as np
import cv2

cap = cv2.VideoCapture('test.avi')
feature_params = dict(maxCorners=150, qualityLevel=0.3, minDistance=12)      # ShiTomasi角点检测的参数
lk_params = dict(winSize=(15, 15), maxLevel=2)                              # Lucas Kanada光流检测的参数
color = np.random.randint(0, 255, (100, 3))                                 # 构建随机颜色
#################################
count = 0
while True:
    ret, old_frame = cap.read()      # 获取帧图像
    count = count + 1
    if count == 235:                # 挑选视频中指定的第N帧为第一帧图像
        break
old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)                      # 转化为灰度图
"""#################################################################
# 确定要追踪的特征点:cv2.goodFeaturesToTrack( image, maxCorners, qualityLevel, minDistance, mask=noArray(),
#                                           blockSize=3, bool useHarrisDetector=false, double k=0.04 );
# 输入参数            image:             输入图像,是八位的或者32位浮点型,单通道图像,所以有时候用灰度图
#                   maxCorners:         返回最大的角点数,是最有可能的角点数,如果这个参数不大于0,那么表示没有角点数的限制。
#                   qualityLevel:       图像角点的最小可接受参数,质量测量值乘以这个参数就是最小特征值,小于这个数的会被抛弃。
#                   minDistance:        返回的角点之间最小的欧式距离。
#                   mask:               检测区域。如果图像不是空的(它需要具有CV_8UC1类型和与图像相同的大小),它指定检测角的区域。
#                   blockSize:          用于计算每个像素邻域上的导数协变矩阵的平均块的大小。
#                   useHarrisDetector:  选择是否采用Harris角点检测,默认是false.
#                   k:                  Harris检测的自由参数。
# 输出参数           corners:             输出为角点。
# 备注:角点最大数量(数量越多,效率慢),品质因子(品质因子越大,角点越少,但越大越好)、角点距离(在角点距离范围内,取N个角点中最好的一个角点)
#################################################################"""
p0 = cv2.goodFeaturesToTrack(old_gray, mask=None, **feature_params)         # 传入字典类型时需要两个**
mask = np.zeros_like(old_frame)     # 为绘制光流追踪图,构建一个Mask
while True:
    ret, frame = cap.read()         # 循环获取帧图像
    if not ret:
        break
    frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    """#################################################################
    # 追踪视频中的特征点:p1, status, err = cv2.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, winSize=(15, 15), maxLevel=2)
    # 输入参数        old_gray         前一帧图像
    #               frame_gray       当前帧图像
    #               p0               待跟踪的特征点向量
    #               nextPts          None
    #               winSize          搜索窗口的大小
    #               maxLevel         最大的金字塔层数
    # 输出参数    p1            跟踪特征点向量
    #           status        特征点是否找到,找到的状态为1,未找到的状态为0
    #################################################################"""
    p1, st, err = cv2.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, **lk_params)
    # 选择轨迹点
    good_new = p1[st == 1]          # st == 1表示目标锁定,如果目标丢失,则后续都将不再找到目标。(因为后续帧图像是依据第一帧图像识别检测的)
    good_old = p0[st == 1]
    # 绘制轨迹
    for i, (new, old) in enumerate(zip(good_new, good_old)):
        a, b = new.ravel()
        c, d = old.ravel()
        a = int(a); b = int(b); c = int(c); d = int(d)
        mask = cv2.line(mask, (a, b), (c, d), color[i].tolist(), 2)
        """#####################################################
        # 画直线段:cv2.line(img, pt1, pt2, color, thickness)
        # 输入参数        img               要划的线所在的图像;
        #                pt1               直线起点
        #                pt2               直线终点
        #                color             直线的颜色
        #                thickness=1       线条粗细
        #####################################################"""
        frame = cv2.circle(frame, (a, b), 5, color[i].tolist(), -1)
        
        """#####################################################
        # 绘制圆型:cv2.circle(图像, 圆心, 半径, 颜色, 厚度)
        # 输入参数         (1)图像:在该图像上绘图。
        #                (2)圆心:圆的中心坐标。坐标表示为两个值的元组, 即(X坐标值, Y坐标值)。
        #                (3)半径:圆的半径。
        #                (4)颜色:圆的边界线颜色。对于BGR, 我们传递一个元组。例如:(255, 0, 0)为蓝色。
        #                (5)厚度:正数表示线的粗细。其中:-1表示实心圆。
        #####################################################"""
    img = cv2.add(frame, mask)          # 将轨迹线与帧图像叠加

    cv2.imshow('frame', img)
    k = cv2.waitKey(50) & 0xff
    if k == 27:     # 退出键
        break
    old_gray = frame_gray.copy()        # 实时更新前一帧
    p0 = good_new.reshape(-1, 1, 2)

cv2.destroyAllWindows()
cap.release()

图文详解 OpenCV中光流以及视频特征点追踪(稀疏光流追踪+ 优化版稀疏光流追踪+密集光流追踪)

(八)DNN模块的分类 —— cv2.dnn.blobFromImage()

在这里插入图片描述

import utils_paths
import numpy as np
import cv2

##################################################################
# 提取标签文件中每一行的内容
# 		(1)训练模型标签文件:"synset_words.txt"
# 		(2)使用open().read()	:打开并读取txt文件中所有的字符串
# 		(3)strip()				:删除字符串两端的空格
# 		(4)split('\n')			:提取每一行的内容
rows = open("synset_words.txt").read().strip().split("\n")
##################################################################
# 提取出每行第一个空格后的字符串
# 		(1)对每一行的内容进行遍历,遍历之后找每一行的空格(r.find(' '))。
# 		(2)找到位置后让位置+1,然后r提取到+1的位置一直到最后
# 		(3)以所有逗号为分隔符然后删除他们,取分割的第一个值
classes = [r[r.find(" ") + 1:].split(",")[0] for r in rows]
##################################################################
# 加载Caffe所需文件
# 		(1)配置文件:"bvlc_googlenet.prototxt"
# 		(2)训练好的权重参数:"bvlc_googlenet.caffemodel"
net = cv2.dnn.readNetFromCaffe("bvlc_googlenet.prototxt", "bvlc_googlenet.caffemodel")
##################################################################
# 读取图像路径
# 		(1)utils_paths.py中的list_images()是提取images文件夹中所有图片的绝对路径,
# 		(2)将所有绝对路径作为元素组成迭代器
# 		(3)使用sorted()进行排序。		这个是字符串之间的排序:先比首字母,再比第二个字母,都相同时比长度。
imagePaths = sorted(list(utils_paths.list_images("images/")))
##################################################################
# (单个)图像预测
image = cv2.imread(imagePaths[0])			# 先读取第0张图片
resized = cv2.resize(image, (224, 224))		# 保持训练模型与测试模型数据大小相同
blob = cv2.dnn.blobFromImage(resized, 1, (224, 224), (104, 117, 123))
print("First Blob: {}".format(blob.shape))

net.setInput(blob)			# 输入数据
preds = net.forward()		# 前向传播得到结果(向量形式)

# 排序,取分类可能性最大的 ———— 该Imagenet是一个千分类模型,它会有1000个值对应1000个分类的概率
# np.argsort()是从小到大排序,故逆序[::-1],然后取第一个值(最大值的索引)
idx = np.argsort(preds[0])[::-1][0]
# 获取要写的内容:(1)该索引对应的标签值(2)pred[0]的值,乘以100,然后保留其两位小数,最后在后面加个百分号
text = "Label: {}, {:.2f}%".format(classes[idx], preds[0][idx] * 100)
# 将要写的内容写在未经处理的图片上
cv2.putText(image, text, (5, 25),  cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
# 显示预测结果
cv2.imshow("Image", image)
cv2.waitKey(0)
##################################################################
##################################################################
# 预测(其余的所有图) ———— 方法与上述一样,但数据是一个batch。
images = []					# 定义一个空列表存图
# 处理除第0张外的所有图片
for p in imagePaths[1:]:
	image = cv2.imread(p)
	image = cv2.resize(image, (224, 224))
	images.append(image)
blob = cv2.dnn.blobFromImages(images, 1, (224, 224), (104, 117, 123))
print("Second Blob: {}".format(blob.shape))

net.setInput(blob)			# 输入数据
preds = net.forward()		# 前向传播得到结果(向量形式)

# 首先读进来图,之后找到对应预测结果中最大的,然后写上标签与概率
for (i, p) in enumerate(imagePaths[1:]):		# i是序号,p是图片路径
	image = cv2.imread(p)
	idx = np.argsort(preds[i])[::-1][0]
	text = "Label: {}, {:.2f}%".format(classes[idx], preds[i][idx] * 100)
	cv2.putText(image, text, (5, 25),  cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
	cv2.imshow("Image", image)
	cv2.waitKey(0)
"""##################################################################
# blob = cv2.dnn.blobFromImage() ———— 改变图像的大小,然后令R,G,B三个通道分别减去均值(去除光照影响)。
# 输出参数:		Blob			例如:(1,3,224,224) 分别表示:图片数量、图片通道数、图像的宽、图像的高
# 输入参数     	resized 		要改变的图像
# 				缩放系数			我们当前用的时1,所以不变
# 				(224,224) 		图像大小
# 				(104,117,123) 	图像三通道均值。这三个均值是Imagenet提供的。
# 预测一个结果:cv2.dnn.blobFromImage()		处理单张图像
# 预测多个结果:cv2.dnn.blobFromImages()		处理多张图像
##################################################################"""

opencv的DNN模块(详细过程)
DNN常用模块简介

(九)矩形涂鸦画板 —— cv.namedWindow()、cv.setMouseCallback()

在这里插入图片描述

"""#########################################################################
# 编写一个矩形涂鸦画板
# 功能:鼠标左键按下拖动绘制矩形,鼠标左键弹起时完成绘制
#       (1)按' c '键清空画板
#       (2)按' ESC '键退出
#########################################################################"""
import numpy as np
import cv2
from random import randint


class Painter:
    def __init__(self) -> None:
        self.mouse_is_pressed = False
        self.last_pos = (-1, -1)
        self.width = 300
        self.height = 512
        self.img = np.zeros((self.width, self.height, 3), np.uint8)
        self.window_name = 'painter'
        self.color = None

    def run(self):
        print('画板,拖动鼠标绘制矩形框,按ESC退出,按c键清空画板')
        cv2.namedWindow(self.window_name)
        cv2.setMouseCallback(self.window_name, lambda event, x, y, flags, param: self.on_draw(event, x, y, flags, param))
        while True:
            cv2.imshow(self.window_name, self.img)
            k = cv2.waitKey(1) & 0xFF
            if k == ord('c'):   # 按' c '键清空画板
                self.clean()                                        # (调用自定义函数):清除画板
            elif k == 27:       # 按' ESC '键退出
                break
        cv2.destroyAllWindows()

    def on_draw(self, event, x, y, flags, param):
        # TODO(You): 请正确实现画板事件响应,完成功能
        # 触发左键按下 -> 触发鼠标移动 -> 开始画矩形 -> 触发左键抬起 -> 终止画矩形
        pos = (x, y)        # 鼠标按下的位置坐标
        if event == cv2.EVENT_LBUTTONDOWN:              # 触发左键按下
            self.mouse_is_pressed = True
            self.last_pos = pos
        elif event == cv2.EVENT_MOUSEMOVE:              # 触发鼠标移动
            if self.mouse_is_pressed == True:           # 判断鼠标是否按下
                self.begin_draw_rectangle(self.last_pos, pos)       # (调用自定义函数):开始画矩形
        elif event == cv2.EVENT_LBUTTONUP:              # 触发左键抬起
            self.end_draw_rectangle(self.last_pos, pos)             # (调用自定义函数):终止画矩形
            self.mouse_is_pressed = False

    def clean(self):
        cv2.rectangle(self.img, (0, 0), (self.height, self.width), (0, 0, 0), -1)

    def begin_draw_rectangle(self, pos1, pos2):
        if self.color is None:      # 设置颜色(每个矩形的颜色都随机)
            self.color = (randint(0, 256), randint(0, 256), randint(0, 256))        # 随机生成三通道颜色
        cv2.rectangle(self.img, pos1, pos2, self.color, -1)

    def end_draw_rectangle(self, pos1, pos2):
        self.color = None


if __name__ == '__main__':
    p = Painter()		# 类的实例化
    p.run()				# 调用类函数

"""#########################################################################
# 11、创建鼠标回调函数:cv2.setMouseCallback(windowName, MouseCallback, param=None)
# 输入参数       windowName:         窗口名称
#               MouseCallback:      鼠标响应回调函数
#               param:              响应函数传递的的参数
#########################################################################
# 22、MouseCallback(int event, int x, int y, int flags,  * userdata)
# 输入参数       x:          鼠标的x坐标
#               y:          鼠标的y坐标
#               userdata:   可选参数
#               event:      一个MouseEventTypes常量
#                                   (1)cv.EVENT_FLAG_LBUTTON= 1,       左键拖拽
#                                   (2)cv.EVENT_FLAG_RBUTTON= 2,       右键拖拽
#                                   (3)cv.EVENT_FLAG_MBUTTON= 4,       中键不放
#                                   (4)cv.EVENT_FLAG_CTRLKEY= 8,       按住ctrl不放
#                                   (5)cv.EVENT_FLAG_SHIFTKEY= 16,     按住shift不放
#                                   (6)cv.EVENT_FLAG_ALTKEY= 32,       按住alt不放
#               flags:      一个MouseEventFlags常量
#                                    (1)cv.EVENT_MOUSEMOVE= 0,       鼠标移动
#                                    (2)cv.EVENT_LBUTTONDOWN= 1,     左键按下
#                                    (3)cv.EVENT_RBUTTONDOWN= 2,     右键按下
#                                    (4)cv.EVENT_MBUTTONDOWN= 3,     中键按下
#                                    (5)cv.EVENT_LBUTTONUP= 4,       左键释放
#                                    (6)cv.EVENT_RBUTTONUP= 5,       右键释放
#                                    (7)cv.EVENT_MBUTTONUP= 6,       中键释放
#                                    (8)cv.EVENT_LBUTTONDBLCLK= 7,   左键双击
#                                    (9)cv.EVENT_RBUTTONDBLCLK= 8,   右键双击
#                                    (10)cv.EVENT_MBUTTONDBLCLK= 9,   中健双击
#                                    (11)cv.EVENT_MOUSEWHEEL= 10,     滚轮滑动
#                                    (12)cv.EVENT_MOUSEHWHEEL= 11     横向滚轮滑动
#########################################################################"""

(十)创建轨迹条 —— createTrackbar()、cv2.getTrackbarPos()

10.1、创建一个轨迹条,用于阈值化图像

在这里插入图片描述

import cv2

img = cv2.imread('1.png')           # 加载图像
cv2.imshow('Image', img)            # 显示图像
cv2.createTrackbar('Threshold', 'Image', 0, 255, lambda x: None)        # 创建阈值滑动条
while True:
    threshold_value = cv2.getTrackbarPos('Threshold', 'Image')          # 获取滑动条的阈值
    threshold_img = cv2.threshold(img, threshold_value, 255, cv2.THRESH_BINARY)[1]      # 阈值化图像
    cv2.imshow('Threshold Image', threshold_img)                                        # 显示图像
    if cv2.waitKey(1) == 27:        # Esc退出
        break
cv2.destroyAllWindows()             # 关闭所有窗口

10.2、创建一个轨迹条,用于画板调色

Python OpenCV 使用滑动条(调色)
python -opencv 使用滑动条(调色画板)
OpenCV学习——实现滑动条式调色板
python -opencv 使用滑动条(图像膨胀)

在这里插入图片描述

"""#########################################################################
# (1)滑动条控制R、G、B的值
# (2)开关按钮switch,用于确认是否使用自定义 RGB 改变原图。
# 		0:恢复原图(全黑)
#       1:画板调色(在画板上显示当前RGB对应的颜色)
#       2: 线条调色(此时,可以绘制线条。且线条颜色对应RGB)
#########################################################################"""
import cv2
import numpy as np


# 定义回调函数,此程序无需回调,所以Pass即可
def nothing(x):
    pass

def Mouseback(event, x, y, flags, param):
    if flags == cv2.EVENT_FLAG_LBUTTON and event == cv2.EVENT_MOUSEMOVE:
        cv2.circle(img, (x, y), 1, [b, g, r], 1)


if __name__ == '__main__':
    img = np.zeros((300, 512, 3), np.uint8)
    cv2.namedWindow('image', cv2.WINDOW_NORMAL)
    cv2.createTrackbar('R', 'image', 0, 255, nothing)
    cv2.createTrackbar('G', 'image', 0, 255, nothing)
    cv2.createTrackbar('B', 'image', 0, 255, nothing)
    switch = 'OFF ON'
    cv2.createTrackbar(switch, 'image', 0, 2, nothing)
    while True:
        cv2.imshow('image', img)
        k = cv2.waitKey(1)
        if k == ord('q'):
            break

        r = cv2.getTrackbarPos('R', 'image')
        g = cv2.getTrackbarPos('G', 'image')
        b = cv2.getTrackbarPos('B', 'image')
        s = cv2.getTrackbarPos(switch, 'image')
        if s == 0:  # 不改变原图
            img[:] = 0
        elif s == 1:  # 调色
            img[:] = [b, g, r]
        elif s == 2:  # 调色画板
            cv2.setMouseCallback('image', Mouseback)
    cv2.destroyAllWindows()
    """#########################################################################
    # 11、创建一个滑动条: cv2.createTrackbar(Track_name, img, min, max, TrackbarCallback)
    # 输入参数:
    #         Track_name:		滑动条的名字。
    #         img:				滑动条所在画布。
    #         min:				滑动条的最小值。
    #         max:				滑动条的最大值。
    #         TrackbarCallback:滑动条的回调函数。
    
    # 22、获取滑动条的值: value = cv2.getTrackbarPos(Track_name, img)
    # 输入参数:
    #         Track_name:		滑动条的名字。
    #         img:				滑动条所在画布。
    # 输出参数:
    #         滑动条当前所在的位置(值)。
    #########################################################################"""

(十一)人像抠图 + 背景替换 —— np.all、np.expand_dims、np.where()

图像处理实战–Opencv实现人像迁移

在这里插入图片描述

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


def composite_images(foreground_path, background_path, background_color=[255, 255, 255]):
    # 读取前景图像和背景图像
    q_img = cv2.imread(foreground_path)
    b_img = cv2.imread(background_path)

    # 调整前景图像大小以匹配背景图像尺寸
    q_img = cv2.resize(q_img, (b_img.shape[1], b_img.shape[0]))

    # 将前景图像转换为二进制掩模
    binary_mask = np.all(q_img == background_color, axis=-1)  # 用于判断数组中的所有元素是否满足某个条件。
    binary_mask = np.expand_dims(binary_mask, axis=-1)  # 用于在数组中插入新的维度。

    # 执行图像合成
    composited_img = np.where(binary_mask, b_img, q_img)  # 用于根据指定条件返回输入数组中满足条件的元素的索引或值。

    # 显示图像
    titles = ['前景图像', '背景图像', '二进制掩模', '合成图像']
    images = [q_img, b_img, binary_mask.astype(np.uint8) * 255, composited_img]

    # 设置中文显示
    plt.rcParams['font.sans-serif'] = ['SimHei']  # 设置字体为SimHei
    plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题

    for i in range(len(titles)):
        plt.subplot(2, 2, i + 1)
        plt.imshow(cv2.cvtColor(images[i], cv2.COLOR_BGR2RGB))
        plt.title(titles[i])
        plt.axis('off')
    plt.show()


if __name__ == '__main__':
    composite_images('1.png', '2.png', background_color=[255, 255, 255])

22、图像处理

(一)图像的读取、保存和显示 —— cv2.imread() + cv2.imwrite() + cv2.imshow()

(1)(在同一个窗口)同时显示多张图 plt.subplot()
(2)cv2.imshow()和plt.imshow()的区别
在这里插入图片描述

"""#####################################################################
# cv2是opencv在python中的缩写; opencv读取图像的格式BGR(图像的格式RGB)
#
# Matplotlib 是一个 Python 库,可以通过 python 脚本创建二维图形和图表。
# Matplotlib 中的 pyplot 模块,可以控制线条样式,字体属性,格式化轴等功能。且支持各种各样的图形绘制,如直方图,条形图,功率谱,误差图等。
#####################################################################"""
import cv2                          # opencv    读取图像默认为BGR
import matplotlib.pyplot as plt     # matplotlib显示图像默认为RGB


image_path = r'F:\py\image.jpg'
img0 = cv2.imread(image_path)
img1 = cv2.imread(image_path, cv2.IMREAD_COLOR)
img2 = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
img3 = cv2.imread(image_path, cv2.IMREAD_UNCHANGED)

cv2.imwrite('image_gray.png', img2)

cv2.imshow('image_raw', img0)
cv2.imshow('cv2.IMREAD_COLOR', img1)
cv2.imshow('cv2.IMREAD_GRAYSCALE', img2)
cv2.imshow('cv2.IMREAD_UNCHANGED', img3)
cv2.waitKey(1000)                  	# 延迟一秒后自动关闭图像
cv2.destroyAllWindows()         	# (同时)关闭所有图窗


plt.subplot(141),   plt.imshow(img0, 'gray'),     plt.title('image_raw')
plt.subplot(142),   plt.imshow(img1, 'gray'),     plt.title('cv2.IMREAD_COLOR')
plt.subplot(143),   plt.imshow(img2, 'gray'),     plt.title('cv2.IMREAD_GRAYSCALE')
plt.subplot(144),   plt.imshow(img3, 'gray'),     plt.title('cv2.IMREAD_UNCHANGED')
plt.show()

plt.imshow(img0)
plt.colorbar()
plt.show()

"""#####################################################################################################################
# 函数功能:用于读取图像文件,并将其加载为一个 Numpy 数组。———— 支持读取多种图像格式,如 JPEG、PNG、BMP 等。(默认读取的格式是BGR)(图像的格式是RGB)
# 函数说明:image = cv2.imread(filename, flags=cv2.IMREAD_COLOR)
# 参数说明:
#         filename:   要读取的图像文件的路径(若路径错误,则返回None,但不报错)
#         flags:      读取图像的方式
#                             cv2.IMREAD_COLOR(也可以传入1):     以彩色方式加载图像,忽略所有透明度信息(默认)。
#                             cv2.IMREAD_GRAYSCALE(也可以传入0): 以灰度方式加载图像。
#                             cv2.IMREAD_UNCHANGED(也可以传入-1):以原始图像加载图像,包括图像的 alpha 通道信息。
# 返回参数:
#         返回一个 Numpy 数组 image,表示读取的图像。
# 
# 备注:路径中不能出现中文,否则系统异常提示
# 		cat_path = r'F:\py\image.jpg'           # 绝对路径
# 		cat_path = r'image.jpg'					# 相对路径(即当前同级目录):不指定路径,则默认当前.py文件的路径下。
# 		cat_path = r'./image.jpg'				# 相对路径(即当前同级目录):与上同效。
# 		cat_path = r'../py/image.jpg'		    # 相对路径(上级目录):表示图像存放在当前.py文件的py文件夹
# 
# 备注:转义字符\可以转义很多字符,比如:'\n'表示换行,'\t'表示制表符,'\\'表示\。当然如果不需要转义,可以使用(r'cat.hpg');
#####################################################################################################################"""

"""#####################################################################################################################
# 函数功能:用于将图像保存到文件中。
# 函数说明:cv2.imwrite(filename, img, params=None)
# 参数说明:
#         filename: 要保存的目标文件的路径。
#         img:      要保存的图像数据,可以是 Numpy 数组。
#         params:   可选参数,用于指定保存图像的额外参数,例如 JPEG 图像的压缩质量。
# 返回参数:
#         如果成功保存图像,则返回 True;否则返回 False。
#####################################################################################################################"""

"""#####################################################################################################################
# 函数功能:用于显示图像
# 函数说明:cv2.imshow(winname, image)
# 参数说明:
#         winname:  窗口的名称,用于标识窗口。
#         image:    要显示的图像,可以是灰度图像或彩色图像,表示为一个 Numpy 数组。
#
# 备注:在显示图像之后,一般会调用 cv2.waitKey() 函数来等待用户按下键盘,否则窗口可能会立即关闭。
# 备注:窗口会自适应图像大小
# 备注:指定多个窗口名称,可以显示多幅图像
# 备注:显示多幅图像时,若cv2.imshow()指定相同的窗口名,则后一个图像会覆盖前一个图像,从而只产生一个(连续)窗口。如:视频
#####################################################################################################################"""

"""#####################################################################################################################
# 函数功能:用于等待键盘按键事件
# 函数说明:key = cv2.waitKey(delay)
# 参数说明:
#       delay:      等待的毫秒数,默认为 0。
#                         (1)cv2.waitKey(0):       表示无限期的等待键盘输入,按任意键继续。如:空格键
#                         (2)cv2.waitKey(delay):   当 delay>0 (单位:ms)时使用,表示等待一定时间。1秒(s) = 1000毫秒
# 返回参数:
#       如果有键按下,返回按键的 ASCII 码值(或 Unicode 码值),否则返回 -1。
#####################################################################################################################"""

"""#####################################################################################################################
# (在同一个窗口)同时显示多张图
#           plt.subplot(231)或plt.subplot(2,3,1)     # 该图指定了(row)2*3(col)的子图区域,并且同一个坐标轴1,2,3,4,5,6内分别画图。
#           plt.plot()                              # 直接在一张大的画布中画图,相当于获取当前活跃的axes然后在上面作图。
# 备注1:在jupyter notebook上,单是plt.imshow()就可显示图片,同时也显示其格式。
# 备注2:在pycharm上,单是plt.imshow()不显示图像,需搭配plt.show()。而加上plt.show()后,结果:仅显示图片,不显示格式。
#####################################################################################################################"""

"""#####################################################################################################################
# cv2.imshow()和plt.imshow()的区别
# 			cv2.imshow():常用于对读入图像进行一系列图像处理后的绘图;
# 			plt.imshow():常用于绘制热图; 热图即通过色差、亮度来展示数据的差异。
# 备注:两者都可以,但要注意图片的格式是RGB格式,而opencv是BGR格式,plt是RGB格式;
#####################################################################################################################"""

(1.1)图窗设置:cv2.namedWindow() + cv2.resizeWindow() + cv2.moveWindow() + cv2.setWindowProperty()

import cv2

window_name = 'projector'			# 图窗标题
cv2.namedWindow(window_name, cv2.WINDOW_KEEPRATIO)				# 创建命名窗口
cv2.resizeWindow(window_name, 10, 20)							# 自定义窗口大小
cv2.moveWindow(window_name, 100, 200)							# 设置窗口的位置
cv2.setWindowProperty(window_name, cv2.WND_PROP_TOPMOST, 1)		# 设置窗口显示在最前面

im = cv2.imread('test01.png')		# 读取图像
cv2.imshow(window_name, im)			# 显示图像
cv2.waitKey(0)						# 等待输入任意键
cv2.destroyAllWindows()				# 摧毁所有图窗
参数cv2.namedWindow(winname, flags)创建命名窗口
1winname窗口名称,用作窗口的标识符。
2flags窗口属性设置标志。
flags=cv2.WINDOW_NORMAL用户可以手动改变窗口大小
flags=cv2.WINDOW_AUTOSIZE窗口大小自动适应图片大小,并且不可手动更改。
flags=cv2.WINDOW_FREERATIO自适应比例
flags=cv2.WINDOW_KEEPRATIO保持比例
flags=cv2.WINDOW_OPENGL窗口创建的时候会支持OpenGL
flags=cv2.WINDOW_GUI_EXPANEDE创建的窗口允许添加工具栏和状态栏。
flags=cv2.WINDOW_GUI_NORMAL创建没有状态栏和工具栏窗口。
flags=cv2.WINDOW_AUTOSIZE窗口大小自动适应图片大小,并且不可手动更改。
参数cv2.resizeWindow(winname, width, height)改变窗口大小
1winname窗口名
2width窗口宽度
3height窗口高度
参数cv2.moveWindow(winname, x, y)设置窗口位置
1winname窗口名
2x窗口x轴位置
3y窗口y轴位置
参数cv2.setWindowProperty(winname, prop_id, prop_value)设置窗口属性
1winname窗口名
2prop_id要编辑的窗口属性。如cv2.WINDOW_NORMAL、cv2.WINDOW_KEEPRATIO、cv2.WINDOW_FULLSCREEN等。
3prop_value窗口属性的新值。如cv2.WND_PROP_FULLSCREEN, cv2.WND_PROP_AUTOSIZE, cv2.WND_PROP_ASPECT_RATIO等。

(1.2)图窗关闭:cv2.waitKey() + cv2.destroyAllWindows()

参数cv2.waitKey()键盘绑定函数
1cv2.waitKey(0)表示无限期的等待键盘输入,按任意键继续。如:空格键
2cv2.waitKey(delay)当 delay>0 (单位:ms)时使用,表示等待一定时间。1秒(s) = 1000毫秒
参数cv2.destroyAllwindows()销毁窗口
1cv2.destroyAllwindows()摧毁所有窗口
2cv2.destroyWindow(winname)摧毁指定的窗口

(二)视频读取与处理 —— cv2.VideoCapture()

读取视频 + 检查视频是否可以打开 + 循环读取视频的每一帧(图像)并显示
在这里插入图片描述

import cv2

# (1)视频读取与处理 —— 读取视频
vc = cv2.VideoCapture(r'picture\test.mp4')

# (2)视频读取与处理 —— 检查视频是否可以打开
if vc.isOpened():
    open, frame = vc.read()
else:
    open = False
    
# (3)视频读取与处理 —— 循环读取视频的每一帧(图像)
while open:
    ret, frame = vc.read()
    # 如果读到的帧数不为空,那么就继续读取,如果为空,就退出
    if frame is None:
        break
    if ret == True:
        gray = cv2.cvtColor(frame,  cv2.COLOR_BGR2GRAY)    # 将读取到视频的每一帧(图像)转换成灰度图
        cv2.imshow('result', gray)
        # 使用 waitKey 可以控制视频的播放速度,数值越小,播放速度越快
        if cv2.waitKey(10) & 0xFF == 27:    # 27 表示退出键(Esc)
            break
vc.release()
cv2.destroyAllWindows()

"""###################################################################
# 11、cv2.VideoCapture可以捕获摄像头,用数字来控制不同的设备。
# 		0:表示调用电脑自带摄像头。
# 		1:表示调用外接USB摄像头。

# 22、vc.isOpened():    检查视频是否可以打开(返回值:True/False)

# 33、vc.read():        读取视频的每一帧(图像)(返回值:True/False,图像彩色图)

# 44、cv2.imshow('frame',frame)将每一帧(图像)显示在一个叫frame的窗口上。
#	 	为什么会产生视频的效果:通过while循环,将图像固定显示在'frame'图窗上,每一帧会覆盖上一帧,就产生了视频的效果。
###################################################################"""

OpenCV 保存视频

(三)图像的三色图 —— cv2.split() + cv.merge()

(1)图像分割得到三色图(BGR)
(2)将分割的三色图还原为彩色图
(3)保留R通道 + 保留G通道 + 保留B通道
在这里插入图片描述

import cv2                          # opencv    读取图像默认为BGR
import matplotlib.pyplot as plt     # matplotlib显示图像默认为RGB

image = cv2.imread(r'F:\py\image.jpg')
b, g, r = cv2.split(image)  # 图像分割
image = cv2.merge((b, g, r))  # 图像合并

################################################
# 只保留红色(R)单通道(G and B通道全部置0即可)
cur_img_R = image.copy()  # 复制原图,避免改变原图
cur_img_R[:, :, 0] = 0
cur_img_R[:, :, 1] = 0

# 只保留绿色(G)单通道(R and B通道全部置0即可)
cur_img_G = image.copy()  # 复制原图,避免改变原图
cur_img_G[:, :, 0] = 0
cur_img_G[:, :, 2] = 0

# 只保留蓝色(B)单通道(R and G通道全部置0即可)
cur_img_B = image.copy()  # 复制原图,避免改变原图
cur_img_B[:, :, 1] = 0
cur_img_B[:, :, 2] = 0

################################################
plt.subplot(131), plt.imshow(cur_img_B), plt.title('Red')
plt.subplot(132), plt.imshow(cur_img_G), plt.title('Green')
plt.subplot(133), plt.imshow(cur_img_R), plt.title('Blue')
plt.show()

"""#######################################################################
# 函数功能:用于将多通道图像分离成单通道图像。 ———— 分割后的单通道图像都为灰度图且大小相同。
# 函数说明:b, g, r = channels = cv2.split(src)
# 参数说明:
#       src:         输入的多通道图像。
# 返回参数:
#       channels:    包含各个通道单独图像的列表。列表的长度等于输入图像的通道数。
#######################################################################"""

"""#######################################################################
# 函数功能:用于将单通道图像合并成多通道图像。 ———— 合并后的通道数是所有输入图像通道数的总和。
# 函数说明:dst = cv2.merge(channels)
# 参数说明:
#       channels:   包含单通道图像的列表,列表的长度应该等于图像的通道数。
# 返回参数:
#       dst:        合并后的多通道图像。

# 注意:输入图像的通道数可以不相同,但必须具有相同大小和数据类型
#######################################################################"""

(四)图像的边缘填充 —— cv2.copyMakeBorder()

在这里插入图片描述

import cv2                          # opencv    读取图像默认为BGR
import matplotlib.pyplot as plt     # matplotlib显示图像默认为RGB

image = cv2.imread(r'image.jpg')
top_size, bottom_size, left_size, right_size = (50, 50, 50, 50)  # 指定边缘(上下左右)需要填充的宽度
replicate  = cv2.copyMakeBorder(image, top_size, bottom_size, left_size, right_size, borderType=cv2.BORDER_REPLICATE)
reflect    = cv2.copyMakeBorder(image, top_size, bottom_size, left_size, right_size, borderType=cv2.BORDER_REFLECT)
reflect101 = cv2.copyMakeBorder(image, top_size, bottom_size, left_size, right_size, borderType=cv2.BORDER_REFLECT_101)
wrap       = cv2.copyMakeBorder(image, top_size, bottom_size, left_size, right_size, borderType=cv2.BORDER_WRAP)
constant   = cv2.copyMakeBorder(image, top_size, bottom_size, left_size, right_size, borderType=cv2.BORDER_CONSTANT, value=0)

plt.subplot(231),   plt.imshow(image, 'gray'),        plt.title('image_raw')
plt.subplot(232),   plt.imshow(replicate, 'gray'),    plt.title('BORDER_REPLICATE')
plt.subplot(233),   plt.imshow(reflect, 'gray'),      plt.title('BORDER_REFLECT')
plt.subplot(234),   plt.imshow(reflect101, 'gray'),   plt.title('BORDER_REFLECT_101')
plt.subplot(235),   plt.imshow(wrap, 'gray'),         plt.title('BORDER_WRAP')
plt.subplot(236),   plt.imshow(constant, 'gray'),     plt.title('BORDER_CONSTANT')
plt.show()

"""#####################################################################################################################
# 函数功能:用于在图像的边界周围添加边框。———— 可以在图像的四个边界上分别添加不同大小和类型的边框
# 函数说明:dst = cv2.copyMakeBorder(src, top, bottom, left, right, borderType, value=None)
# 参数说明:
#         src:      输入的图像。
#         top:      顶部边框的大小(以像素为单位)。
#         bottom:   底部边框的大小(以像素为单位)。
#         left:     左侧边框的大小(以像素为单位)。
#         right:    右侧边框的大小(以像素为单位)。
#         borderType:边框类型
#                         cv2.BORDER_CONSTANT:常数边框,使用 value 参数指定填充值。
#                         cv2.BORDER_REPLICATE:复制边框。
#                         cv2.BORDER_REFLECT:反射边框。
#                         cv2.BORDER_WRAP:包裹边框。
#         value:    可选参数,用于指定常数边框的填充值。
#####################################################################################################################"""

(五)图像融合 —— cv2.addWeighted()

(1)图像截取
(2)图像加 / 减:常数
(3)图像加 / 减:另一个图像
(4)图像相加:cv2.add()
在这里插入图片描述

import cv2                          # opencv    读取图像默认为BGR
import matplotlib.pyplot as plt     # matplotlib显示图像默认为RGB

img_cat = cv2.imread(r'picture\cat.jpg')
img_dog = cv2.imread(r'picture\dog.jpg')
print(img_cat.shape)
print(img_dog.shape)
########################################
# 图像截取
cat_piece = img_cat[100:300, 0:200]
########################################
# 图像加/减一个常数 ———— 即图像的每个像素都加常数值
img_plus = img_cat - 50
########################################
# 图像相加/相减 ———— 两个图像的尺寸要一样。
# 方式1:在numpy中,如果两个图像相加超过255,将自动减去255,然后得到剩下的值。相当于取余【% 256】
# 方式2:cv2.add():如果两个图像相加超过255,则等于255,否则当前值保留不变;
img_cat = cv2.resize(img_cat, (400, 480))
img_dog = cv2.resize(img_dog, (400, 480))
print(img_cat.shape)
print(img_dog.shape)

res_add1 = img_cat + img_dog
res_add2 = cv2.add(img_cat, img_dog)
########################################
plt.subplot(231),       plt.imshow(img_cat, 'gray'),        plt.title('img_cat_resize')
plt.subplot(232),       plt.imshow(img_dog, 'gray'),        plt.title('img_dog_resize')
plt.subplot(233),       plt.imshow(cat_piece, 'gray'),      plt.title('cat_piece')
plt.subplot(234),       plt.imshow(img_plus, 'gray'),       plt.title('fig1 - 50')
plt.subplot(235),       plt.imshow(res_add1, 'gray'),       plt.title('fig1 + fig2')
plt.subplot(236),       plt.imshow(res_add2, 'gray'),       plt.title('cv2.add')
plt.show()

"""#####################################################################
# 图像融合:cv2.addWeighted(src1, alpha, src2, beta, gamma)
# 功能:将两张相同shape的图像按权重进行融合
# 输入参数         src1/src2        图像1与图像2
#                  alpha/beta       图像1与图像2对应的权重(融合后的图像偏向于权重高的一边)
#                  gamma            相当于(y=a*x+b)中的截距。用于调节亮度
# 权重融合公式:dst = src1 * alpha + src2 * beta + gamma
#####################################################################"""
img_cat = cv2.resize(img_cat, (500, 414))       # 将图像裁剪到指定尺寸
img_dog = cv2.resize(img_dog, (500, 414))       # 将图像裁剪到指定尺寸
res = cv2.addWeighted(img_cat, 0.35, img_dog, 0.65, 2)
plt.imshow(res)
plt.show()

(六)颜色空间转换 —— cv2.cvtColor()

在这里插入图片描述

import cv2                          # opencv    读取图像默认为BGR
import matplotlib.pyplot as plt     # matplotlib显示图像默认为RGB

"""######################################################################################
# 函数功能:用于在不同的颜色空间之间进行转换。
# 函数说明:dst = cv2.cvtColor(src, code, dstCn=None)
# 参数说明:
#         src:  输入图像。
#         code: 转换码,用于指定要执行的转换类型。
#                         cv2.COLOR_BGR2GRAY:将彩色图像转换为灰度图像。
#                         cv2.COLOR_GRAY2BGR:将灰度图像转换为彩色图像(使用灰度值复制通道)。
#                         cv2.COLOR_BGR2RGB:将 BGR 彩色图像转换为 RGB 彩色图像。
#                         cv2.COLOR_RGB2BGR:将 RGB 彩色图像转换为 BGR 彩色图像。
#                         其他转换码请参考 OpenCV 文档。
#         dstCn:可选参数,用于指定输出图像的通道数。
######################################################################################"""
img_BGR = cv2.imread(r'F:\py\image.jpg')  # BGR

img_RGB     = cv2.cvtColor(img_BGR, cv2.COLOR_BGR2RGB)
img_GRAY    = cv2.cvtColor(img_BGR, cv2.COLOR_BGR2GRAY)
img_HSV     = cv2.cvtColor(img_BGR, cv2.COLOR_BGR2HSV)
img_YcrCb   = cv2.cvtColor(img_BGR, cv2.COLOR_BGR2YCrCb)
img_HLS     = cv2.cvtColor(img_BGR, cv2.COLOR_BGR2HLS)
img_XYZ     = cv2.cvtColor(img_BGR, cv2.COLOR_BGR2XYZ)
img_LAB     = cv2.cvtColor(img_BGR, cv2.COLOR_BGR2LAB)
img_YUV     = cv2.cvtColor(img_BGR, cv2.COLOR_BGR2YUV)

plt.subplot(3, 3, 1),    plt.imshow(img_BGR),   plt.axis('off'),      plt.title('BGR')
plt.subplot(3, 3, 2),    plt.imshow(img_RGB),   plt.axis('off'),      plt.title('RGB')
plt.subplot(3, 3, 3),    plt.imshow(img_GRAY),  plt.axis('off'),      plt.title('GRAY')
plt.subplot(3, 3, 4),    plt.imshow(img_HSV),   plt.axis('off'),      plt.title('HSV')
plt.subplot(3, 3, 5),    plt.imshow(img_YcrCb), plt.axis('off'),      plt.title('YcrCb')
plt.subplot(3, 3, 6),    plt.imshow(img_HLS),   plt.axis('off'),      plt.title('HLS')
plt.subplot(3, 3, 7),    plt.imshow(img_XYZ),   plt.axis('off'),      plt.title('XYZ')
plt.subplot(3, 3, 8),    plt.imshow(img_LAB),   plt.axis('off'),      plt.title('LAB')
plt.subplot(3, 3, 9),    plt.imshow(img_YUV),   plt.axis('off'),      plt.title('YUV')
plt.show()

(七)阈值处理 —— cv2.threshold() + cv2.adaptiveThreshold()

(1)全局阈值分割:cv2.threshold()
在这里插入图片描述
(2)自适应阈值分割:cv2.adaptiveThreshold()
在这里插入图片描述

import cv2                          # opencv    读取图像默认为BGR
import matplotlib.pyplot as plt     # matplotlib显示图像默认为RGB


img_BGR = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE)
_, thresh1 = cv2.threshold(img_BGR, 127, 255, cv2.THRESH_BINARY)
_, thresh2 = cv2.threshold(img_BGR, 127, 255, cv2.THRESH_BINARY_INV)
_, thresh3 = cv2.threshold(img_BGR, 127, 255, cv2.THRESH_TRUNC)
_, thresh4 = cv2.threshold(img_BGR, 127, 255, cv2.THRESH_TOZERO)
_, thresh5 = cv2.threshold(img_BGR, 127, 255, cv2.THRESH_TOZERO_INV)

titles = ['Original Image', 'BINARY', 'BINARY_INV', 'TRUNC', 'TOZERO', 'TOZERO_INV']
images = [img_BGR, thresh1, thresh2, thresh3, thresh4, thresh5]

for ii in range(6):
    plt.subplot(2, 3, ii + 1), plt.imshow(images[ii], 'gray')
    plt.title(titles[ii])
    plt.xticks([]), plt.yticks([])
plt.show()
"""#############################################################
# 函数功能:全局阈值分割 ———— 根据阈值将图像的像素分为两个类别:高于阈值的像素和低于阈值的像素。
# 函数说明:ret, dst = cv2.threshold(src, thresh, max_val, type)
# 输入参数:    	
#         src:   	    输入灰度图像
#         thresh: 	    阈值
#         max_val:    	阈值上限。通常为255(8-bit)。
#         type:   	    二值化操作的类型,包含以下5种类型:
#                               (1) cv2.THRESH_BINARY             超过阈值部分取max_val(最大值),否则取0
#                               (2) cv2.THRESH_BINARY_INV         THRESH_BINARY的反转
#                               (3) cv2.THRESH_TRUNC              大于阈值部分设为阈值,否则不变
#                               (4) cv2.THRESH_TOZERO             大于阈值部分不改变,否则设为0
#                               (5) cv2.THRESH_TOZERO_INV         THRESH_TOZERO的反转
# 输出参数:     
#         ret           浮点数,表示最终使用的阈值。
#         dst   	    经过阈值分割操作后的二值图像
#############################################################"""

image = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE)
thresh1 = cv2.adaptiveThreshold(image, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 11, 2)
thresh2 = cv2.adaptiveThreshold(image, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV, 11, 2)
thresh3 = cv2.adaptiveThreshold(image, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2)
thresh4 = cv2.adaptiveThreshold(image, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2)

titles = ['gray Image', 'mean + binary', 'mean + binary_inv', 'gray Image', 'gaussian + binary', 'gaussian + binary_inv']
images = [image, thresh1, thresh2, image, thresh3, thresh4]
for ii in range(6):
    plt.subplot(2, 3, ii + 1), plt.imshow(images[ii], 'gray')
    plt.title(titles[ii])
    plt.xticks([]), plt.yticks([])
plt.show()

"""##########################################################################
# 函数功能:自适应阈值分割 ———— 根据图像不同区域的像素值来自动确定阈值,从而实现更好的图像分割效果。
# 函数说明:adaptive_threshold = cv2.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C)
# 输入参数:
#         src:                  输入灰度图像。
#         maxValue:             阈值上限。通常为255(8-bit)。
#         adaptiveMethod:       自适应方法,包含以下2种:
#                                       (1)cv2.ADAPTIVE_THRESH_MEAN_C(局部均值)
#                                       (2)cv2.ADAPTIVE_THRESH_GAUSSIAN_C(局部高斯加权均值)
#         thresholdType:        阈值类型。
#                                       (1)cv2.THRESH_BINARY        超过阈值部分取max_val(最大值),否则取0
#                                       (2)cv2.THRESH_BINARY_INV    THRESH_BINARY的反转
#         blockSize:            区域大小,用于计算局部阈值。通常是一个奇数,例如 3、5、7 等。
#         C:                    从均值中减去的常数,用于调整阈值的灵敏度。
# 输出参数:
#         adaptive_threshold    自适应阈值
##########################################################################"""

(八)均值/方框/高斯/中值滤波 —— cv2.blur() + cv2.boxFilter() + cv2.GaussianBlur() + cv2.medianBlur()

在这里插入图片描述

import cv2                          # opencv    读取图像默认为BGR
import matplotlib.pyplot as plt     # matplotlib显示图像默认为RGB
import numpy as np

image = cv2.imread(r'image.jpg')

image_box_T   = cv2.boxFilter(src=image, ddepth=-1, ksize=(3, 3), normalize=True)
image_box_F   = cv2.boxFilter(src=image, ddepth=-1, ksize=(3, 3), normalize=False)
image_blur    = cv2.blur(src=image, ksize=(3, 3))
image_gaussian = cv2.GaussianBlur(image, (5, 5), 1)
image_median  = cv2.medianBlur(image, 5)

plt.subplot(2, 3, 1),    plt.imshow(image),            plt.title('image_raw')
plt.subplot(2, 3, 2),    plt.imshow(image_box_T),      plt.title('image_box_T')
plt.subplot(2, 3, 3),    plt.imshow(image_box_F),      plt.title('image_box_F')
plt.subplot(2, 3, 4),    plt.imshow(image_blur),       plt.title('image_blur')
plt.subplot(2, 3, 5),    plt.imshow(image_gaussian),   plt.title('image_gaussian')
plt.subplot(2, 3, 6),    plt.imshow(image_median),     plt.title('image_median')
plt.show()

# 均值滤波与方框滤波的区别在于滤波核的计算方式和是否进行归一化处理。

"""######################################################################################
# 函数功能:用于对图像进行方框滤波。 ———— 方框滤波是一种线性滤波技术,它用一个固定大小的方框内的像素值的平均值来替代中心像素的值。
# 函数说明:filtered_image = cv2.boxFilter(src, ddepth, ksize, normalize=True)
# 参数说明:
#         src:      输入的图像,数据类型为 uint8 或 float32。
#         ddepth:   输出图像的深度,通常为 -1 表示与输入图像相同。
#         ksize:    滤波核的大小,表示每个方向的滤波器窗口的大小。通常为一个二元组 (width, height)。
#         normalize:一个布尔值,用于指定是否应该对结果进行归一化。如果设置为 True,则对结果进行归一化。
#
# 均值滤波与方框滤波的区别:在于滤波核的计算方式和是否进行归一化处理。
######################################################################################"""

"""######################################################################################
# 函数功能:用于对图像进行均值滤波。 ———— 将每个像素的值替换为其邻域像素值的平均值。
# 函数说明:blurred_image = cv2.blur(src, ksize)
# 参数说明:
#         src:      输入的图像。
#         ksize:    滤波核的大小,表示每个方向的滤波器窗口的大小。通常为一个二元组 (width, height)。
#                       若ksize 的宽度和高度都是奇数,则使用归一化的均值滤波核。
#                       若其中一个是偶数,           则生成的核不会归一化,且会产生较少的模糊效果。
#
# 作用:对于椒盐噪声的滤除效果比较好。
######################################################################################"""

"""######################################################################################
# 函数功能:用于对图像进行高斯滤波。 ———— 高斯滤波是一种常见的图像平滑滤波技术,它使用高斯函数对图像进行卷积操作,以降低图像中的噪声并平滑图像。
# 函数说明:blurred_image = cv2.GaussianBlur(src, ksize, sigmaX, sigmaY=0, borderType=cv2.BORDER_DEFAULT)
# 参数说明:
#         src:      输入的图像。
#         ksize:    高斯核的大小,通常为一个二元组 (width, height)。它表示高斯核的宽度和高度,必须为奇数。如果设置为 (0, 0),则根据 sigmaX 和 sigmaY 自动计算核的大小。
#         sigmaX:   X 方向的高斯核标准差。
#         sigmaY:   Y 方向的高斯核标准差。如果设置为 0,则默认与 sigmaX 相同。
#         borderType:可选参数,表示边界填充方式。默认值为 cv2.BORDER_DEFAULT。
#
# 作用:去噪。
######################################################################################"""

"""######################################################################################
# 函数功能:用于对图像进行中值滤波 ———— 中值滤波是一种非线性滤波技术,它将每个像素的值替换为其邻域像素值的中值。
# 函数说明:blurred_image = cv2.medianBlur(src, ksize)
# 参数说明:
#         src:      输入的图像。
#         ksize:    滤波核的大小,通常为一个奇数。它表示每个方向的滤波器窗口的大小。
#
# 作用:用于去除图像中的椒盐噪声等随机噪声。
######################################################################################"""

res = np.hstack((image_blur, image_aussian, image_median))
cv2.imshow('median - Gaussian - average', res)
cv2.waitKey(0)
cv2.destroyAllWindows()

res = np.vstack((image_blur, image_aussian, image_median))
cv2.imshow('median - Gaussian - average', res)
cv2.waitKey(0)
cv2.destroyAllWindows()
"""###################################################
# np.hstack(img1, img2, img3)     在水平方向上,平铺图像
# np.vstack(img1, img2, img3)     在竖直方向上,堆叠图像
###################################################"""

(九)腐蚀与膨胀 —— cv2.erode() + cv2.dilate()

在这里插入图片描述

import numpy as np
import cv2                          # opencv    读取图像默认为BGR
import matplotlib.pyplot as plt     # matplotlib显示图像默认为RGB

"""####################################################################################################################
# 函数功能:腐蚀操作 ———— 腐蚀是一种图像形态学操作,它通过移动滑动结构元素(也称为内核)在图像上,将内核覆盖区域的像素值设置为该区域内的最小像素值,从而使图像中的前景对象缩小或被消除。
# 函数说明:eroded_image = cv2.erode(src, kernel, iterations=1, borderType=cv2.BORDER_CONSTANT, borderValue=0)
# 参数说明:
#         src:          输入的图像,通常为二值图像(黑白图像)。
#         kernel:       腐蚀操作的内核,决定了腐蚀的效果。可以使用 cv2.getStructuringElement() 函数创建一个结构元素。
#         iterations:   可选参数,表示腐蚀操作的迭代次数。默认值为 1。
#         borderType:   可选参数,表示边界填充方式。默认值为 cv2.BORDER_CONSTANT。
#         borderValue:  可选参数,表示边界填充的像素值。默认值为 0。
####################################################################################################################"""

"""####################################################################################################################
# 函数功能:膨胀操作 ———— 膨胀是一种图像形态学操作,它通过移动滑动结构元素(也称为内核)在图像上,将内核覆盖区域的像素值设置为该区域内的最大像素值,从而使图像中的前景对象增大或扩展。
# 函数说明:dilated_image = cv2.dilate(src, kernel, iterations=1, borderType=cv2.BORDER_CONSTANT, borderValue=0)
# 参数说明:
#         src:          输入的图像,通常为二值图像(黑白图像)。
#         kernel:       膨胀操作的内核,决定了膨胀的效果。可以使用 cv2.getStructuringElement() 函数创建一个结构元素。
#         iterations:   可选参数,表示膨胀操作的迭代次数。默认值为 1。
#         borderType:   可选参数,表示边界填充方式。默认值为 cv2.BORDER_CONSTANT。
#         borderValue:  可选参数,表示边界填充的像素值。默认值为 0。
####################################################################################################################"""

image = cv2.imread(r'Screenshot_1.png')
kernel = np.ones((3, 3), np.uint8)  # 初始化卷积核(np.ones: 生成一个数值全为1的3x3数组)

dilate_1 = cv2.dilate(image, kernel, iterations=1)  # 膨胀(迭代次数1次)
dilate_2 = cv2.dilate(image, kernel, iterations=5)  # 膨胀
dilate_3 = cv2.dilate(image, kernel, iterations=10)  # 膨胀

erosion_1 = cv2.erode(image, kernel, iterations=1)  # 腐蚀(迭代次数1次)
erosion_2 = cv2.erode(image, kernel, iterations=5)  # 腐蚀
erosion_3 = cv2.erode(image, kernel, iterations=10)  # 腐蚀

plt.subplot(2, 4, 1),    plt.imshow(image),      plt.title('image')
plt.subplot(2, 4, 2),    plt.imshow(dilate_1),       plt.title('erode-1')
plt.subplot(2, 4, 3),    plt.imshow(dilate_2),       plt.title('erode-2')
plt.subplot(2, 4, 4),    plt.imshow(dilate_3),       plt.title('erode-3')

plt.subplot(2, 4, 5),    plt.imshow(image),      plt.title('image')
plt.subplot(2, 4, 6),    plt.imshow(erosion_1),      plt.title('dilate-1')
plt.subplot(2, 4, 7),    plt.imshow(erosion_2),      plt.title('dilate-2')
plt.subplot(2, 4, 8),    plt.imshow(erosion_3),      plt.title('dilate-3')
plt.show()

(十)形态学变化 —— cv2.morphologyEx():开运算 + 闭运算 + 梯度计算 + 顶帽 + 黑帽

在这里插入图片描述

import numpy as np
import cv2                          # opencv    读取图像默认为BGR
import matplotlib.pyplot as plt     # matplotlib显示图像默认为RGB

image = cv2.imread(r'Screenshot_1.png')
kernel = np.ones((5, 5), np.uint8)  # 初始化卷积核(np.ones: 生成一个数值全为1的3x3数组)

image_open  = cv2.morphologyEx(image, cv2.MORPH_OPEN, kernel)       # 开运算
image_close = cv2.morphologyEx(image, cv2.MORPH_CLOSE, kernel)      # 闭运算
image_grad  = cv2.morphologyEx(image, cv2.MORPH_GRADIENT, kernel)   # 梯度计算
image_top   = cv2.morphologyEx(image, cv2.MORPH_TOPHAT, kernel)     # 顶帽运算
image_black = cv2.morphologyEx(image, cv2.MORPH_BLACKHAT, kernel)   # 黑帽运算

plt.subplot(2, 3, 1),    plt.imshow(image),            plt.title('image')
plt.subplot(2, 3, 2),    plt.imshow(image_open),       plt.title('cv2.MORPH_OPEN')
plt.subplot(2, 3, 3),    plt.imshow(image_close),      plt.title('cv2.MORPH_CLOSE')
plt.subplot(2, 3, 4),    plt.imshow(image_grad),       plt.title('cv2.MORPH_GRADIENT')
plt.subplot(2, 3, 5),    plt.imshow(image_top),        plt.title('cv2.MORPH_TOPHAT')
plt.subplot(2, 3, 6),    plt.imshow(image_black),      plt.title('cv2.MORPH_BLACKHAT')
plt.show()

"""#################################################################################################################
# 函数功能:用于对图像进行形态学操作,如腐蚀、膨胀、开运算、闭运算等。
# 函数说明:dst = cv2.morphologyEx(src, op, kernel)
# 参数说明:
#         src:      输入图像,通常是灰度图像或二值图像。
#         op:       形态学操作类型,可以是以下几种之一:
#                             cv2.MORPH_ERODE:      腐蚀操作。
#                             cv2.MORPH_DILATE:     膨胀操作。
#                             cv2.MORPH_OPEN:       开运算(先腐蚀后膨胀)。            开运算可以用来消除小黑点。
#                             cv2.MORPH_CLOSE:      闭运算(先膨胀后腐蚀)。            闭运算可以用来突出边缘特征。
#                             cv2.MORPH_GRADIENT:   形态学梯度(膨胀图像减去腐蚀图像)。  突出团块(blob)的边缘,保留物体的边缘轮廓。
#                             cv2.MORPH_TOPHAT:     顶帽运算(原始图像减去开运算图像)。  突出比原轮廓亮的部分。
#                             cv2.MORPH_BLACKHAT:   黑帽运算(闭运算图像减去原始图像)。  突出比原轮廓暗的部分。
#         kernel:   结构元素,用于指定形态学操作的形状和大小。
#################################################################################################################"""

"""##########################################################
# morphology
# n. (生物)形态学;(语言学中的)词法,形态学;结构,形态

# morph
# n. 形素,语素;形态;图像变换
# v. (使)图像变形;将(图像)进行合成处理;改变,变化,变形
##########################################################"""

(十一)边缘检测算子 —— cv2.sobel()、cv2.Scharr()、cv2.Laplacian()、cv2.Canny()

(1)不同算子的差异:Sobel算子、Scharr算子、Laplacian算子
(2)Canny算子中不同阈值的区别
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

import cv2                          # opencv    读取图像默认为BGR
import matplotlib.pyplot as plt     # matplotlib显示图像默认为RGB
import numpy as np

img = cv2.imread(r'F:\py\image.jpg')
img_GRAY = cv2.imread(r'F:\py\image.jpg', cv2.IMREAD_GRAYSCALE)
############################################################################################
# (1)左边减右边(2)白到黑是正数,黑到白就是负数,且所有的负数会被截断成0,所以要取绝对值。
sobel_Gx        = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3)
sobel_Gx_Abs    = cv2.convertScaleAbs(sobel_Gx)
sobel_Gy        = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3)
sobel_Gy_Abs    = cv2.convertScaleAbs(sobel_Gy)
# 分别计算x和y,再求和
sobel_Gx_Gy_Abs = cv2.addWeighted(sobel_Gx_Abs, 0.5, sobel_Gy_Abs, 0.5, 0)  # 权重值x + 权重值y +偏置b

# 同时对x和y进行求导,会导致部分信息丢失。(不建议)
# sobel_Gx = cv2.Sobel(img, cv2.CV_64F, 1, 1, ksize=3)
########################################
plt.subplot(2, 3, 1),    plt.imshow(img),               plt.title('image_raw')
plt.subplot(2, 3, 2),    plt.imshow(sobel_Gx),          plt.title('image_sobel_Gx')
plt.subplot(2, 3, 3),    plt.imshow(sobel_Gx_Abs),      plt.title('image_sobel_Gx_Abs')
plt.subplot(2, 3, 4),    plt.imshow(sobel_Gy),          plt.title('image_sobel_Gy')
plt.subplot(2, 3, 5),    plt.imshow(sobel_Gy_Abs),      plt.title('image_sobel_Gy_Abs')
plt.subplot(2, 3, 6),    plt.imshow(sobel_Gx_Gy_Abs),   plt.title('image_sobel_Gx_Gy_Abs')
plt.show()

"""#################################################################################################################
# 函数功能:Sobel算子是一种常用的边缘检测算子 ———— 对噪声具有平滑作用,提供较为精确的边缘方向信息,但是边缘定位精度不够高。
# 操作步骤:    
#             备注1:分别计算x和y,再求和(效果好),若同时对x和y进行求导,会导致部分信息丢失。(不建议)
#             备注2:对于二维图像,Sobel算子在x,y两个方向求导,故有不同的两个卷积核(Gx, Gy),且Gx的转置等于Gy。分别反映每个像素在水平和在垂直方向上的亮度变换情况.
#             边缘即灰度像素值快速变化的地方。如:黑到白的边界
# 
# 函数说明:dst = cv2.Sobel(src, ddepth, dx, dy, ksize=3, scale=1, delta=0, borderType=cv2.BORDER_DEFAULT)
# 参数说明:	
#             src:        输入图像,通常是单通道的灰度图像。通常情况下,输入图像的深度是 cv2.CV_8U(8 位无符号整型)
#             ddepth:     输出图像的深度,通常为-1表示与输入图像相同。支持以下类型:cv2.CV_16S、cv2.CV_32F、cv2.CV_64F
#             dx:          x 方向上的导数阶数,0 表示不对 x 方向求导数。
#             dy:          y 方向上的导数阶数,0 表示不对 y 方向求导数。
#             ksize:       滤波器的内核大小。必须是 -1、1、3、5 或 7,表示内核的大小是 1x1、3x3、5x5 或 7x7。 
#                                       -1表示使用 Scharr 滤波器。Scharr 滤波器是 Sobel 滤波器的改进版本,具有更好的性能。
#             scale:       (可选)表示计算结果的缩放因子。
#             delta:       (可选)表示输出图像的偏移值。
#             borderType:  (可选)表示边界处理方式。
# 返回参数:
#             滤波后图像
#################################################################################################################"""

"""#################################################################################################################
# 函数功能:用于将输入数组的每个元素乘以缩放因子 alpha,然后加上偏移量 beta,并取结果的绝对值后转换为无符号8位整数类型。
# 函数说明:dst = cv2.convertScaleAbs(src, alpha=1, beta=0)
# 参数说明:
#         src:      输入数组,可以是任意类型的数组。
#         alpha:    缩放因子
#         beta:     偏移量
# 返回参数:
#         dst       转换后数组(与输入数组相同大小和类型的数组,数据类型为无符号8位整数。)
#################################################################################################################"""
    
# 不同算子的差异:Sobel算子、Scharr算子、laplacian算子
sobelx = cv2.Sobel(img_GRAY, cv2.CV_64F, 1, 0, ksize=3)
sobely = cv2.Sobel(img_GRAY, cv2.CV_64F, 0, 1, ksize=3)
sobelx = cv2.convertScaleAbs(sobelx)
sobely = cv2.convertScaleAbs(sobely)
sobelxy = cv2.addWeighted(sobelx,0.5,sobely,0.5,0)

scharrx = cv2.Scharr(img_GRAY, cv2.CV_64F, 1, 0)
scharry = cv2.Scharr(img_GRAY, cv2.CV_64F, 0, 1)
scharrx = cv2.convertScaleAbs(scharrx)
scharry = cv2.convertScaleAbs(scharry)
scharrxy = cv2.addWeighted(scharrx, 0.5, scharry, 0.5, 0)

laplacian = cv2.Laplacian(img_GRAY, cv2.CV_64F)
laplacian = cv2.convertScaleAbs(laplacian)

plt.subplot(1, 3, 1),    plt.imshow(sobelxy, 'gray'),     plt.title('image_sobel_Gy_Abs')
plt.subplot(1, 3, 2),    plt.imshow(scharrxy, 'gray'),    plt.title('image_sobel_Gy_Abs')
plt.subplot(1, 3, 3),    plt.imshow(laplacian, 'gray'),   plt.title('image_sobel_Gx_Gy_Abs')
plt.show()

"""############################################################################################
# 边缘检测Canny算子
# 		(1)使用高斯滤波器,以平滑图像,滤除噪声。
# 		(2)计算图像中每个像素点的梯度强度和方向。
# 		(3)应用非极大值(Non-Maximum Suppression)抑制,以消除边缘检测带来的杂散响应。保留大值,去除小值。
#		(4)应用双阈值(Double-Threshold)检测来确定真实的和潜在的边缘。
#		(5)通过抑制孤立的弱边缘最终完成边缘检测。
############################################################################################"""
v1 = cv2.Canny(img_GRAY, 80, 150)    # minVal越小,检测出来的特征越多(可能是假边界值);maxVal越大,检测出来的特征越少
v2 = cv2.Canny(img_GRAY, 50, 100)

plt.subplot(1, 2, 1),    plt.imshow(v1, 'gray'),    plt.title('image_canny1')
plt.subplot(1, 2, 2),    plt.imshow(v2, 'gray'),    plt.title('image_canny2')
plt.show()

opencv边缘检测sobel算子
opencv边缘检测Canny

(十二)图像金字塔 —— cv2.pyrUp()、cv2.pyrDown()

在这里插入图片描述

import cv2                          # opencv    读取图像默认为BGR
import matplotlib.pyplot as plt     # matplotlib显示图像默认为RGB

image = cv2.imread(r'F:\py\image.jpg')
image_up = cv2.pyrUp(image)                 # 高斯金字塔:图像上采样(放大一倍)
image_up2 = cv2.pyrUp(image_up)             # 高斯金字塔:二次图像上采样(放大两倍)
############################
image_down = cv2.pyrDown(image)             # 高斯金字塔:图像下采样(缩小一倍)
image_down2 = cv2.pyrDown(image_down)       # 高斯金字塔:二次图像下采样(缩小两倍)
############################
image_up_down = cv2.pyrDown(image_up)       # 高斯金字塔:先上采样,然后下采样
image_down_up = cv2.pyrUp(image_down)       # 高斯金字塔:先下采样,然后上采样
image_down_up = cv2.resize(image_down_up, (image.shape[1], image.shape[0]))  # 确保尺度一致
image_laplacian = image - image_down_up     # 拉普拉斯金字塔:(1)源图像先缩小后再放大(2)源图像减去(1)操作后的图像。

plt.subplot(3, 3, 1),    plt.imshow(image),               plt.title('image_raw')
plt.subplot(3, 3, 2),    plt.imshow(image_up),            plt.title('image_up')
plt.subplot(3, 3, 3),    plt.imshow(image_up2),           plt.title('image_up2')

plt.subplot(3, 3, 4),    plt.imshow(image),               plt.title('image_raw')
plt.subplot(3, 3, 5),    plt.imshow(image_down),          plt.title('image_down')
plt.subplot(3, 3, 6),    plt.imshow(image_down2),         plt.title('image_down2')

plt.subplot(3, 3, 7),    plt.imshow(image_up_down),       plt.title('image_up_down')
plt.subplot(3, 3, 8),    plt.imshow(image_down_up),       plt.title('image_down_up')
plt.subplot(3, 3, 9),    plt.imshow(image_laplacian),     plt.title('image_laplacian')
plt.show()

"""##################################################################
# 高斯金字塔: cv2.pyrUp 与 cv2.pyrDown
#   cv2.pyrDown:  向下采样(缩小一倍)。          (1)对图像进行高斯内核卷积;(2)将所有偶数行和列去除;
#   cv2.pyrUp:    向上采样(放大一倍),分辨率降低。(1)将图像每隔“一行与一列”,全部填充0;(2)使用先前同样的高斯内核(乘以4)与放大后的图像卷积,获得近似值;
# 形成过程大致:   	(1)对原图像进行低通滤波和降采样得到一个粗尺度的近似图像,即分解得到的低通近似图像,
#               	(2)对近似图像经过插值(即上采样)和低通滤波
#               	(3)计算它和原图像的差值,得到分解的带通分量。
#          		——— 第一步:源图像先缩小后再放大; 第二步:源图像减去第一步操作后得到新图像。
# 图像缩放: (1)图像金字塔(2)resize()函数; 后者效果更好,不会降低分辨率。
#####################################################################"""

OpenCV计算机视觉学习 —— 图像金字塔(高斯金字塔,拉普拉斯金字塔,图像缩放resize函数)

(十三)轮廓检测 —— cv2.findContours()、cv2.drawContours()、cv2.arcLength()、cv2.approxPolyDP()、cv2.rectangle()

(1)轮廓的多边形拟合曲线:cv2.approxPolyDP()
(2)用矩形画出轮廓的边界:cv2.boundingRect()、cv2.rectangle()
(3)用外接圆画出轮廓的边界:cv2.minEnclosingCircle()、cv2.circle()
在这里插入图片描述

import cv2                          # opencv    读取图像默认为BGR
import matplotlib.pyplot as plt     # matplotlib显示图像默认为RGB

img = cv2.imread(r'picture\contours2.png')  # 读取图像
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  # 灰度图
ret, thresh = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY)  # 二值化

"""(1)轮廓检测与绘制轮廓"""
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
draw_img1 = img.copy()
res1 = cv2.drawContours(draw_img1, contours, -1, (0, 0, 255), 2)

"""(2)轮廓的多边形拟合曲线"""
contours1 = contours[0]
epsilon = 0.21*cv2.arcLength(contours1, True)       	# 系数k:越小越接近于真实轮廓,越大拟合越粗糙
approx = cv2.approxPolyDP(contours1, epsilon, True)
draw_img2 = img.copy()
res2 = cv2.drawContours(draw_img2, [approx], -1, (0, 0, 255), 2)

"""(3)用矩形画出轮廓的边界"""
x, y, w, h = cv2.boundingRect(contours1)
draw_img3 = img.copy()
img_rectangle = cv2.rectangle(draw_img3, (x, y), (x+w, y+h), (0, 255, 0), 2)

"""(4)用外接圆画出轮廓的边界"""
(x, y), radius = cv2.minEnclosingCircle(contours1)
draw_img4 = img.copy()
img_circle = cv2.circle(draw_img4, (int(x), int(y)), int(radius), (0, 255, 0), 2)

plt.subplot(2, 3, 1),    plt.imshow(img),               plt.title('RAW')           # 轮廓点绘制的颜色通道是BGR; 但是Matplotlib是RGB;
plt.subplot(2, 3, 2),    plt.imshow(res1),              plt.title('findContours')  # 故在绘图时,(0, 0, 255)会由BGR转换为RGB(红 - 蓝)
plt.subplot(2, 3, 3),    plt.imshow(res2),              plt.title('approxPolyDP')
plt.subplot(2, 3, 4),    plt.imshow(draw_img3),         plt.title('rectangle')
plt.subplot(2, 3, 5),    plt.imshow(draw_img4),         plt.title('circle')
plt.show()

"""#################################################################################################################
# 函数功能:用于在二值图像中轮廓检测
# 函数说明:contours, hierarchy = cv2.findContours(image, mode, method)
# 参数说明:
#         image:        输入的二值图像,通常为灰度图像或者是经过阈值处理后的图像。
#         mode:         轮廓检索模式,指定轮廓的层次结构。
#                             cv2.RETR_EXTERNAL:    仅检测外部轮廓。
#                             cv2.RETR_LIST:        检测所有轮廓,不建立轮廓层次。
#                             cv2.RETR_CCOMP:       检测所有轮廓,将轮廓分为两级层次结构。
#                             cv2.RETR_TREE:        检测所有轮廓,建立完整的轮廓层次结构。
#         method:       轮廓逼近方法,指定轮廓的近似方式。
#                             cv2.CHAIN_APPROX_NONE:        存储所有的轮廓点,相邻的点之间不进行抽稀。
#                             cv2.CHAIN_APPROX_SIMPLE:      仅存储轮廓的端点,相邻的点之间进行抽稀,以减少存储容量。
#                             cv2.CHAIN_APPROX_TC89_L1:     使用 Teh-Chin 链逼近算法中的 L1 范数,以减少存储容量。
#                             cv2.CHAIN_APPROX_TC89_KCOS:   使用 Teh-Chin 链逼近算法中的 KCOS 范数,以减少存储容量。
# 输出参数:
#         contours:     检测到的轮廓列表,每个轮廓是一个点的列表。
#         hierarchy:    轮廓的层次结构,一般用不到,可以忽略。
#
# 备注1:轮廓就是将连续的点(连着边界)连在一起的曲线,具有相同的颜色或者灰度。轮廓在形状分析和物体的检测和识别中很有用。
# 备注2:函数在opencv2只返回两个值:contours, hierarchy。
# 备注3:函数在opencv3会返回三个值:image, countours, hierarchy
#################################################################################################################"""

"""#################################################################################################################
# 函数功能:用于在图像上绘制轮廓。
# 函数说明:cv2.drawContours(image, contours, contourIdx, color, thickness=1, lineType=cv2.LINE_8, hierarchy=None, maxLevel=None, offset=None)
# 参数说明:
#         image:        要绘制轮廓的图像,可以是单通道或多通道的图像。
#         contours:     要绘制的轮廓列表,通常通过 cv2.findContours 函数获取。
#         contourIdx:   要绘制的轮廓索引,如果为负数则绘制所有轮廓。
#         color:        轮廓的颜色,通常是一个元组,如 (0, 255, 0) 表示绿色。
#         thickness:    轮廓线的粗细,如果为负数或 cv2.FILLED 则填充轮廓区域。
#         lineType:     线的类型
#                             cv2.LINE_4:4连接线。
#                             cv2.LINE_8:8连接线。
#                             cv2.LINE_AA:抗锯齿线。
#         hierarchy:    轮廓的层级结构,一般由 cv2.findContours 函数返回,可选参数。
#         maxLevel:     要绘制的轮廓的最大层级,一般由 cv2.findContours 函数返回,可选参数。
#         offset:       轮廓偏移量,一般不需要设置,可选参数。
#
# 备注:使用copy()复制图像后绘制, 否则原图会同时改变。
#################################################################################################################"""

"""#################################################################################################################
# 函数功能:用于计算轮廓的周长或曲线的长度。
# 函数说明:length = cv2.arcLength(curve, closed)
# 参数说明:
#         curve:    输入的轮廓或曲线,通常是一个包含点的列表。
#         closed:   一个布尔值,指示曲线是否闭合。如果是闭合曲线(如多边形轮廓),则设置为 True,否则设置为 False。
# 返回参数:      
#         length     轮廓的长度(周长)。
#################################################################################################################"""

"""#################################################################################################################
# 函数功能:用于对轮廓进行多边形拟合曲线。
# 函数说明:approximated_contour = cv2.approxPolyDP(curve, epsilon, closed)
# 输入参数:     
#         curve:      输入的轮廓,通常是一个包含点的列表。
#         epsilon:    逼近精度参数,表示逼近结果与原始曲线之间的最大距离。较小的 epsilon 值会产生更精确的逼近结果。
#         closed:     一个布尔值,指示轮廓是否闭合。如果是闭合轮廓(如多边形轮廓),则设置为 True,否则设置为 False。
# 返回参数:      
#         approximated_contour:逼近后的多边形轮廓。
#################################################################################################################"""

"""#################################################################################################################
# 函数功能:用于在图像上绘制矩形。
# 函数说明:image_with_rectangle = cv2.rectangle(image, pt1, pt2, color, thickness=1, lineType=cv2.LINE_8, shift=0)
# 输入参数:     
#         image:    输入的图像。
#         pt1:      矩形的左上角坐标,格式为 (x1, y1)。
#         pt2:      矩形的右下角坐标,格式为 (x2, y2)。
#         color:    矩形的颜色,可以是一个 BGR 颜色值的元组,例如 (255, 0, 0) 表示蓝色。
#         thickness:可选参数,表示矩形边框的粗细。默认值为 1。
#         lineType: 可选参数,表示线条的类型。默认值为 cv2.LINE_8。
#         shift:    可选参数,表示点坐标中的小数位数。默认值为 0。
# 返回参数:      
#         image_with_rectangle:绘制了矩形的图像。
#################################################################################################################"""

(十四)模板匹配 —— cv2.matchTemplate()、cv2.minMaxLoc()

在这里插入图片描述

############################################################################################
# 模板匹配:在原始图像上,通过滑动匹配的方式,计算模板与(模板覆盖的图像)的相似程度;
#
#       原图形大小: AxB;    而模板大小: axb;    则输出结果的矩阵大小: (A-a+1)x(B-b+1)
############################################################################################
import cv2                          # opencv    读取图像默认为BGR
import matplotlib.pyplot as plt     # matplotlib显示图像默认为RGB

image = cv2.imread(r'F:\py\image', 0)   # 读取目标图像
template = cv2.imread(r'F:\py\image', 0)   # 读取模板图像
height, width = template.shape[::1]  # 获得模板图像的高宽尺寸

#############################################################
# 如果模板匹配方法是平方差或者归一化平方差,要用min_loc; 其余用max_loc
#############################################################
"""模板匹配模式:cv2.TM_SQDIFF"""
res1 = cv2.matchTemplate(image, template, cv2.TM_SQDIFF)
min_val1, max_val1, min_loc1, max_loc1 = cv2.minMaxLoc(res1)
top_left1 = min_loc1
bottom_right1 = (top_left1[0] + width, top_left1[1] + height)

"""模板匹配模式:cv2.TM_CCORR"""
res2 = cv2.matchTemplate(image, template, cv2.TM_CCORR)
min_val2, max_val2, min_loc2, max_loc2 = cv2.minMaxLoc(res2)
top_left2 = max_loc2
bottom_right2 = (top_left2[0] + width, top_left2[1] + height)

"""模板匹配模式:cv2.TM_CCOEFF"""
res3 = cv2.matchTemplate(image, template, cv2.TM_CCOEFF)
min_val3, max_val3, min_loc3, max_loc3 = cv2.minMaxLoc(res3)
top_left3 = max_loc3
bottom_right3 = (top_left3[0] + width, top_left3[1] + height)

"""模板匹配模式:cv2.TM_SQDIFF_NORMED"""
res4 = cv2.matchTemplate(image, template, cv2.TM_SQDIFF_NORMED)
min_val4, max_val4, min_loc4, max_loc4 = cv2.minMaxLoc(res4)
top_left4 = min_loc4
bottom_right4 = (top_left4[0] + width, top_left4[1] + height)

"""模板匹配模式:cv2.TM_CCORR_NORMED"""
res5 = cv2.matchTemplate(image, template, cv2.TM_CCORR_NORMED)
min_val5, max_val5, min_loc5, max_loc5 = cv2.minMaxLoc(res5)
top_left5 = max_loc5
bottom_right5 = (top_left5[0] + width, top_left5[1] + height)

"""模板匹配模式:cv2.TM_CCOEFF_NORMED"""
res6 = cv2.matchTemplate(image, template, cv2.TM_CCOEFF_NORMED)
min_val6, max_val6, min_loc6, max_loc6 = cv2.minMaxLoc(res6)
top_left6 = max_loc6
bottom_right6 = (top_left6[0] + width, top_left6[1] + height)

# 绘制矩形
image1 = image.copy();       image2 = image.copy();           image3 = image.copy()
image4 = image.copy();       image5 = image.copy();           image6 = image.copy()
cv2.rectangle(image1, top_left1, bottom_right1, 255, 2)
cv2.rectangle(image2, top_left2, bottom_right2, 255, 2)
cv2.rectangle(image3, top_left3, bottom_right3, 255, 2)
cv2.rectangle(image4, top_left4, bottom_right4, 255, 2)
cv2.rectangle(image5, top_left5, bottom_right5, 255, 2)
cv2.rectangle(image6, top_left6, bottom_right6, 255, 2)
##############################################
# plt.imshow(image1) 彩图显示
# plt.imshow(image1, cmap='gray') 灰色图
plt.subplot(231),       plt.imshow(image1, cmap='gray'),         plt.axis('off'),        plt.title('cv2.TM_SQDIFF')
plt.subplot(232),       plt.imshow(image2, cmap='gray'),         plt.axis('off'),        plt.title('cv2.TM_CCORR')
plt.subplot(233),       plt.imshow(image3, cmap='gray'),         plt.axis('off'),        plt.title('cv2.TM_CCOEFF')
plt.subplot(234),       plt.imshow(image4, cmap='gray'),         plt.axis('off'),        plt.title('cv2.TM_SQDIFF_NORMED')
plt.subplot(235),       plt.imshow(image5, cmap='gray'),         plt.axis('off'),        plt.title('cv2.TM_CCORR_NORMED')
plt.subplot(236),       plt.imshow(image6, cmap='gray'),         plt.axis('off'),        plt.title('cv2.TM_CCOEFF_NORMED')
plt.show()

"""#########################################################################################################
# 函数功能:模板匹配 ———— 在输入图像上滑动目标模板,并计算目标模板与图像的匹配程度来实现目标检测。
# 函数说明:cv2.matchTemplate(image, template, method)
# 参数说明:
#         image:      输入图像,需要在其中搜索目标模板。
#         templ:      目标模板,需要在输入图像中匹配的部分。
#         method:     匹配方法
#                             cv2.TM_SQDIFF:         计算平方差。         计算结果越接近0,越相关
#                             cv2.TM_CCORR:          计算相关性。          计算结果越大,越相关
#                             cv2.TM_CCOEFF:         计算相关系数。         计算结果越大,越相关
#                             cv2.TM_SQDIFF_NORMED:  计算(归一化)平方差。   计算结果越接近0,越相关
#                             cv2.TM_CCORR_NORMED:   计算(归一化)相关性。   计算结果越接近1,越相关
#                             cv2.TM_CCOEFF_NORMED:  计算(归一化)相关系数。  计算结果越接近1,越相关
#                      备注:归一化效果更好
#########################################################################################################"""

"""#########################################################################################################
# 函数功能:用于找到数组中的最小值和最大值,以及它们的位置。
# 函数说明:min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(ret)
# 输入参数:
#         ret       cv2.matchTemplate函数返回的矩阵,即匹配结果图像。
# 返回参数:
#         min_val:匹配结果图像中的最小值。
#         max_val:匹配结果图像中的最大值。
#         min_loc:最小值对应的位置(x,y)。
#         max_loc:最大值对应的位置(x,y)。
#
# 备注:如果模板匹配方法是平方差或者归一化平方差,则使用min_loc; 否则使用max_loc
#########################################################################################################"""

opencv 实现模板匹配、特征点匹配

(十五)直方图(均衡化) —— cv2.calcHist()、img.ravel()、cv2.bitwise_and()、cv2.equalizeHist()、cv2.createCLAHE()

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

import cv2                          # opencv    读取图像默认为BGR
import matplotlib.pyplot as plt     # matplotlib显示图像默认为RGB
import numpy as np

# (1)单通道的直方图
img = cv2.imread(r'F:\py\image.jpg', 0)  # 0表示灰度图
hist = cv2.calcHist([img], [0], None, [256], [0, 256])  # 计算直方图
plt.hist(img.ravel(), 256)  # 绘制直方图。# img.ravel() 将图片转化成一维数组; 256 是BIN的数目
plt.show()

# (2)多通道的直方图
img1 = cv2.imread(r'F:\py\image.jpg', 1)  # 0表示灰度图
color = ('blue', 'green', 'red')
for i, col in enumerate(color):
    hist1 = cv2.calcHist([img1], [i], None, [256], [0, 256])  # img必须是单通道图像,否则报错:error: (-215)
    plt.plot(hist1, color=col)
plt.show()
#################################################################################
# (3)创建掩膜:mask
mask = np.zeros(img.shape[:2], np.uint8)                    # np.uint8:八位无符号整型:0~255
mask[100:300, 100:400] = 255                                # 掩膜区域显示255
masked_img = cv2.bitwise_and(img, img, mask=mask)           # 与操作
hist_full = cv2.calcHist([img], [0], None, [256], [0, 256])
hist_mask = cv2.calcHist([img], [0], mask, [256], [0, 256])

plt.subplot(221), plt.imshow(img, 'gray'),                  plt.title('image_raw')
plt.subplot(222), plt.imshow(mask, 'gray'),                 plt.title('mask')
plt.subplot(223), plt.imshow(masked_img, 'gray'),           plt.title('cv2.bitwise_and')
plt.subplot(224), plt.plot(hist_full), plt.plot(hist_mask), plt.title('hist_full + hist_mask')
plt.subplot(224), plt.plot(hist_full), plt.plot(hist_mask), plt.title('hist_full + hist_mask')
plt.xlim([0, 256])
plt.show()
#################################################################################
# (4)直方图均衡化
equ = cv2.equalizeHist(img)     # 将0~255两端的像素进行整幅图像的均匀化。即去掉:极黑/极白像素
# (5)自适应直方图均衡化
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))    # 将图像切分成8*8份,分别进行直方图均衡化(有效果降低过度处理)
res_clahe = clahe.apply(img)

plt.subplot(131),   plt.imshow(img, 'gray'),        plt.title('image_raw')
plt.subplot(132),   plt.imshow(equ, 'gray'),        plt.title('cv2.equalizeHist')
plt.subplot(133),   plt.imshow(res_clahe, 'gray'),  plt.title('cv2.createCLAHE')
plt.show()

"""####################################################################################################################
# 函数功能:用于计算图像的直方图。 ———— 直方图是一种统计图表,用于表示图像中各个像素灰度级别的分布情况。
# 函数说明:hist = cv2.calcHist(images, channels, mask, histSize, ranges, accumulate=False)
# 参数说明:
#         images:       输入的图像,可以是单张图像或图像列表。
#         channels:     指定要统计的通道索引,如果输入图像是灰度图像,则为 [0],如果是彩色图像,则可以是 [0]、[1]、[2] 分别代表蓝、绿、红通道。
#         mask:         可选参数,用于指定感兴趣区域的掩码,如果不需要,则设置为 None。
#         histSize:     指定直方图的大小,即灰度级别的数量。
#         ranges:       指定灰度级别的范围,通常为 [0, 256] 表示 0 到 255 的灰度级别范围。
#         accumulate:   可选参数,指定是否累积直方图。如果设置为 True,则会累积多个图像的直方图。
####################################################################################################################"""

matplotlib.pyplot.hist()函数
像素点直方图统计、掩膜图像
错误提示:cv::binary_op
错误提示:cv::histPrepareImages

(十六)傅里叶变换 + 低通/高通滤波 —— cv2.dft()、cv2.idft()、np.fft.fftshift()、np.fft.ifftshift()、cv2.magnitud()

傅里叶变换

  • 时域分析:以时间作为参照来观察动态世界的方法。
    • 世间万物都在随着时间不停的改变,并且永远不会静止下来。但在频域中,你会发现世界是静止的、永恒不变的。
  • 傅里叶告诉我们:任何周期函数,都可以看作是不同振幅,不同相位正弦波的叠加。
    • 举例:利用对不同琴键不同力度,不同时间点的敲击,可以组合出任何一首乐曲。
  • 傅里叶分析可分为傅里叶级数(Fourier Serie)和傅里叶变换(Fourier Transformation)。

傅里叶变换的作用
(1)高频:变化剧烈的灰度分量,例如:边界/图像的轮廓
(2)低频:变化缓慢的灰度分量,例如:一片大海
滤波器
(1)低通滤波器:通低频阻高频(图像模糊)
(2)高通滤波器:阻低频通高频(图像细节增强)
在这里插入图片描述

import cv2                          # opencv    读取图像默认为BGR
import matplotlib.pyplot as plt     # matplotlib显示图像默认为RGB
import numpy as np

# (1)频谱图像设计
img = cv2.imread(r'F:\py\image.jpg', 0)    # 0表示灰度图
dft = cv2.dft(np.float32(img), flags=cv2.DFT_COMPLEX_OUTPUT)                            # 傅里叶变换(np.float32 格式)
dft_shift = np.fft.fftshift(dft)                                                        # 移动到中心位置
magnitude_spectrum = 20*np.log(cv2.magnitude(dft_shift[:, :, 0],dft_shift[:, :, 1]))    # 理解为固定公式即可
# 频谱:最中心的频率最小,像圆一样向外扩散,越来越大。# 备注:20*np.log(cv2.magnitude())

# (2)低通滤波设计
rows, cols = img.shape
crow, ccol = int(rows/2), int(cols/2)                           # 获取图像中心位置

mask_low = np.zeros((rows, cols, 2), np.uint8)
mask_low[crow-30:crow+30, ccol-30:ccol+30] = 1

fshift_low = dft_shift * mask_low
f_ishift_low = np.fft.ifftshift(fshift_low)
img_low = cv2.idft(f_ishift_low)                                # 逆傅里叶变换
img_low = cv2.magnitude(img_low[:, :, 0], img_low[:, :, 1])     # 频谱图像转灰度图像

# (3)高通滤波设计
mask_high = np.ones((rows, cols, 2), np.uint8)
mask_high[crow-30:crow+30, ccol-30:ccol+30] = 0

fshift_high = dft_shift * mask_high
f_ishift_high = np.fft.ifftshift(fshift_high)
img_high = cv2.idft(f_ishift_high)                              # 逆傅里叶变换
img_high = cv2.magnitude(img_high[:, :, 0], img_high[:, :, 1])  # 频谱图像转灰度图像

# (4)显示图像
plt.subplot(141),  plt.imshow(img, cmap='gray'),                 plt.title('image_raw'),                    plt.xticks([]),   plt.yticks([])
plt.subplot(142),  plt.imshow(magnitude_spectrum, cmap='gray'),  plt.title('image_Magnitude_Spectrum'),     plt.xticks([]),   plt.yticks([])
plt.subplot(143),  plt.imshow(img_low, cmap='gray'),             plt.title('image_Low_pass_filter'),        plt.xticks([]),   plt.yticks([])
plt.subplot(144),  plt.imshow(img_high, cmap='gray'),            plt.title('image_High_pass_filter'),       plt.xticks([]),   plt.yticks([])
plt.show()

"""#####################################################################################################
# 在OpenCV中,我们通过cv2.dft()来实现傅里叶变换,使用cv2.idft()来实现逆傅里叶变换。
#     注意1:变换后得到原始图像的频谱信息。其中:频率为0的部分(零分量)会在左上角,需要使用numpy.fft.fftshift()函数,将其移动到中间位置。
#     注意2:变换后的频谱图像是双通道的(实部,虚部)。需要使用cv2.magnitude函数,将幅度映射到灰度空间[0,255]内,使其以灰度图像显示出来。
##################################################
# 傅里叶变换:cv2.dft(np.float32, cv2.DFT_COMPLEX_OUTPUT)
# 输入参数:  
#           (1)输入图像需要先转换成np.float32 格式。
#           (2)转换标识 - cv2.DFT_COMPLEX_OUTPUT - 用来输出一个复数阵列
##################################################
# 逆傅里叶变换:cv2.idft(dft_shift)
# 输入参数:  
#           傅里叶变换后并位置转换后的频谱图像。
##################################################
# 频谱图像:cv2.magnitude(x-实部, y-虚部)
# 输入参数:   
#           (1)浮点型x坐标值(实部)
#           (2)浮点型y坐标值(虚部)
# 备注:两个参数的必须具有相同的大小(size)
#####################################################################################################"""

傅里叶分析之详细剖析(完整版-强烈推荐)

(十七)Harris角点检测 —— cv2.cornerHarris() + cv2.KeyPoint() + cv2.drawKeypoints()

在这里插入图片描述

import cv2  		                    # opencv读取的格式是BGR
import matplotlib.pyplot as plt         # Matplotlib是RGB
import numpy as np

# (1)加载图像
image = cv2.imread(r'picture/Black_and_white_chess.jpg')
image_gray = cv2.cvtColor(src=image, code=cv2.COLOR_BGR2GRAY)  # 将图像转换为灰度图像

# (2)角点检测
image_harris = cv2.cornerHarris(src=image_gray, blockSize=2, ksize=3, k=0.04)  # 创建 Harris 角点检测器对象

value = 0.01 * image_harris.max()  # 最佳阈值因图而异
points = np.argwhere(image_harris > value).astype(np.float16)  # 找到检测到的角点(若角点 > 角点最大值*0.001,则判断为角点)
keypoints = [cv2.KeyPoint(x[1], x[0], 1) for x in points]  # 创建带有指定大小的关键点

image_result = cv2.drawKeypoints(image, keypoints, None, color=(0, 0, 255), flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)  # 绘制检测到的角点

# (3)显示图像
print('image.shape:', image.shape)
print('image_harris.shape:', image_harris.shape)
plt.subplot(121), plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)),        plt.title('image')
plt.subplot(122), plt.imshow(cv2.cvtColor(image_result, cv2.COLOR_BGR2RGB)), plt.title('image_result')
plt.show()

"""#############################################################################################
# 函数功能:对图像进行 Harris 角点检测。
# 函数说明:dst = cv2.cornerHarris(src, blockSize, ksize, k, dst=None, borderType=None)
# 参数说明:
#         src:输入图像,应为灰度图像(单通道图像)。
#         blockSize:指定计算导数时要考虑的领域大小。它是用于 Sobel 求导的窗口大小,常为 3、5、7 等。一般取值为奇数,默认为 3。
#         ksize:指定 Sobel 滤波器的孔径大小,常为 3。必须大于 1。默认为 3。
#         k:Harris 角点检测参数,取值范围为 [0.04, 0.06],表示角点响应函数中的自由参数 k。默认为 0.04。
#         dst:可选参数,用于存储输出角点响应图像。如果为 None,则函数会创建一个与输入图像相同大小和类型的图像。默认为 None。
#         borderType:可选参数,用于指定图像边界的填充方式。默认为 None,表示使用默认的边界填充方式(cv2.BORDER_DEFAULT)。
# 返回值:
#         dst:输出角点响应图像,数据类型为单通道浮点型。
#############################################################################################"""

"""#############################################################################################
# 函数功能:表示图像中的一个关键点。
# 函数说明:keypoint = cv2.KeyPoint(x, y, _size, _angle=-1, _response=0, _octave=0, _class_id=-1)
# 参数说明:
#         x:关键点的 x 坐标。
#         y:关键点的 y 坐标。
#         _size:关键点的特征尺度。
#         _angle:可选参数,表示关键点的方向。默认为 -1,表示未指定方向。
#         _response:可选参数,表示关键点的响应值。默认为 0。
#         _octave:可选参数,表示关键点所在金字塔组和组内层级的信息。默认为 0。
#         _class_id:可选参数,表示关键点所属的类别 ID。默认为 -1,表示未指定类别。
# 返回值:
#         keypoint:表示图像中的一个关键点。
#############################################################################################"""

"""#############################################################################################
# 函数功能:在图像上绘制关键点。
# 函数说明:image_with_keypoints = cv2.drawKeypoints(image, keypoints, outImage=None, color=(0, 255, 0), flags=cv2.DRAW_MATCHES_FLAGS_DEFAULT)
# 参数说明:
#         image:输入图像,可以是灰度图像或者彩色图像(但是颜色通道顺序为 BGR)。
#         keypoints:关键点列表,每个关键点是一个 cv2.KeyPoint 对象。
#         outImage:可选参数,用于指定输出图像,如果不为 None,则将关键点绘制在此图像上。默认为 None,表示在输入图像上绘制。
#         color:可选参数,绘制关键点的颜色,默认为 (0, 255, 0),表示绿色。
#         flags:可选参数,绘制关键点的标志,默认为 cv2.DRAW_MATCHES_FLAGS_DEFAULT。其他可选值包括 cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS 和 cv2.DRAW_MATCHES_FLAGS_NOT_DRAW_SINGLE_POINTS。
# 返回值:
#         image_with_keypoints:绘制了关键点的图像。
#############################################################################################"""

OpenCV特征提取与检测之Harris角点检测

(十八)SIFT尺度不变特征检测 —— cv2.xfeatures2d.SIFT_create()、sift.detectAndCompute()、cv2.drawKeypoints()

在这里插入图片描述

"""#########################################################
# 尺度不变特征变换(Scale-invariant feature transform,SIFT)
#       特点:具有尺度不变性、旋转不变性、光照不变性;且对于局部遮挡或变形仍可以提取有效特征
#       缺点:计算复杂度高,内存消耗大,专利限制,参数敏感
#
# Harris角点检测
#       特点:对较小范围的噪声有一定的鲁棒性
#       缺点:尺度不变性差、旋转不变性差、对较大范围的噪声敏感,灰度变化敏感,参数敏感
#########################################################
# 在 Opencv 中,SIFT 函数的 3.4.3 版本及以上涉及到专利保护,故需要进行 Opencv 降版本。
#       卸载旧版本:pip uninstall opencv-python             pip uninstall opencv-contrib-python
#       安装新版本:pip install opencv-python==3.4.1.15     pip install opencv-contrib-python==3.4.1.15
#########################################################"""
import cv2  		                    # opencv读取的格式是BGR
import matplotlib.pyplot as plt         # Matplotlib是RGB
import numpy as np

image = cv2.imread('picture/house.jpg')                                 # (1)读取图像
image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)  		            # (2)灰度化
sift = cv2.xfeatures2d.SIFT_create()                                    # (3)创建 SIFT 特征检测器
key_points, des = sift.detectAndCompute(image_gray, None)               # (4)检测图像中的关键点并计算描述符
image_result = cv2.drawKeypoints(image_gray, key_points, image.copy())  # (5)绘制检测到的关键点

# (6)显示图像
plt.subplot(121), plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)),        plt.title('image')
plt.subplot(122), plt.imshow(cv2.cvtColor(image_result, cv2.COLOR_BGR2RGB)), plt.title('image_result')
plt.show()

"""#############################################################################################
# 函数功能:创建 SIFT(尺度不变特征变换)特征检测器对象。
# 函数说明:sift = cv2.xfeatures2d.SIFT_create(nfeatures=0, nOctaveLayers=3, contrastThreshold=0.04, edgeThreshold=10, sigma=1.6)
# 参数说明:
#         nfeatures:可选参数,表示要提取的特征点的数量。默认为 0,表示提取所有特征点。
#         nOctaveLayers:可选参数,表示每个金字塔组的层数。默认为 3。
#         contrastThreshold:可选参数,表示特征点的对比度阈值。默认为 0.04。
#         edgeThreshold:可选参数,表示特征点的边缘阈值。默认为 10。
#         sigma:可选参数,表示高斯滤波器的方差。默认为 1.6。
# 返回值:
#         sift:SIFT 特征检测器对象。
#############################################################################################"""

"""#############################################################################################
# 函数功能:检测图像中的关键点并计算描述符。
# 函数说明:keypoints, descriptors = sift.detectAndCompute(image, mask=None, descriptors=None, useProvidedKeypoints=False)
# 参数说明:
#         image:输入图像,应为灰度图像或者彩色图像(但是颜色通道顺序为 BGR)。
#         mask:可选参数,掩膜,用于指定在哪些区域内进行关键点检测。默认为 None,表示对整个图像进行检测。
#         descriptors:可选参数,指定输入的描述符数组,如果不为空,则检测关键点时将忽略输入图像,而直接使用输入的关键点。
#         useProvidedKeypoints:可选参数,布尔值,指定是否使用提供的关键点进行计算描述符。默认为 False。
# 返回值:
#         keypoints:检测到的关键点列表,每个关键点是一个 cv2.KeyPoint 对象。
#         descriptors:计算得到的描述符数组,每行是一个关键点的描述符向量。如果未能计算描述符,则返回 None。
#############################################################################################"""

"""#############################################################################################
# 函数功能:在图像上绘制关键点。
# 函数说明:image_with_keypoints = cv2.drawKeypoints(image, keypoints, outImage=None, color=(0, 255, 0), flags=cv2.DRAW_MATCHES_FLAGS_DEFAULT)
# 参数说明:
#         image:输入图像,可以是灰度图像或者彩色图像(但是颜色通道顺序为 BGR)。
#         keypoints:关键点列表,每个关键点是一个 cv2.KeyPoint 对象。
#         outImage:可选参数,用于指定输出图像,如果不为 None,则将关键点绘制在此图像上。默认为 None,表示在输入图像上绘制。
#         color:可选参数,绘制关键点的颜色,默认为 (0, 255, 0),表示绿色。
#         flags:可选参数,绘制关键点的标志,默认为 cv2.DRAW_MATCHES_FLAGS_DEFAULT。其他可选值包括 cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS 和 cv2.DRAW_MATCHES_FLAGS_NOT_DRAW_SINGLE_POINTS。
# 返回值:
#         image_with_keypoints:绘制了关键点的图像。
#############################################################################################"""

(十九)暴力特征匹配 —— cv2.BFMatcher_create()、bf.match()、bf_knn.knnMatch()、cv2.drawMatches()

在这里插入图片描述

#############################################################################################
# 暴力特征匹配主要流程
#      (1)先在查询描述符中取一个关键点的描述符,将其与训练描述符中的所有关键点描述符进行比较。
#      (2)每次比较后会计算出一个距离值,距离最小的值对应最佳匹配结果。
#      (3)所有描述符比较完后,匹配器返回匹配结果列表。
#############################################################################################
# 在 Opencv 中,SIFT 函数的 3.4.3 版本及以上涉及到专利保护,故需要进行 Opencv 降版本。
# 		卸载旧版本:pip uninstall opencv-python            pip uninstall opencv-contrib-python
# 		安装新版本:pip install opencv-python==3.4.1.15    pip install opencv-contrib-python==3.4.1.15
#############################################################################################
import cv2                              # opencv读取的格式是BGR
import matplotlib.pyplot as plt         # Matplotlib是RGB
import numpy as np


# (1)读取图像
image1 = cv2.imread(r'picture\box.png')			  # 匹配图像
image2 = cv2.imread(r'picture\box_in_scene.png')  # 待匹配图像

# (2)检测并计算关键点和描述符
sift = cv2.xfeatures2d.SIFT_create()  # 创建SIFT特征检测器
# 检测并计算图像:关键点kp1 + 特征向量des1
kp1, des1 = sift.detectAndCompute(image1, None)
kp2, des2 = sift.detectAndCompute(image2, None)
# 暴力匹配器的匹配函数只支持描述符的类型为 np.uint8和np.float32。
des1 = des1.astype(np.float32)
des2 = des2.astype(np.float32)

# (3)使用暴力匹配器,并获取最佳结果
"""方法一:使用match()匹配指定数量的最佳匹配结果"""
bf = cv2.BFMatcher(cv2.NORM_L2, crossCheck=False)  # 创建暴力匹配器对象
matches = bf.match(des1, des2)  # 使用match()方法进行特征匹配,返回匹配结果
matches = sorted(matches, key=lambda x: x.distance)  # 根据距离对所有匹配结果进行排序
result = cv2.drawMatches(image1, kp1, image2, kp2, matches[:15], None)  # 绘制前15个匹配结果

"""方法二:使用knnMatch()匹配指定数量的最佳结果"""
bf_knn = cv2.BFMatcher(cv2.NORM_L2, crossCheck=False)  # 创建暴力匹配器对象
ms_knn = bf_knn.knnMatch(des1, des2, k=2)  # 使用knnMatch()方法进行k近邻匹配,每个描述符返回2个最佳匹配结果
# 应用比例测试选择要使用的匹配结果
results = []
for m, n in ms_knn:
    if m.distance < 0.75 * n.distance:
        results.append(m)
# 绘制不同方式的匹配结果
image_DEFAUL = cv2.drawMatches(image1, kp1, image2, kp2, results[:20], None, flags=cv2.DrawMatchesFlags_DEFAULT)
image_NO_POINTS = cv2.drawMatches(image1, kp1, image2, kp2, results[:20], None, flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
image_KEYPOINTS = cv2.drawMatches(image1, kp1, image2, kp2, results[:20], None, flags=cv2.DrawMatchesFlags_DRAW_RICH_KEYPOINTS)

# (4)可视化结果
plt.subplot(231), plt.imshow(cv2.cvtColor(image1, cv2.COLOR_BGR2RGB)),               plt.title('raw_image1')
plt.subplot(232), plt.imshow(cv2.cvtColor(image2, cv2.COLOR_BGR2RGB)),               plt.title('raw_image2')
plt.subplot(233), plt.imshow(cv2.cvtColor(result, cv2.COLOR_BGR2RGB)),               plt.title('match_result')

plt.subplot(234), plt.imshow(cv2.cvtColor(image_DEFAUL, cv2.COLOR_BGR2RGB)),         plt.title('knnMatch_DEFAUL')
plt.subplot(235), plt.imshow(cv2.cvtColor(image_NO_POINTS, cv2.COLOR_BGR2RGB)),      plt.title('knnMatch_NO_POINTS')
plt.subplot(236), plt.imshow(cv2.cvtColor(image_KEYPOINTS, cv2.COLOR_BGR2RGB)),      plt.title('knnMatch_KEYPOINTS')
plt.show()

"""#############################################################################################
# 函数功能:用于创建一个暴力匹配器(Brute-Force Matcher)。暴力匹配器在特征匹配中逐一比较两个图像的每个描述符之间的距离,找到最相似的一对特征点。
# 函数说明:bf = cv2.BFMatcher_create(normType=cv2.NORM_L2, crossCheck=False)
# 参数说明:
#         normType:指定用于测量描述符之间距离的度量方法。常见的取值包括:
#                 cv2.NORM_L1:使用 L1 范数(曼哈顿距离)。
#                 cv2.NORM_L2:使用 L2 范数(欧氏距离),适用于 SIFT 和 SURF 特征描述符。
#                 cv2.NORM_HAMMING:适用于二进制描述符,例如 ORB、BRIEF、BRISK 等。
#                 cv2.NORM_HAMMING2:适用于 ORB 描述符(WTA_K=3 或 4 时)。
#         crossCheck:布尔值。默认为 False。如果设置为 True,则匹配器只返回那些在两个方向上都满足条件的匹配结果,即查询描述符 A 与训练描述符 B 互为最佳匹配。
# 返回值
#         返回一个 cv2.BFMatcher 对象。
#############################################################################################"""

"""#############################################################################################
# 函数功能:用于匹配查询描述符与训练描述符,返回每个查询描述符的最佳匹配结果。
# 函数说明:matches = bf.match(des1, des2)
# 参数说明:
#         des1:查询描述符(第一幅图像的描述符),类型应为 np.ndarray。
#         des2:训练描述符(第二幅图像的描述符),类型应为 np.ndarray。
# 返回值:
#         matches:DMatch 对象的列表。每个 DMatch 对象包含以下属性:
#                 - queryIdx:查询描述符的索引(对应 des1 中的索引)。
#                 - trainIdx:训练描述符的索引(对应 des2 中的索引)。
#                 - imgIdx:图像索引,仅在多图像匹配时使用。
#                 - distance:描述符之间的距离,距离越小表示匹配度越高。
#############################################################################################"""

"""#############################################################################################
# 函数功能:用于进行 k 近邻匹配,返回每个查询描述符的 k 个最佳匹配结果。
# 函数说明:matches = bf.knnMatch(des1, des2, k[, mask[, compactResult[, crossCheck]]])
# 参数说明:
#         des1:查询描述符(第一幅图像的描述符),类型应为 np.ndarray。
#         des2:训练描述符(第二幅图像的描述符),类型应为 np.ndarray。
#         k:返回的最佳匹配个数。
#         mask:可选参数,掩膜,用于决定对哪些查询描述符进行匹配,默认为 None。
#         compactResult:可选参数,布尔值,指定是否返回紧凑的结果列表,默认为 True。
#         crossCheck:可选参数,布尔值,默认为 False。如果设置为 True,则匹配器只返回那些在两个方向上都满足条件的匹配结果,即查询描述符 A 与训练描述符 B 互为最佳匹配。
# 返回值:
#         matches:匹配结果列表,每个元素是一个列表,包含 k 个 DMatch 对象。如果设置了 compactResult 参数为 False,则返回每个查询描述符的 k 个最佳匹配结果。
#############################################################################################"""

"""#############################################################################################
# 函数功能:绘制特征匹配结果图像,将两幅图像中匹配的关键点用连线连接起来,并可选择绘制关键点。
# 函数说明:outImg = cv2.drawMatches(img1, keypoints1, img2, keypoints2, matches1to2[, matchColor[, singlePointColor[, matchesMask[, flags]]]])
# 参数说明:
#         img1:第一幅图像,类型应为 np.ndarray,通常是匹配时的查询图像。
#         keypoints1:第一幅图像的关键点列表,每个关键点是一个 cv2.KeyPoint 对象。
#         img2:第二幅图像,类型应为 np.ndarray,通常是匹配时的训练图像。
#         keypoints2:第二幅图像的关键点列表,每个关键点是一个 cv2.KeyPoint 对象。
#         matches1to2:匹配结果列表,每个元素是一个 DMatch 对象,表示一对匹配的关键点。
#         matchColor:可选参数,用于绘制匹配关键点之间连线的颜色,默认为随机颜色。
#         singlePointColor:可选参数,用于绘制单个关键点的颜色,默认为随机颜色。
#         matchesMask:可选参数,掩膜,用于决定绘制哪些匹配结果,默认为空,表示绘制所有匹配结果。
#         flags:可选参数,标志,用于设置绘制方式,默认为 cv2.DrawMatchesFlags_DEFAULT。
#                 可选取值为:
#                     - cv2.DrawMatchesFlags_DEFAULT:绘制两个源图像、匹配项和单个关键点,没有围绕关键点的圆以及关键点的大小和方向。
#                     - cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS:不会绘制单个关键点。
#                     - cv2.DrawMatchesFlags_DRAW_RICH_KEYPOINTS:在关键点周围绘制具有关键点大小和方向的圆圈。
# 返回值:
#         outImg:返回的绘制结果图像,图像中查询图像与训练图像中匹配的关键点之间的连线为彩色。
#############################################################################################"""

OpenCV特征匹配 SIFT demo(图解)
OpenCV特征检测之SIFT算法(原理详解)
OpenCV特征检测之特征匹配方式详解

(二十)图像缩放+镜像+平移+旋转+仿射变换+透视变换 —— cv2.resize()、cv2.getRotationMatrix2D()、cv2.getAffineTransform()、cv2.getPerspectiveTransform()、cv2.warpPerspective()、cv2.warpAffine()

在这里插入图片描述

import numpy as np
import cv2                          # opencv    读取图像默认为BGR
import matplotlib.pyplot as plt     # matplotlib显示图像默认为RGB

"""########## 读取图像 ##########"""
image = cv2.imread(r"image.jpg", 1)
height, width, channels = image.shape

"""########## 图像缩放 ##########"""
# (1)直接缩放
image_Height = int(height/2)
image_Width = int(width/2)
image_resize_dir = cv2.resize(src=image, dsize=(image_Width, image_Height))

# (2)使用邻域插值法进行缩放
image_resize_linear = np.zeros(shape=[image_Height, image_Width, 3], dtype=np.uint8)
for i in range(image_Height):
    for j in range(image_Width):
        iNew = i * (height * 1.0 / image_Height)
        jNew = j * (width * 1.0 / image_Width)
        image_resize_linear[i, j] = image[int(iNew), int(jNew)]
        
"""########## 图像镜像 ##########"""
image_mirror = np.zeros(shape=[height * 2, width, channels], dtype=np.uint8)
for i in range(height):
    for j in range(width):
        image_mirror[i, j] = image[i, j]
        image_mirror[height * 2 - i - 1, j] = image[i, j]
for i in range(width):
    image_mirror[height, i] = (0, 0, 255)
    
"""########## 图像平移 ##########"""
image_translation = np.zeros(shape=image.shape, dtype=np.uint8)
for i in range(height):
    for j in range(width - 200):           # 平移大小:100
        image_translation[i, j+100] = image[i, j]  # 新图像截取原图像部分数据
        
"""########## 图像旋转 ##########"""
matRotate = cv2.getRotationMatrix2D(center=(height*0.5, width*0.5), angle=45, scale=0.7)  # 获取旋转矩阵
image_rotate = cv2.warpAffine(src=image, M=matRotate, dsize=(height, width))              # 仿射变换

"""########## 仿射变换 ##########"""
matSrc_A = np.float32([[0, 0], [0, height-1], [width-1, 0]])                    # 输入图像的三个点坐标
matDst_A = np.float32([[50, 50], [100, height-50], [width-200, 100]])           # 仿射变换的三个点坐标
matAffine = cv2.getAffineTransform(matSrc_A, matDst_A)                          # 获取仿射变换矩阵
image_affine = cv2.warpAffine(src=image, M=matAffine, dsize=(height, width))    # 仿射变换

"""########## 透视变换 ##########"""
matSrc_P = np.float32([[50, 50], [100, height-50], [width-200, 100], [width-200, height-50]])  # 输入图像的四个点坐标
matDst_P = np.float32([[0, 0], [0, height-1], [width-1, 0], [width-1, height-1]])                 # 透视变换的四个点坐标
matWarp = cv2.getPerspectiveTransform(src=matSrc_P, dst=matDst_P)                               # 获取透视变换矩阵
image_Perspective = cv2.warpPerspective(src=image, M=matWarp, dsize=(height, width))             # 透视变换
image_Perspective_hstack = np.hstack(tup=(image_affine, image_Perspective))

plt.subplot(241),       plt.imshow(image, 'gray'),                     plt.title('image')
plt.subplot(242),       plt.imshow(image_resize_dir, 'gray'),          plt.title('image_resize_dir (coordinates)')
plt.subplot(243),       plt.imshow(image_resize_linear, 'gray'),       plt.title('image_resize_linear (coordinates)')
plt.subplot(244),       plt.imshow(image_mirror, 'gray'),              plt.title('image_mirror')
plt.subplot(245),       plt.imshow(image_translation, 'gray'),         plt.title('image_translation')
plt.subplot(246),       plt.imshow(image_rotate, 'gray'),              plt.title('image_rotate')
plt.subplot(247),       plt.imshow(image_affine, 'gray'),              plt.title('image_affine')
plt.subplot(248),       plt.imshow(image_Perspective_hstack, 'gray'),  plt.title('image_Perspective')
plt.show()

"""#####################################################################################################################
# 函数功能:图像缩放 ———— 可以将图像缩放到指定的尺寸,也可以根据缩放比例进行缩放。
# 函数说明:resized_image = cv2.resize(src, dsize, fx=0, fy=0, interpolation=cv2.INTER_LINEAR)
# 参数说明:       
#         src             要调整大小的输入图像。
#         dsize           输出图像的大小 
#                                 (1)矩阵参数缩放到指定大小(width,height);           例如:cv2.resize(img_dog, (500, 414))
#                                 (2)矩阵参数为(0,0),原图像缩放倍数通过fx, fy来控制;    例如:cv2.resize(img_dog, (0, 0), fx=4, fy=4)
#         fx:             水平方向的缩放比例。
#         fy:             垂直方向的缩放比例。
#         interpolation:  插值方法,用于指定图像缩放时的插值算法
#                                 cv2.INTER_NEAREST:最近邻插值。
#                                 cv2.INTER_LINEAR:双线性插值(默认)。
#                                 cv2.INTER_CUBIC:双立方插值。
#                                 cv2.INTER_AREA:区域插值。
#                                 cv2.INTER_LANCZOS4:Lanczos插值。
#####################################################################################################################"""

"""#####################################################################################################################
# 函数功能:用于获取旋转矩阵 ———— 该矩阵可以将图像围绕指定中心点进行旋转。通常与 cv2.warpAffine 函数一起使用,用于实现图像的旋转操作。
# 函数说明:M = cv2.getRotationMatrix2D(center, angle, scale)
# 参数说明:     
#         center:       旋转中心点的坐标,通常是一个元组 (x, y)。
#         angle:        旋转角度,以逆时针方向为正方向。
#         scale:        缩放比例,默认为 1。如果为 1,则表示不进行缩放。
#####################################################################################################################"""

"""#####################################################################################################################
# 函数功能:用于获取仿射变换矩阵 ———— 用于进行二维图像的仿射变换操作,如平移、旋转、缩放等。
# 函数说明:M = cv2.getAffineTransform(srcPoints, dstPoints)
# 输入参数:
#         srcPoints:    原始图像中的三个非共线点的坐标,通常是一个形如 (3, 2) 的二维数组,每一行表示一个点的 (x, y) 坐标。
#         dstPoints:    目标图像中的三个对应点的坐标,通常是一个形如 (3, 2) 的二维数组,每一行表示一个点的 (x, y) 坐标。
#####################################################################################################################"""

"""#####################################################################################################################
# 函数功能:用于获取透视变换矩阵 ———— 用于进行二维图像的透视变换操作,如投影、旋转、缩放等。
# 函数说明:M = cv2.getPerspectiveTransform(srcPoints, dstPoints)
# 输入参数:
#         srcPoints:    原始图像中的四个非共线点的坐标,通常是一个形如 (4, 2) 的二维数组,每一行表示一个点的 (x, y) 坐标。
#         dstPoints:    目标图像中的四个对应点的坐标,通常是一个形如 (4, 2) 的二维数组,每一行表示一个点的 (x, y) 坐标。
#####################################################################################################################"""

"""#####################################################################################################################
# 函数功能:仿射变换 ———— 根据给定的仿射变换矩阵对图像进行平移、旋转、缩放等操作。
# 函数说明:dst = cv2.warpAffine(src, M, dsize, flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_CONSTANT, borderValue=0)
#
# 函数功能:透视变换 ———— 透视变换可以对图像进行投影、旋转、缩放等操作,常用于校正图像的透视畸变或者进行图像矫正。
# 函数说明:dst = cv2.warpPerspective(src, M, dsize, flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_CONSTANT, borderValue=(0, 0, 0))
# 
# 输入参数:
#         src:      原始图像。
#         M:        变换矩阵
#         dsize:    输出图像的大小,可以是一个元组 (width, height),也可以是一个 None。
#         flags:    插值方法,用于指定图像变换时的插值算法,可以是以下几种之一:
#                       cv2.INTER_NEAREST:最近邻插值。
#                       cv2.INTER_LINEAR:双线性插值(默认)。
#                       cv2.INTER_CUBIC:双立方插值。
#                       cv2.INTER_AREA:区域插值。
#                       cv2.INTER_LANCZOS4:Lanczos插值。
#         borderMode:边界模式,用于指定处理图像边界时的填充方式,可以是以下几种之一:
#                       cv2.BORDER_CONSTANT:常数填充(默认),使用 borderValue 参数指定填充值。
#                       cv2.BORDER_REPLICATE:复制边界像素填充。
#                       cv2.BORDER_REFLECT:反射边界填充。
#                       cv2.BORDER_WRAP:环绕边界填充。
#         borderValue:当 borderMode 设置为 cv2.BORDER_CONSTANT 时使用,用于指定填充的常数值。
#
# 备注:cv2.warpAffine 与 cv2.getRotationMatrix2D / cv2.getAffineTransform 搭配使用。
# 备注:cv2.warpPerspective 与 cv2.getPerspectiveTransform 搭配使用。
#####################################################################################################################"""

  • 322
    点赞
  • 2632
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 55
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 55
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

胖墩会武术

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值