Python 图像处理

Pillow

opencv

参考文档

1、前戏

OpenCV是一个基于BSD许可(开源)发行的跨平台计算机视觉库,可以运行在Linux、Windows、Android和Mac OS操作系统上。它轻量级而且高效——由一系列 C 函数和少量 C++ 类构成,同时提供了Python、Ruby、MATLAB等语言的接口,实现了图像处理和计算机视觉方面的很多通用算法(百度百科)。

(1) 安装

pip install opencv-python
pip install opencv-python -i https://pypi.tuna.tsinghua.edu.cn/simple/ 

(2) 基本使用

  • cv2.IMREAD_COLOR:彩色图像
  • cv2.IMREAD_GRAYSCALE:灰度图像
import cv2
from opencv_demo.utils.src import show_image

image_path = '../static/images/demo_01.jpg'
# 读取原图
img = cv2.imread(image_path)
# 展示原图
show_image('Original', img)

2、图片处理

(1) 截取部分图像数据

import cv2
from opencv_demo.utils.src import show_image


def intercept_image(image_path):
    # 读取原图
    img = cv2.imread(image_path)
    # 展示原图
    show_image('Original', img)
    # 截取部分图片数据
    image = img[250:650, 0:400]
    show_image('image', image)

if __name__ == '__main__':
    image_path = '../static/images/demo_01.jpg'
    intercept_image(image_path)

(2) 颜色通道提取

import cv2
from opencv_demo.utils.src import show_image

def extract_image(image_path):
    # 读取原图
    img = cv2.imread(image_path)

    b, g, r = cv2.split(img)
    print("----- 读取蓝色通道 -----: \n", b)
    print("----- 读取绿色通道 -----: \n", g)
    print("----- 读取红色通道 -----: \n", r)
    print("----- 读取蓝色通道shape -----: ", b.shape)
    print("----- 读取绿色通道shape -----: ", g.shape)
    print("----- 读取红色通道shape -----: ", r.shape)

    # 只保留B 并显示图片
    cur_img = img.copy()
    cur_img[:, :, 1] = 0
    cur_img[:, :, 2] = 0
    show_image('b_cur_img', cur_img)

    # 只保留G 并显示图片
    cur_img = img.copy()
    cur_img[:, :, 0] = 0
    cur_img[:, :, 2] = 0
    show_image('g_cur_img', cur_img)

    # 只保留R 并显示图片
    cur_img = img.copy()
    cur_img[:, :, 0] = 0
    cur_img[:, :, 1] = 0
    show_image('r_cur_img', cur_img)


if __name__ == '__main__':
    image_path = "../static/images/demo_01.jpg"
    extract_image(image_path)

(3) 边界填充

  • BORDER_REPLICATE:复制法,也就是复制最边缘像素。
  • BORDER_REFLECT:反射法,对感兴趣的图像中的像素在两边进行复制例如:fedcba|abcdefgh|hgfedcb
  • BORDER_REFLECT_101:反射法,也就是以最边缘像素为轴,对称,gfedcb|abcdefgh|gfedcba
  • BORDER_WRAP:外包装法cdefgh|abcdefgh|abcdefg
  • BORDER_CONSTANT:常量法,常数值填充。
import cv2
from matplotlib import pyplot
from opencv_demo.utils.src import show_image

# 解决中文显示问题
pyplot.rcParams['font.sans-serif'] = ['SimHei']
pyplot.rcParams['axes.unicode_minus'] = False

def boundary_fill_image(image_path):
    # 读取原图
    img = cv2.imread(image_path)
    # 展示原图
    show_image('Original', img)

    top_size, bottom_size, left_size, rigth_size = (50, 50, 50, 50)

    # 方式一: 复制法,也就是复制最边缘像素
    replicate = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, rigth_size, borderType=cv2.BORDER_REPLICATE)

    # 方式二: 反射法,对感兴趣的图像中的像素在两边进行复制例如:sgdhsdgyh|abcdefgh|hgfedcb
    reflect = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, rigth_size, borderType=cv2.BORDER_REFLECT)

    # 方式三: 反射法,也就是以最边缘像素为轴,对称:sdtsydt|sgdhsdgyh|sdgysg
    reflect101 = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, rigth_size, borderType=cv2.BORDER_REFLECT101)

    # 方式四: 外包装法:sdtsydt|sgdhsdgyh|sdgysg
    wrap = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, rigth_size, borderType=cv2.BORDER_WRAP)

    # 方式五: 常量法
    constant = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, rigth_size, borderType=cv2.BORDER_CONSTANT, value=0)

    # 将图片放在一起展示
    pyplot.subplot(231), pyplot.imshow(img, 'gray'), pyplot.title("原图")
    pyplot.subplot(232), pyplot.imshow(replicate, 'gray'), pyplot.title("边缘复制法")
    pyplot.subplot(233), pyplot.imshow(reflect, 'gray'), pyplot.title("边缘反射法")
    pyplot.subplot(234), pyplot.imshow(reflect101, 'gray'), pyplot.title("边缘反射法")
    pyplot.subplot(235), pyplot.imshow(wrap, 'gray'), pyplot.title("边缘外包装法")
    pyplot.subplot(236), pyplot.imshow(constant, 'gray'), pyplot.title("边缘常量法")
    pyplot.show()

if __name__ == '__main__':
    image_path = '../static/images/demo_01.jpg'
    boundary_fill_image(image_path)

(4) 数值计算

import cv2

# 读取图片
img_1 = cv2.imread('../static/images/demo_01.jpg')
img_2 = cv2.imread('../static/images/demo_01.jpg')

# 打印前五行数据
print("打印前五行数据: \n", img_1[:5, :, 0])

# 给每个像素点的值加10
img1 = img_1 + 10

# 打印前五行数据
print("给每个像素点的值加10: \n", img1[:5, :, 0])
print("两个图片的像素点相加: \n", (img_1 + img1)[:5, :, 0])
print("两个图片的像素点相加: \n", cv2.add(img_1, img1)[:5, :, 0])

(5) 图像融合

cv2.addWeighted()

语法:

CV2.addWeighted(src1, alpha, src2, beta, gamma, dst=None, dtype=None)

参数说明

参数 说明
src1 第一个输入图像数组(numpy.ndarray)
alpha 透明度(权重)
src2 第二个输入图像数组(numpy.ndarray)
beta 透明度(权重)
gamma 把标量加到每个和中
dst 输出数组,与输入数组具有相同的通道大小和数量
dtype 可选的输出数组的深度;当两个输入数组具有相同的深度时,dtype,可以设置为-1,相当于src1.depth()。

实例

import cv2
from opencv_demo.utils.src import show_image

def image_fusion(image_path_1, image_path_2):
    # 读取第一张原图,并重置大小
    img_1 = cv2.imread(image_path_1)
    img_1 = cv2.resize(img_1, (1000, 562))
    show_image("Original_1", img_1)

    # 读取第二张原图,并重置大小
    img_2 = cv2.imread(image_path_2)
    img_2 = cv2.resize(img_2, (1000, 562))
    show_image("Original_2", img_2)

    # 图像融合
    res = cv2.addWeighted(img_1, 0.3, img_2, 0.6, 0)
    show_image("res", res)

if __name__ == '__main__':
    image_path_1 = "../static/images/demo_02.jpg"
    image_path_2 = "../static/images/demo_03.jpg"
    image_fusion(image_path_1, image_path_2)

(6) 图像阈值

语法

ret, image = cv2.threshold(src, thresh, maxval, type, dst=None)

参数说明

参数 说明
src 输入图,只能输入单通道图像,通常来说为灰度图
thresh 阈值
maxval 当像素值超过了阈值(或者小于阈值,根据type来决定),所赋予的值
type 二值化操作类型
dst 输出图

type: 二值化操作类型包括以下五种类型:

类型 说明
cv2.THRESH_BINARY 超过阈值部分取maxval(最大值),否则取0
cv2.THRESH_BINARY_INV THRESH_BINARY的翻转
cv2.THRESH_TRUNC 大于阈值部分设为阈值,否则不变
cv2.THRESH_TOZERO 大于阈值部分不改变,否则设为0
cv2.THRESH_TOZERO_INV THRESH_TOZERO的翻转

案例

import cv2
from matplotlib import pyplot

# 解决中文显示问题
pyplot.rcParams['font.sans-serif'] = ['SimHei']
pyplot.rcParams['axes.unicode_minus'] = False

def image_threshold(image_path):
    # 读取原图,并转为灰度图
    image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)

    # cv2.THRESH_BINARY : 超过阈值部分取maxval(最大值),否则取0
    ret_1, image_1 = cv2.threshold(image, 127, 255, cv2.THRESH_BINARY)

    # cv2.THRESH_BINARY_INV : THRESH_BINARY的翻转
    ret_2, image_2 = cv2.threshold(image, 127, 255, cv2.THRESH_BINARY_INV)

    # cv2.THRESH_TRUNC : 大于阈值部分设为阈值,否则不变
    ret_3, image_3 = cv2.threshold(image, 127, 255, cv2.THRESH_TRUNC)

    # cv2.THRESH_TOZERO : 大于阈值部分不改变,否则设为0
    ret_4, image_4 = cv2.threshold(image, 127, 255, cv2.THRESH_TOZERO)

    # cv2.THRESH_TOZERO_INV   # THRESH_TOZERO的翻转
    ret_5, image_5 = cv2.threshold(image, 127, 255, cv2.THRESH_TOZERO_INV)

    title_list = ['原图', 'BINARY', 'BINARY_INV', 'THRESH_TRUNC', 'TOZERO', 'TOZERO_INV']
    image_list = [image, image_1, image_2, image_3, image_4, image_5]
    for i in range(6):
        pyplot.subplot(2, 3, i + 1), pyplot.imshow(image_list[i], 'gray')
        pyplot.title(title_list[i])
        pyplot.xticks([]), pyplot.yticks([])

    pyplot.show()

if __name__ == '__main__':
    image_path = '../static/images/demo_01.jpg'
    image_threshold(image_path)

(7) 平滑操作

可选方法

方法 说明
cv2.blur 均值滤波 - 简单的平均卷积操作
cv2.boxFilter 方框滤波 - 基本和滤波一样,可以选择归一化
cv2.GaussianBlur 高斯滤波 - 滤波模糊的卷积核里的数值是满足高斯分布,相当于更重视中间的
cv2.medianBlur 中值滤波 - 相当于用中值代替

案例

import cv2
import numpy as np
from opencv_demo.utils.src import show_image

def blur_image(img):
    """
    均值滤波
    简单的平均卷积操作
    """
    blur = cv2.blur(img, (3, 3))
    show_image('blur', blur)
    return blur


def box_image_true(img):
    """
    方框滤波
    基本和滤波一样,可以选择归一化
    """
    box = cv2.boxFilter(img, -1, (3, 3), normalize=True)
    show_image('box', box)
    return box


def box_image_false(img):
    """
    方框滤波
    基本和滤波一样,可以选择归一化,容易越界
    """
    box = cv2.boxFilter(img, 3, (3, 3), normalize=False)
    show_image('box', box)
    return box


def aussian_image(img):
    """
    高斯滤波
    滤波模糊的卷积核里的数值是满足高斯分布,相当于更重视中间的
    """
    aussian = cv2.GaussianBlur(img, (5, 5), 1)
    show_image('aussian', aussian)
    return aussian


def median_image(img):
    """
    中值滤波
    相当于用中值代替
    """
    median = cv2.medianBlur(img, 5)
    show_image('median', median)
    return median


if __name__ == '__main__':
    path = "../static/images/demo_04.jpg"
    img = cv2.imread(path)
    
    blur = blur_image(img)
    box_true = box_image_true(img)
    box_false = box_image_false(img)
    aussian = aussian_image(img)
    median = median_image(img)
    
    res = np.hstack((img, blur, box_true, aussian, median))
    show_image("all", res)

(8) 腐蚀操作

语法

erosion = cv2.erode(src, kernel, dst=None, anchor=None, iterations=None, borderType=None, borderValue=None)

参数说明

参数 说明
src 输入图像;通道的数量可以是任意的,但是深度应该是CV_8U、CV_16U、CV_16S、CV_32F或CV_64F之一。
kernel 用于腐蚀的结构元素
dst 输出与src相同大小和类型的图像。
anchor 锚点在元素中的位置
iterations 应用侵蚀的次数
borderType 像素外推方法
borderValue 边界为常量时的边界值

案例一:

import cv2
from opencv_demo.utils.src import show_image

def erosion_image(img):
    kernel = np.ones((5,5),np.uint8)
    erosion = cv2.erode(img,kernel,iterations=1)
    show_image("erosion",erosion)

if __name__ == '__main__':
    path = '../static/images/demo_05.jpg'
    img = cv2.imread(path)
    erosion_image(img)

案例二:

import cv2
import numpy as np
from opencv_demo.utils.src import show_image

# 腐蚀操作
def erosion_image(img):
    kernel = np.ones((20, 20), np.uint8)
    erosion_1 = cv2.erode(img, kernel, iterations=1)
    erosion_2 = cv2.erode(img, kernel, iterations=2)
    erosion_3 = cv2.erode(img, kernel, iterations=3)
    res = np.hstack((erosion_1, erosion_2, erosion_3))
    show_image("res", res)

if __name__ == '__main__':
    path = '../static/images/demo_06.jpg'
    img = cv2.imread(path)
    show_image("img", img)
    erosion_image(img)

(9) 膨胀操作

语法

dilate = dilate(src, kernel, dst=None, anchor=None, iterations=None, borderType=None, borderValue=None)

参数说明

参数 说明
src 输入图像;通道的数量可以是任意的,但是深度应该是CV_8U、CV_16U、CV_16S、CV_32F或CV_64F之一
kernel 用于膨胀的核结构元素
dst 输出与src相同大小和类型的图像
anchor 锚点在单元内的锚点位置;缺省值(-1,-1)表示,锚点在元素中心。
iterations 施加膨胀的次数
borderType 像素外推方法,参见#BorderTypes。#不支持BORDER_WRAP。
borderValue 边界为常量时的边界值

案例

import cv2
import numpy as np
from opencv_demo.utils.src import show_image


# 腐蚀去掉毛刺
def erosion_image(img):
    kernel = np.ones((3, 3), np.uint8)
    dige_erosion = cv2.erode(img, kernel, iterations=1)
    show_image("erosion", dige_erosion)
    return dige_erosion


# 膨胀操作
def dilate_image(img):
    kernel = np.ones((3, 3), np.uint8)
    dige_dilate = cv2.dilate(img, kernel, iterations=1)
    show_image("erosion", dige_dilate)
    return dige_dilate


if __name__ == '__main__':
    path = '../static/images/demo_05.jpg'
    img = cv2.imread(path)
    
    dige_erosion = erosion_image(img)
    dige_dilate = dilate_image(dige_erosion)
    
    res = np.hstack((img, dige_erosion, dige_dilate))
    show_image("res", res)

(10) 开闭运算

语法

xxx = cv2.morphologyEx(src, op, kernel, dst=None, anchor=None, iterations=None, borderType=None, borderValue=None)

属性说明

属性 说明
src 源图像。信道的数量可以是任意的。深度应该是 CV_8U, CV_16U, CV_16S, CV_32F或CV_64F
op 形态操作的类型,请参阅 #MorphTypes
kernel 结构元素。它可以使用 #getStructuringElement创建。
dst 目标图像的大小和类型与源图像相同。
anchor 与内核的锚定位置。负值表示锚点在核中心。
iterations 施加侵蚀和膨胀的次数。
borderType 像素外推方法,参见 #BorderTypes。不支持#BORDER_WRAP。
borderValue 边界为常量时的边界值。默认值有特殊含义。

案例

import cv2
import numpy as np
from opencv_demo.utils.src import show_image


# 开:先腐蚀再膨胀
def open_image(img):
    kernel = np.ones((5,5),np.uint8)
    opening = cv2.morphologyEx(img,cv2.MORPH_OPEN,kernel)
    show_image("opening",opening)
    return opening

# 闭:先膨胀,再腐蚀
def close_image(img):
    kernel = np.ones((5, 5), np.uint8)
    closeing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
    show_image("closeing", closeing)
    return closeing

if __name__ == '__main__':
    path = '../static/images/demo_05.jpg'
    img = cv2.imread(path)
    opening = open_image(img)
    close_image = close_image(img)
    res = np.hstack((img, opening, close_image))
    show_image("res", res)

(11) 梯度运算

11-1 Laplacian 算子

语法:

laplacian = cv2.Laplacian(src, ddepth, dst=None, ksize=None, scale=None, delta=None, borderType=None)

参数说明

属性 说明
src src源图像
ddepth 目标图像的深度,一般为-1
dst 目标图像的大小和通道数目与src相同
ksize 孔径大小用于计算滤波器的二阶导数。看到 getDerivKernels,大小必须为正奇数。
scale 可选的计算拉普拉斯值的比例因子。默认情况下,不应用伸缩。详情请参阅#getDerivKernels。
delta 增量可选,在结果存储到dst之前添加到结果中的增量值。
borderType 像素外推方法,参见#BorderTypes。不支持 #BORDER_WRAP。

案例

import cv2
from opencv_demo.utils.src import show_image

# Laplacian 算子
def laplacian_image(img):
    laplacian = cv2.Laplacian(img, cv2.CV_64F)
    laplacian = cv2.convertScaleAbs(laplacian)
    show_image('laplacian', laplacian)
    return laplacian

if __name__ == '__main__':
    path = '../../static/images/demo_01.jpg'
    img = cv2.imread(path, 0)
    laplacian = laplacian_image(img)

11-2 Scharr 算子

语法

scharr = cv2.Scharr(src, ddepth, dx, dy, dst=None, scale=None, delta=None, borderType=None)

参数说明

属性 说明
src 输入图像
ddepth 输出图像的大小和通道数目与src相同。
dx dx的阶导数x。
dy dy的导数y的阶。
dst 输出图像的大小和通道数目与src相同。
scale 计算的导数值的可选比例因子;缺省情况下,没有可伸缩性
delta 可选的增量值,在将结果存储到dst之前添加到结果中。
borderType 像素外推方法,参见#BorderTypes。不支持 #BORDER_WRAP。

案例

import cv2
from opencv_demo.utils.src import show_image

def scharrx_image(img):
    scharrx = cv2.Scharr(img, cv2.CV_64F, 1, 0)
    scharrx = cv2.convertScaleAbs(scharrx)
    show_image('scharrx', scharrx)
    return scharrx


def scharry_image(img):
    scharry = cv2.Scharr(img, cv2.CV_64F, 0, 1)
    scharry = cv2.convertScaleAbs(scharry)
    show_image('scharry', scharry)
    return scharry


def scharrxy_image(scharrx, scharry):
    scharrxy = cv2.addWeighted(scharrx, 0.5, scharry, 0.5, 0)
    show_image('scharrxy', scharrxy)


if __name__ == '__main__':
    path = '../../static/images/demo_01.jpg'
    img = cv2.imread(path, 0)
    scharrx = scharrx_image(img)
    scharry = scharry_image(img)
    scharrxy_image(scharrx, scharry)

11-3 Sobel 算子

语法

sobel = cv2.Sobel(src, ddepth, dx, dy, dst=None, ksize=None, scale=None, delta=None, borderType=None)

参数说明

属性 说明
src 输入图像
ddepth 输出图像的大小和通道数目与src相同。
dx dx的阶导数x。
dy dy的导数y的阶。
dst 输出图像的大小和通道数目与src相同。
ksize 扩展Sobel核的大小;它一定是1 3 5 7。
scale 计算的导数值的可选比例因子;缺省情况下,没有可伸缩性
delta 可选的增量值,在将结果存储到dst之前添加到结果中。
borderType 像素外推方法,参见#BorderTypes。不支持 #BORDER_WRAP。

案例

import cv2
import numpy as np
from opencv_demo.utils.src import show_image

def sobelx_image(img):
    sobelx = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3)
    sobelx = cv2.convertScaleAbs(sobelx)
    show_image('sobelx', sobelx)
    return sobelx

def sobely_image(img):
    sobely = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3)
    sobely = cv2.convertScaleAbs(sobely)
    show_image('sobely', sobely)
    return sobely

def sobelxy_image(sobelx, sobely):
    sobelxy = cv2.addWeighted(sobelx, 0.5, sobely, 0.5, 0)
    show_image('sobelxy', sobelxy)

# 不建议
def sobel_image(img):
    sobel = cv2.Sobel(img, cv2.CV_64F, 1, 1, ksize=3)
    sobel = cv2.convertScaleAbs(sobel)
    show_image('sobel', sobel)

if __name__ == '__main__':
    path = '../../static/images/demo_01.jpg'
    img = cv2.imread(path, 0)
    sobelx = sobelx_image(img)
    sobely = sobely_image(img)
    sobelxy_image(sobelx, sobely)

(12) 礼帽与黑帽

语法:

hat = cv2.morphologyEx(src, op, kernel, dst=None, anchor=None, iterations=None, borderType=None, borderValue=None)

参数说明

属性 说明
src 源图像
op 形态学操作的op类型,参见#MorphTypes
kernel 内核结构元素。它可以使用#getStructuringElement创建。
dst 目标图像的大小和类型与源图像相同
anchor 内核的锚位置。负值表示锚点在核中心。
iterations 应用侵蚀和膨胀的次数。
borderType 像素外推方法,参见#BorderTypes。不支持#BORDER_WRAP。
borderValue 边界为常量时的边界值。默认值有特殊含义。

案例

import cv2
import numpy as np
from opencv_demo.utils.src import show_image

# 礼帽 = 原始输入 - 开运算结果
def tophat_image(img):
    kernel = np.ones((5, 5), np.uint8)
    tophat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)
    show_image('tophat', tophat)

# 黑帽 = 闭运算结果 - 原始输入
def blackhat_image(img):
    kernel = np.ones((5, 5), np.uint8)
    blackhat = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel)
    show_image('blackhat', blackhat)

if __name__ == '__main__':
    path = '../static/images/demo_05.jpg'
    img = cv2.imread(path)
    tophat_image(img)
    blackhat_image(img)

(13) 边缘检测算法

语法

cv2.Canny(image, threshold1, threshold2, edges=None, apertureSize=None, L2gradient=None)

参数

参数 说明
image 8位输入图像。
threshold1 迟滞过程的第一个阈值
threshold2 迟滞量的第二个阈值
edges 输出边缘映射;单通道8位图像,与图像大小相同。
apertureSize Sobel操作符的孔径大小
L2gradient 指示是否更精确的\f$ l2 \f$范数

案例

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

1. 高斯滤波器
    |——                         ——|
    |  0.0924   0.1192  0.0924    |
H = |  0.1192   0.1538  0.1192    |   <-- 这里还进行归一化处理
    |  0.0924   0.1192  0.0924    |
    |——                         ——|
"""

import cv2
import numpy as np
from opencv_demo.utils.src import show_image

def canny_1_image(img):
    canny_1 = cv2.Canny(img, 80, 150)
    canny_2 = cv2.Canny(img, 50, 100)
    res = np.hstack((canny_1, canny_2))
    show_image('res_1', res)

def canny_2_image(img):
    canny_1 = cv2.Canny(img, 120, 250)
    canny_2 = cv2.Canny(img, 50, 100)
    res = np.hstack((canny_1, canny_2))
    show_image('res_2', res)

if __name__ == '__main__':
    path = '../static/images/demo_01.jpg'
    img = cv2.imread(path, 0)
    canny_1_image(img)
    canny_2_image(img)

(14) 图像金字塔

14-1 拉普拉斯金字塔

向上采样法

语法

up = cv2.pyrUp(src, dst=None, dstsize=None, borderType=None)

参数

参数 说明
src 输入图像
dst 输出图像。它具有指定的大小和与src相同的类型
dstsize 输出图像大小
borderType 像素外推方法,参见#BorderTypes(只支持#BORDER_DEFAULT)

向下采样法

语法

down = cv2.pyrDown(src, dst=None, dstsize=None, borderType=None)

参数

参数 说明
src 输入图像
dst 输出图像。它具有指定的大小和与src相同的类型
dstsize 输出图像大小
borderType 像素外推方法,参见#BorderTypes(只支持#BORDER_DEFAULT)

案例

import cv2
from opencv_demo.utils.src import show_image

# 向上采样法 (放大)
def up_image(img):
    up = cv2.pyrUp(img)
    show_image('up', up)

# 向下采样法 (缩小)
def down_image(img):
    down = cv2.pyrDown(img)
    show_image('down', down)

# 先缩小一半,再放大一倍
def down_up_image(img):
    down = cv2.pyrDown(img)
    down_up = cv2.pyrUp(down)
    l_l = img - down_up
    show_image('l_l', l_l)

if __name__ == '__main__':
    path = '../../static/images/demo_02.jpg'
    # 读取原图,并转换为灰度图
    img = cv2.imread(path, 0)
    # 重置图片大小
    img = cv2.resize(img, (1000, 562))

    up_image(img)
    down_image(img)
    down_up_image(img)

14-2 高斯金字塔

向上采样法

语法

up = cv2.pyrUp(src, dst=None, dstsize=None, borderType=None)

参数

参数 说明
src 输入图像
dst 输出图像。它具有指定的大小和与src相同的类型
dstsize 输出图像大小
borderType 像素外推方法,参见#BorderTypes(只支持#BORDER_DEFAULT)

向下采样法

语法

down = cv2.pyrDown(src, dst=None, dstsize=None, borderType=None)

参数

参数 说明
src 输入图像
dst 输出图像。它具有指定的大小和与src相同的类型
dstsize 输出图像大小
borderType 像素外推方法,参见#BorderTypes(只支持#BORDER_DEFAULT)

案例

import cv2
from opencv_demo.utils.src import show_image

# 高斯金字塔:向上采样方法(放大)
def up_image(img):
    up = cv2.pyrUp(img)
    print(img.shape)
    print(up.shape)
    show_image('up', up)


# 高斯金字塔:向下采样方法(缩小)
def down_image(img):
    down = cv2.pyrDown(img)
    print(img.shape)
    print(down.shape)
    show_image('down', down)

if __name__ == '__main__':
    path = '../../static/images/demo_01.jpg'
    img = cv2.imread(path)
    up_image(img)
    down_image(img)

(15) 轮廓检测

15-1 外接圆

import cv2
from opencv_demo.utils.src import show_image

# 外接圆
def border_rectangle(img):
    # 将图像转换成灰度图像
    gary = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    # 将图像转化成二值图像
    ret, thresh = cv2.threshold(gary, 127, 255, cv2.THRESH_BINARY)

    # 从二值图像中提取轮廓, contours中包含检测到的所有轮廓,以及每个轮廓的坐标点
    contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
    cnt = contours[4]

    # (x, y): 圆心的坐标点; radius: 半径
    (x, y), radius = cv2.minEnclosingCircle(cnt)
    center = (int(x), int(y))
    radius = int(radius)
    print(center, radius)

    # 画圆
    img = cv2.circle(img, center, radius, (0, 255, 0), 1)

    # 显示图片
    show_image('img', img)


if __name__ == '__main__':
    path = '../../static/images/demo_07.jpg'
    img = cv2.imread(path)
    border_rectangle(img)

15-2 轮廓近似

import cv2
from opencv_demo.utils.src import show_image

# 轮廓近似
def contour_approximation(img):
    # 显示原图
    show_image('img', img)

    # 将图像转换为灰度图
    gary = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    # 转化成二值图像
    ret, thresh = cv2.threshold(gary, 127, 255, cv2.THRESH_BINARY)

    # 从二值图像中提取轮廓, contours中包含检测到的所有轮廓,以及每个轮廓的坐标点
    contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)

    # 找到指定的轮廓
    cnt = contours[1]

    # 复制图像, 画出边框为2像素的红色轮廓
    draw_img = img.copy()
    res = cv2.drawContours(draw_img, [cnt], -1, (0, 0, 255), 2)
    show_image('res', res)

    # 轮廓近似 epsilon 的值越小轮廓变化越小(0.1)
    epsilon = 0.1 * cv2.arcLength(cnt, True)
    approx = cv2.approxPolyDP(cnt, epsilon, True)
    draw_img = img.copy()
    res = cv2.drawContours(draw_img, [approx], -1, (0, 0, 255), 2)
    show_image('res', res)


if __name__ == '__main__':
    path = '../../static/images/demo_08.png'
    img = cv2.imread(path)
    contour_approximation(img)

15-3 边界矩形

import cv2
from opencv_demo.utils.src import show_image

# 边界矩形
def border_rectangle(img):
    # 将原图转换为灰度图
    gary = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    
    # 二值处理
    ret, thresh = cv2.threshold(gary, 127, 255, cv2.THRESH_BINARY)
    
    # 从二值图像中提取轮廓, contours中包含检测到的所有轮廓,以及每个轮廓的坐标点
    contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
    
    # 获取指定的轮廓
    cnt = contours[5]
    
    # x: x轴坐标; y: y轴坐标; w: 宽度; h: 高度
    x, y, w, h = cv2.boundingRect(cnt)
    print(x, y, w, h)
    img = cv2.rectangle(img, (x, y), (x + w, y + h), (1, 255, 0), 1)
    show_image('img', img)

    # 计算轮廓面积与边界矩形比
    area = cv2.contourArea(cnt)
    x, y, w, h = cv2.boundingRect(cnt)
    rect_area = w * h
    ectent = float(area) / rect_area
    print("轮廓面积与边界矩形比:", ectent)


if __name__ == '__main__':
    path = '../../static/images/demo_07.jpg'
    img = cv2.imread(path)
    border_rectangle(img)

15-4 画出所有轮廓

"""
cv2.findContours(img, mode, method)
img: 输入图像
mode:
    1. RETR_EXTERNAL : 只检索最外面的轮廓
    2. RETR_LIST : 检索所有轮廓,并将其保存到一张链表当中
    3. RETR_CCOMP : 检索所有轮廓,并将它们组织为两层;顶层是各部分的外观边界,第二层是空洞的边界
    4. RETR_TREE : 检索所有轮廓,并重构嵌套的真个层次

method:
    1. CHAIN_APPROX_NONE : 以Freeman链码的方式输出轮廓,所有其他方法输出多边型(顶点的序列)
    2. CHAIN_APPROX_SIMPLE : 压缩水平的,垂直的和斜的部分,也就是,函数值保留它们的终点部分
"""

import cv2
from opencv_demo.utils.src import show_image

def binary_image(img):
    # 将图片转换为灰度图
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    # 图像阈值(对图像进行二值处理)
    ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
    show_image('thresh', thresh)
    return thresh

# 从二值图像中提取轮廓, contours中包含检测到的所有轮廓,以及每个轮廓的坐标点
def tree_image(thresh):
    contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
    return contours

# 绘制轮廓
def draw_profile(img, contours):
    draw_img = img.copy()
    res = cv2.drawContours(draw_img, contours, -1, (0, 0, 255), 1)
    show_image('res', res)
    res = cv2.drawContours(draw_img, contours, 0, (0, 0, 255), 1)
    show_image('res', res)

if __name__ == '__main__':
    path = '../static/images/demo_07.jpg'
    img = cv2.imread(path)
    thresh = binary_image(img)
    contours = tree_image(thresh)
    draw_profile(img, contours)

(16) 模板匹配

16-1 匹配单个模板

import cv2
from matplotlib import pyplot

"""
语法:
    matchTemplate(image, templ, method, result=None, mask=None)
参数:
    image: 原图像
    templ:模板图形
    method:方法
        - TM_SQDIFF: 计算平方不同,计算出来的值越小,越相关
        - TM_CCORR: 计算相关性,计算出来的值越大,越相关
        - TM_CCOEFF: 计算相关系数,计算出来的值越大,越相关
        - TM_SQDIFF_NORMED: 计算归一化平方不同,计算出来的值越接近0, 越相关
        - TM_CCORR_NORMED: 计算归一化相关性,计算出来的值越接近1,越相关
        - TM_CCOEFF_NORMED: 计算归一化相关系数,计算出来的值越接近1, 越相关
"""

# 模板匹配
def template_match(img, template):
    # 获取模板的高度和宽度
    h, w = template.shape[:2]
    print("模板的宽高: ", h, w)

    res = cv2.matchTemplate(img, template, cv2.TM_CCORR_NORMED)
    print("模板在原图上的位置: ", res.shape)

    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
    print("最小值: ", min_val)
    print("最大值: ", max_val)
    print("最小坐标: ", min_loc)
    print("最大坐标: ", max_loc)

    # 定义方法列表
    methods = ['cv2.TM_CCOEFF', 'cv2.TM_CCOEFF_NORMED', 'cv2.TM_CCORR', 'cv2.TM_CCORR_NORMED', 'cv2.TM_SQDIFF', 'cv2.TM_SQDIFF_NORMED']
    for meth in methods:
        img_2 = img.copy()
        # 匹配方法的真值
        method = eval(meth)
        # 模板匹配
        res = cv2.matchTemplate(img_2, template, method)
        min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
        if method in [cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED]:
            top_left = min_loc
        else:
            top_left = max_loc

        bottom_right = (top_left[0] + w, top_left[1] + h)

        # 画矩形
        cv2.rectangle(img_2, top_left, bottom_right, 255, 2)
        pyplot.subplot(121)
        pyplot.imshow(res, cmap='gray')
        pyplot.xticks([])
        pyplot.yticks([])

        pyplot.subplot(122)
        pyplot.imshow(img_2, cmap='gray')
        pyplot.xticks([])
        pyplot.yticks([])

        pyplot.suptitle(meth)
        pyplot.show()


if __name__ == '__main__':
    img_path = '../static/images/demo_09.jpg'
    img = cv2.imread(img_path, 0)
    template_path = '../static/images/template_01.jpg'
    template = cv2.imread(template_path, 0)
    template_match(img, template)

16-2 匹配多个模板

import cv2
from matplotlib import pyplot
import numpy
from opencv_demo.utils.src import show_image

# 模板匹配
def template_match(img, template):
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    template = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
    h, w = template.shape[:2]
    res = cv2.matchTemplate(img_gray, template, cv2.TM_CCOEFF_NORMED)
    threshold = 0.9
    # 取匹配程度大于80%的坐标
    loc = numpy.where(res >= threshold)
    for pt in zip(*loc[::-1]):  # *表示可选参数
        bottom_right = (pt[0] + w, pt[1] + h)
        cv2.rectangle(img, pt, bottom_right, (0, 0, 255), 1)
    show_image('img', img)


if __name__ == '__main__':
    img_path = '../../static/images/demo_10.jpg'
    img = cv2.imread(img_path)
    template_path = '../../static/images/template_02.jpg'
    template = cv2.imread(template_path)
    template_match(img, template)

(17) 直方图

17-1 基本使用

"""
直方图
语法: cv2.calcHist(images,channels,mask,histSize,ranges)
参数:
    images : 原图图像格式为uint8 或 float32,当传入函数时应用括号[] 括来例如[img]
    channels : 同样用括号括起来 它会告诉我们统幅图像的直方图。如果图像是灰度图它的值就是[0],如果是彩色图像的传入参数可以是[0][1][2]它们分别对应着BGR
    mask : 掩模图像,统计整幅图像的直方图就为None,但是如果你想统计图像某一处的直方图你就制作一个掩模图像并使用它,
    histSize : BIN的数目。也应用中括号括起来
    ranges : 像素值范围常围[0256]
"""

import cv2
from matplotlib import pyplot


# 直方图
def hist_image(img):
    hist = cv2.calcHist([img], [0], None, [256], [0, 256])
    print(hist.shape)
    pyplot.hist(img.ravel(), 256)
    pyplot.show()


# 各颜色的直方图
def hist_all_image(img):
    color = ('b', 'g', 'r')
    for i, col in enumerate(color):
        histr = cv2.calcHist([img], [i], None, [265], [0, 256])
        pyplot.plot(histr, color=col)
        pyplot.xlim([0, 256])
    pyplot.show()

if __name__ == '__main__':
    path = '../static/images/demo_01.jpg'
    img = cv2.imread(path)
    hist_image(img)
    hist_all_image(img)

17-2 直方图均衡化

import cv2
import numpy as np
from matplotlib import pyplot
from opencv_demo.utils.src import show_image

# 直方图
def hist_image(img):
    pyplot.hist(img.ravel(), 256)
    pyplot.show()

# 均衡化处理
def equ_image(img):
    equ = cv2.equalizeHist(img)
    pyplot.hist(equ.ravel(), 256)
    pyplot.show()
    res = np.hstack((img, equ))
    show_image('res', res)

if __name__ == '__main__':
    path = '../../static/images/demo_01.jpg'
    img = cv2.imread(path, 0)
    hist_image(img)
    equ_image(img)

17-3 自适应直方图均衡化

import cv2
import numpy as np
from matplotlib import pyplot
from opencv_demo.utils.src import show_image

# 直方图
def hist_image(img):
    pyplot.hist(img.ravel(), 256)
    pyplot.show()

# 自适应均衡化处理
def clahe_image(img):
    clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
    equ = cv2.equalizeHist(img)
    res_clahe = clahe.apply(img)
    res = np.hstack((img, equ, res_clahe))
    show_image('res', res)

if __name__ == '__main__':
    path = '../../static/images/demo_01.jpg'
    img = cv2.imread(path, 0)
    hist_image(img)
    clahe_image(img)

(18) 掩码操作

import cv2
import numpy as np
from matplotlib import pyplot
from opencv_demo.utils.src import show_image


# 掩码操作
def mask_image(img):
    # 创建mask
    mask = np.zeros(img.shape[:2], np.uint8)
    mask[100:500, 100:400] = 255
    show_image('mask', mask)

    # 与操作
    masked_img = cv2.bitwise_and(img, img, mask=mask)
    show_image('masked_img', masked_img)
    hist_full = cv2.calcHist([img], [0], None, [256], [0, 256])
    hist_mask = cv2.calcHist([img], [0], mask, [256], [0, 256])
    pyplot.subplot(223)
    pyplot.plot(hist_full)
    pyplot.plot(hist_mask)
    pyplot.xlim([0, 256])
    pyplot.show()

if __name__ == '__main__':
    path = '../static/images/demo_01.jpg'
    img = cv2.imread(path)
    mask_image(img)

(19) 傅里叶变换

19-1 低通滤波器

案例一

"""
低频:变化缓慢的灰度分量,例如一片大海
低通滤波器:只保留低频,会使图像变得模糊

opencv 中主要就是 cv2.dft() 和 cv2.idft() ,输入图像需要先转换成np.float32格式
得到的结果中频率为0时的部分会在左上角,通常要转换到中心位置,可以通过shift变换来实现
cv2.dft()返回的结果是双通道的(实部,虚部),通常还需要转换程图像格式才能展示(0,255)
"""
import cv2
import numpy as np
from matplotlib import pyplot

# 直方图
def magnitude_spectrum_image(img):
    # 图像需要先转换成np.float32格式
    img_float32 = np.float32(img)
    dft = cv2.dft(img_float32, flags=cv2.DFT_COMPLEX_OUTPUT)
    dft_shift = np.fft.fftshift(dft)
    magnitude_spectrum = 20 * np.log(cv2.magnitude(dft_shift[:, :, 0], dft_shift[:, :, 1]))
    pyplot.subplot(121)
    pyplot.imshow(img, cmap='Spectral')
    pyplot.title('Input Image')
    pyplot.xticks([])
    pyplot.yticks([])
    
    pyplot.subplot(122)
    pyplot.imshow(magnitude_spectrum, cmap='Spectral')
    pyplot.title('Magnitude Spectrum')
    pyplot.xticks([])
    pyplot.yticks([])
    pyplot.show()

if __name__ == '__main__':
    path = '../../static/images/demo_01.jpg'
    img = cv2.imread(path, 0)
    magnitude_spectrum_image(img)

案例二

import numpy as np
import cv2
from matplotlib import pyplot
from opencv_demo.utils.src import show_image

# 直方图
def magnitude_spectrum_image(img):
    # 图像需要先转换成np.float32格式
    img_float32 = np.float32(img)

    dft = cv2.dft(img_float32, flags=cv2.DFT_COMPLEX_OUTPUT)
    dft_shift = np.fft.fftshift(dft)

    rows, cols = img.shape
    # 获取图片中心位置
    crow, ccol = int(rows / 2), int(cols / 2)

    # 低通滤波器
    mask = np.zeros((rows, cols, 2), np.uint8)
    mask[crow - 30:crow + 30, ccol - 30:ccol + 30] = 1

    # IDFT
    fshift = dft_shift * mask
    f_ishift = np.fft.ifftshift(fshift)
    img_back = cv2.idft(f_ishift)
    img_back = cv2.magnitude(img_back[:, :, 0], img_back[:, :, 1])

    pyplot.subplot(121)
    pyplot.imshow(img, cmap='gray')
    pyplot.title('Input Image')
    pyplot.xticks([])
    pyplot.yticks([])

    pyplot.subplot(122)
    pyplot.imshow(img_back, cmap='gray')
    pyplot.title('Result')
    pyplot.xticks([])
    pyplot.yticks([])
    pyplot.show()

if __name__ == '__main__':
    path = '../../static/images/demo_01.jpg'
    img = cv2.imread(path, 0)
    magnitude_spectrum_image(img)

19-2 高通滤波器

"""
高频:变化剧烈的灰度分量,例如边界
高通滤波器:只保留高频,会使图像细节增强
"""
import numpy as np
import cv2
from matplotlib import pyplot

def high_pass_filter(img):
    # 图像需要先转换成np.float32格式
    img_float32 = np.float32(img)

    dft = cv2.dft(img_float32, flags=cv2.DFT_COMPLEX_OUTPUT)
    dft_shift = np.fft.fftshift(dft)
    rows, cols = img.shape
    
    # 获取图片中心位置
    crow, ccol = int(rows / 2), int(cols / 2)

    # 低通滤波器
    mask = np.ones((rows, cols, 2), np.uint8)
    mask[crow - 30:crow + 30, ccol - 30:ccol + 30] = 0

    # IDFT
    fshift = dft_shift * mask
    f_ishift = np.fft.ifftshift(fshift)
    img_back = cv2.idft(f_ishift)
    img_back = cv2.magnitude(img_back[:, :, 0], img_back[:, :, 1])

    pyplot.subplot(121)
    pyplot.imshow(img, cmap='gray')
    pyplot.title('Input Image')
    pyplot.xticks([])
    pyplot.yticks([])

    pyplot.subplot(122)
    pyplot.imshow(img_back, cmap='gray')
    pyplot.title('Result')
    pyplot.xticks([])
    pyplot.yticks([])
    pyplot.show()

if __name__ == '__main__':
    path = '../../static/images/demo_01.jpg'
    img = cv2.imread(path, 0)
    high_pass_filter(img)

3、视频处理

4、项目案例

(1) 图片人脸识别

import cv2

# 该函数负责展示图片,以及读取图片属性
def show_image(title, img):
    # 根据读取出来的BGR值显示图像
    cv2.imshow(title, img)
    # 等待时间,单位毫秒,0表示按任意键终止
    cv2.waitKey(0)
    # 关闭窗口
    cv2.destroyAllWindows()


def face_recognition(imagepath):
    # 读取图片
    img = cv2.imread(imagepath)
    # 展示原图
    show_image(title='img', img=img)
    # 将原图转为灰度图
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    # 展示灰度图
    show_image(title='gray', img=gray)
    # OpenCV人脸识别分类器(该文件无需下载安装的时候就有,注意你的路径)
    classifier = cv2.CascadeClassifier("F:/Python_Projects/PythonDataAnalysis/venv/Lib/site-packages/cv2/data/haarcascade_frontalface_default.xml")
    # 定义绘制颜色
    color = (0, 0, 255)
    # 调用识别人脸
    faceRects = classifier.detectMultiScale(gray, scaleFactor=1.2, minNeighbors=3, minSize=(32, 32))
    if len(faceRects):  # 大于0则检测到人脸
        for faceRect in faceRects:  # 单独框出每一张人脸
            x, y, w, h = faceRect
            # 框出人脸
            cv2.rectangle(img, (x, y), (x + h, y + w), color, 2)
            """
            # 左眼
            cv2.circle(img, (x + w // 4, y + h // 4 + 30), min(w // 8, h // 8),color)
            # 右眼
            cv2.circle(img, (x + 3 * w // 4, y + h // 4 + 30), min(w // 8, h // 8),color)
            # 嘴巴
            cv2.rectangle(img, (x + 3 * w // 8, y + 3 * h // 4), (x + 5 * w // 8, y + 7 * h // 8), color)
            """
    show_image(title='result', img=img)

if __name__ == '__main__':
    imagepath = "images/demo_03.jpg"
    face_recognition(imagepath)

(2) 视频人脸识别

# -*- coding:utf-8 -*-
# OpenCV版本的视频检测
import cv2


# 图片识别方法封装
def discern(img):
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    cap = cv2.CascadeClassifier("F:/Python_Projects/PythonDataAnalysis/venv/Lib/site-packages/cv2/data/haarcascade_frontalface_default.xml")
    faceRects = cap.detectMultiScale(gray, scaleFactor=1.2, minNeighbors=3, minSize=(50, 50))
    if len(faceRects):
        for faceRect in faceRects:
            x, y, w, h = faceRect
            cv2.rectangle(img, (x, y), (x + h, y + w), (0, 255, 0), 2)  # 框出人脸
    cv2.imshow("Image", img)


video = cv2.VideoCapture('images/demo_01.mp4')
while (1):  # 逐帧显示
    ret, img = video.read()
    discern(img)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

# video.release()  # 释放摄像头
# cv2.destroyAllWindows()  # 释放窗口资源
posted @ 2023-05-31 09:02  菜鸟程序员_python  阅读(304)  评论(0)    收藏  举报