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() # 释放窗口资源
浙公网安备 33010602011771号