文章目录
OpenCV-Python计算机视觉入门
开发环境
- 开发工具:PyCharm 2020.1
- Python 版本:3.7
- OpenCV 版本:4.5.0
- numpy 版本:1.19.4
OpenCV 官网:https://opencv.org/
OpenCV 中文网站:http://wiki.opencv.org.cn/index.php
Anacoda安装和使用:https://blog.csdn.net/u011424614/article/details/105579502
当前博文:https://blog.csdn.net/u011424614/article/details/112428533
一、入门基础
1.演示
- 显示图像的例子
# 引入OpenCV
import cv2
# 读取图片-与python文件相同目录
img = cv2.imread("image.png")
# 显示窗口命名
cv2.namedWindow("demo")
# 显示加载的图像
cv2.imshow("demo", img)
# 窗口保持显示(回车键可关闭窗口)
cv2.waitKey(0)
# 销毁所有窗口
cv2.destroyAllWindows()
2.图像的读取、显示和保存
1)读取图像
- retval = cv2.imread(文件名 [,显示控制参数])
显示控制参数:
- cv2.IMREAD_UNCHANGED:不改变,原始图像
- cv2.IMREAD_GRAYSCALE:灰度
- cv2.IMREAD_COLOR:彩色
2)显示图像
- Nonc = cv2.imshow(窗口名, 图像变量名)
- retval = cv2.waitKey([delay])
delay 说明:
delay > 0 :等待 delay 毫秒
delay < 0 :等待键盘单击
delay = 0 :无限等待
- cv2.destroyAllWindows()
销毁所有窗口,清除内存的缓存
3)保存图像
- retval = cv2.imwrite(文件保存位置和文件名, 图像变量名)
# 导入 OpenCV
import cv2
# 读取图片-与python文件相同目录
img = cv2.imread("image.png", cv2.IMREAD_GRAYSCALE)
# 显示窗口命名
cv2.namedWindow("demo")
# 显示加载的图像
cv2.imshow("demo", img)
# 窗口保持显示(回车键可关闭窗口)
cv2.waitKey(0)
# 销毁所有窗口
cv2.destroyAllWindows()
# 保存图像
cv2.imwrite("D:\\demo.png", img)
二、图像处理基础
1.图像处理入门基础
1)图像是由像素构成的
- 像素可以是方格或是点;相同面积下,像素越多,图像越清晰
2)图像分类
-
二值图像:图像只有两个值,任何一个点非黑即白
-
灰度图像:图像有黑白灰三种颜色,并分为 256 个颜色,0 为黑色,255 为白色,1 - 254 为不同程度的灰色
-
RGB 图像:
-
彩色图像,三原色 R [red 红色]、G [green 绿色]、B[blue 蓝色] ;
-
每个颜色都有 0 - 255 的数值,表示颜色的深浅;通过三种颜色,不同比例的混合,生成新的颜色
-
OpenCV 中的通道顺序为:B G R,如某个像素用 OpenCV 读取为 (122, 87, 23) ,即 B = 122,G = 87,R = 23
-
-
RGB 图像 转 灰度图像:
- 目的是将彩色图原来任何一个像素都是3个值,转成灰度图变为任何一个像素都是1个值,方便图像处理
- 从彩色图转为灰度图后,原来RGB的数值,依旧保留在灰度图中
-
灰度图像 转 二值图像
- 二值图像数字化表示:
- 灰度图数字化表示:
- RGB 图像数字化表示:
- RGB 图像构成的数字化表示:
2.图像像素处理
1)读取像素
- 返回值 = 图像(位置参数)
-
灰度图像,返回灰度值;
- 例如:p = img[88, 123] 表示 88 行、123 列的值
-
BGR 图像,返回 B,G,R 的值;例如
- 例如:blue = img[45, 123, 0] 表示 45行、123列和第 0 个通道(蓝色 Blue)的值;0,1,2 通道分别对应 B,G,R 三个通道
- 例如:bgr = img[45, 123] 表示同时读取 45 行、123 列的 BGR 三个通道的值
- 例如:bgra = img[45, 123] 示同时读取 45 行、123 列的 BGRA 四个通道的值,其中 A 表示图像的 alpha 通道
2)修改像素值
- 像素 = 新值
- 灰度图像
- 例如:img[11, 22] = 233 表示给图像 11 行、22 列的像素进行赋值 233
- BGR 图像
- img[11, 22, 0] = 233 表示给 11 行、22 列和第 0 通道的像素进行赋值 233
- img[11, 22] = [233, 233, 233]表示同时给 B,G,R 三个通道的 11行和22列进行赋值 233
# 导入 OpenCV
import cv2
# 读取图片-与python文件相同目录, 灰度图
img = cv2.imread("image.png", cv2.IMREAD_GRAYSCALE)
# 读取 11行 - 22列 的值
print(img[11, 22])
# 修改 11行 - 22列 的值
img[11, 22] = 233
print(img[11, 22])
print("========================================")
# 读取图片-与python文件相同目录, 原图-彩色图
img = cv2.imread("image.png", cv2.IMREAD_UNCHANGED)
# 读取 11行 - 22列 的值
print(img[11, 22])
# 修改 11行 - 22列 的 BGRA 值
img[11, 22] = [233, 233, 233, 233]
print(img[11, 22])
print("========================================")
# 读取图片-与python文件相同目录, 原图-彩色图
img = cv2.imread("image.png", cv2.IMREAD_UNCHANGED)
# 读取 第 0 个通道 - 11行 - 22列 的值
print(img[11, 22, 0])
# 修改 第 0 个通道 - 11行 - 22列 的 BGRA 值
img[11, 22, 0] = 233
print(img[11, 22])
print("========================================")
# 读取图片-与python文件相同目录, 原图-彩色图
img = cv2.imread("image.png", cv2.IMREAD_UNCHANGED)
# 显示原图
cv2.imshow("original", img)
# 修改 11行至16行 - 22列至28列 的 BGRA 值
img[11:16, 22:28] = [233, 233, 233, 233]
# 显示修改图
cv2.imshow("result", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
3.使用numpy访问像素
1)读取像素
- 返回值 = 图像.item(位置参数)
-
灰度图像,返回灰度值
例如:img.item(11, 22) 表示读取 11 行、22 列的值
-
BGR 图像,返回值 B,G,R 的值
例如:blue = img.item(11, 22, 0) 表示读取第 0 个通道,11行、22 列的值
2)修改显示
- 图像名.itemset(位置, 新值)
-
灰度图像
例如:img.itemset((11, 22), 233) 表示给 11 行、22 列赋值为 233
-
BRG 图像、
例如:img.itemset((11, 22, 0), 233) 表示给第 0 个通道、11 行、22 列赋值为 233
# 导入 OpenCV
import cv2
# 导入 numpy
import numpy as np
# 读取图片-与python文件相同目录, 灰度图
img = cv2.imread("image.png", cv2.IMREAD_GRAYSCALE)
# 读取 11行 - 22列 的值
print(img.item(11, 22))
# 修改 11行 - 22列 的值
img.itemset((11, 22), 233)
print(img.item(11, 22))
print("========================================")
# 读取图片-与python文件相同目录, 原图-彩色图
img = cv2.imread("image.png", cv2.IMREAD_UNCHANGED)
# 读取 第 0 个通道 - 11行 - 22列 的值
print(img.item(11, 22, 0))
# 修改 第 0 个通道 - 11行 - 22列 的值
img.itemset((11, 22, 0), 233)
print(img.item(11, 22, 0))
4.获取像素属性
1)形状:行、列、通道数
- shape 可以获取图像的形状,返回包含行数,列数,通道数的元组
-
灰度图像,返回行数,列数
例如:img.shape 获取图像形状的元组,(600, 200) 表示图像有 600 行、200 列
-
彩色图像,返回行数,列数,通道数
例如:img.shape 获取图像形状的元组和通道数,(600, 200, 3) 表示图像有 600 行、200 列、有 3 个通道
**2)像素数目 **
- size 可以获取图像的像素数目
-
灰度图像,返回行数 * 列数
例如:img.size 获取像素数目
-
彩色图像,返回行数 * 列数 * 通道数
3)图像的数据类型
-
dtype 可以获取每一个图像点的数据类型
例如:img.dtype 返回 uint8 数据类型
# 导入 OpenCV
import cv2
# 读取图片-与python文件相同目录, 原图
img = cv2.imread("image.png", cv2.IMREAD_UNCHANGED)
# 形状
print(img.shape)
# 像素数数目
print(img.size)
# 像素点的数据类型
print(img.dtype)
print("========================================")
# 读取图片-与python文件相同目录, 灰度图
img = cv2.imread("image.png", cv2.IMREAD_GRAYSCALE)
# 形状
print(img.shape)
# 像素数数目
print(img.size)
# 像素点的数据类型
print(img.dtype)
5.感兴趣区域ROI
- ROI (region of interest)感兴趣区域
- 从被处理的图像,以方框、圆、椭圆、不规则多边形等方式勾勒出需要处理的区域
- 可以通过各种算子(Operator)和函数来求得感兴趣区域 ROI,并进行图像的下一步处理
1)通过像素数位置,获取感兴趣的区域
- 获取感兴趣区域
- 将 ROI 图像显示在原图的左上角中
import cv2
import numpy as np
# 读取图片-与python文件相同目录, 原图
img = cv2.imread("image.png", cv2.IMREAD_UNCHANGED)
# 显示图像
cv2.imshow("result-original", img)
# 获取区域
roi = img[11:33, 22:44]
# 显示图像
cv2.imshow("result-roi", roi)
# numpy 创建图像
roi = np.ones((22, 22, 3))
# 获取 ROI 图像
roi = img[11:33, 22:44]
# 显示图像
cv2.imshow("result-roi2", roi)
# 将 ROI 图像显示在原图的左上角中
img[0:22, 0:22] = roi
# 显示图像
cv2.imshow("result", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
6.通道的拆分和合并
1)拆分通道
-
将彩色图像分为B,G,R
例如:b, g, r = cv2.split(img)
2)合并通道
-
将B,G,R 通道合并为彩色图像
例如:cv2.merge([b, g, r])
import cv2
import numpy as np
# 读取图片-与python文件相同目录, 原图
img = cv2.imread("image.png", cv2.IMREAD_UNCHANGED)
# 拆分图像
b,g,r,a = cv2.split(img)
# 显示图像(可通过 shape 属性查看通道数)
cv2.imshow("B", b)
cv2.imshow("G", g)
cv2.imshow("R", r)
cv2.imshow("A", a)
# 可获取单通道图像
b = cv2.split(img)[0]
g = cv2.split(img)[1]
r = cv2.split(img)[2]
# numpy 创建零值通道
rows, cols, chn = img.shape
zero = np.zeros((rows, cols), dtype=img.dtype)
# 合并绿色通道和零值通道
mg = cv2.merge([zero, g, zero])
cv2.imshow("merge-g", mg)
# 合并通道
mbgr = cv2.merge([b, g, r])
cv2.imshow("merge-bgr", mbgr)
cv2.waitKey(0)
cv2.destroyAllWindows()
三、图像运算
1.图像加法
参与运算的图像大小、类型必须一致
1)取模加法
-
numpy 运算方式:结果 = 图像1 + 图像2
-
像素值在十进制的情况下,最大值为 255,像素值相加后,有可能出现大于 255 的情况
(1) 像素值 <= 255,直接使用结果
(2) 像素值 > 255,需要结果对 255 取模,即【结果 % 255 = 最终结果】
2)饱和运算
-
opencv 运算方式:结果 = cv2.add(图像1, 图像2)
(1) 像素值 <= 255,直接使用结果
(2) 像素值 > 255,取最大值 255
import cv2
import numpy as np
# 读取图片-与python文件相同目录, 原图
img = cv2.imread("image.png", cv2.IMREAD_UNCHANGED)
img2 = img
# numpy 加法(取模加法)
numpy_img = img + img2
# opencv 加法(饱和运算)
cv_img = cv2.add(img, img2)
# 显示图像
cv2.imshow("numpy_img", numpy_img)
cv2.imshow("cv_img", cv_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
2.图像融合
-
将 2 张或 2 张以上的 图像融合为 1 张图像上
-
融合的图像含有更多的信息,能够更方便人的观察和计算机处理
-
例如:两张相同的图像,每张图片都在不同的位置出现模糊的情况,但是两张图像融合之后,还原出一张清晰的图像
-
图像加法中,图像时按照 1:1 进行相加,而融合是需要调整系数后再相加
1)numpy 图像融合
-
结果图像 = 图像1 * 系数1 + 图像2 + 系数2 + 亮度调节量
-
img = img1 * 0.3 + img2 * 0.7 + 18
2)opencv 图像融合
- dst = cv2.addWeighted(img1, alpha, img2, beta, gamma)
- 内部计算方式:dst = img1 * alpha + img2 * beta + gamma
- 参数 gamma 不能省略,可传入 0 值
import cv2
import numpy as np
# 读取图片-与python文件相同目录, 原图
img = cv2.imread("image.png", cv2.IMREAD_UNCHANGED)
img2 = cv2.imread("image2.png", cv2.IMREAD_UNCHANGED)
# numpy 融合
numpy_img = img * 0.3 + img2 * 0.7 + 10
# opencv 融合
cv_img = cv2.addWeighted(img, 0.3,img2, 0.7, 10)
# 显示图像
cv2.imshow("numpy_img", numpy_img)
cv2.imshow("cv_img", cv_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
四、类型转换
1.类型转换
- 将图像有一种类型转换为另外一直类型
opencv 中提供了 200 多种不同类型直接的转换
- 彩色图像转灰度图像:cv2.COLOR_BGR2GRAY
- BGR 转 RGB:cv2.COLOR_BGR2RGB
- 灰度转 BGR:cv2.COLOR_GRAY2BGR
import cv2
# 读取图片-与python文件相同目录, 原图
img = cv2.imread("image2.png", cv2.IMREAD_UNCHANGED)
# 读取图片-与python文件相同目录, 灰度图
img2 = cv2.imread("image2.png", cv2.IMREAD_GRAYSCALE)
# 图像转换
b2g = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
b2r = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
g2b = cv2.cvtColor(img2, cv2.COLOR_GRAY2BGR)
# 输出形状信息
print(b2g.shape)
print(b2r.shape)
print(g2b.shape)
# 显示图像
cv2.imshow("b2g", b2g)
cv2.imshow("b2r", b2r)
cv2.imshow("g2b", g2b)
cv2.waitKey(0)
cv2.destroyAllWindows()
五、几何变换
1.图像缩放
-
resize 函数
-
语法格式:dst = cv2.resize(img, dsize) 参数为图像和缩放大小(列, 行)
例如:dst = cv2.resize(img, (122, 122))
-
语法格式:dst = cv2.resize(img, dsize, fx, fy) 参数 fx 表示水平缩放倍数,fy 表示垂直缩放倍数
例如:dst = cv2.resize(img, None, fx=0.3, fy=0.6)
dsize 和 fx,xy 设置一个即可,dsize 优先
import cv2
# 读取图片-与python文件相同目录, 原图
img = cv2.imread("image2.png", cv2.IMREAD_UNCHANGED)
# 图像缩放(列,行)
rezie = cv2.resize(img, (100, 100))
# 图像缩放-倍数(行,列)
rezie2 = cv2.resize(img, None, fx=0.7, fy=0.7)
# 图像缩放-比例(列,行)
rows,cols = img.shape[:2]
rezie3 = cv2.resize(img, (round(cols * 0.8), round(rows * 0.8)))
# 显示图像
cv2.imshow("rezie", rezie)
cv2.imshow("rezie2", rezie2)
cv2.imshow("rezie3", rezie2)
cv2.waitKey(0)
cv2.destroyAllWindows()
2.图像旋转
-
flip 函数
-
语法:dst = cv2.flip(img, filipCode) 参数为图像 和翻转模式
-
翻转模式:
等于 0 表示以 X 轴进行对称翻转(上下翻转)
大于 0 表示以 Y 轴进行对称轴翻转(左右翻转)
小于 0 表示 在 X 轴和 Y 轴方向同时翻转(先上下,后左右)
import cv2
# 读取图片-与python文件相同目录, 原图
img = cv2.imread("image2.png", cv2.IMREAD_UNCHANGED)
# 上下翻转
x = cv2.flip(img, 0)
# 左右翻转
y = cv2.flip(img, 1)
# 上下、左右同时翻转
xy = cv2.flip(img, -1)
# 显示图像
cv2.imshow("x", x)
cv2.imshow("y", y)
cv2.imshow("xy", xy)
cv2.waitKey(0)
cv2.destroyAllWindows()
六、阈值分割
1.基础理论
1)Value and Threshold Level 原始图像
- 图像中有阈值、最大值和最小值
2)Threshold Binary 二进制阈值化
- 将比阈值大的像素值处理为图像的最大值,将等于或小于阈值的像素值处理为最小值
- 例如:图像中最大值为 255,最小值为 6,阈值为 99,经过二进制阈值化后,图像大于 99 的像素值处理为 255,等于或小于 99 的像数值处理为 6
3)Threshold Binary, Inverted 反二进制阈值化
- 将比阈值大的像素值处理为图像的最小值,将等于或小于阈值的像素值处理为最大值
- 例如:图像中最大值为 255,最小值为 6,阈值为 99,经过反二进制阈值化后,图像大于 99 的像素值处理为 6,等于或小于 99 的像数值处理为 255
4)Truncate 截断阈值化
- 将大于阈值的像数值处理为阈值,而等于或小于阈值的像素值保持不变
- 例如:设置阈值为 99,经过截断阈值化后,像素值大于 99 的像素值处理为 99,其它值保持不变
5)Threshold Zero,Inverted 反阈值化为 0
- 将大于阈值的像数值处理为 0,而等于或小于阈值的像素值保持不变
- 例如:设置阈值为 99,经过反阈值化为 0 后,图像大于 99 的像素值处理为 0,其它值保持不变
6)Threshold to Zero 阈值化为0
- 将等于或小于阈值的像素值处理为 0,而大于阈值的像素值保持不变
- 例如:设置阈值为 99,经过阈值化为 0 后,图像等于或小于 99 的像素值处理为 0,其它值保持不变
2.threshold函数
-
语法:retval, dst = cv2.threshod(img, thresh, maxval, type)
参数:retval=阈值,dst=处理结果,img=源图像,thresh=阈值,maxval=最大值,type=类型
type 类型 | 说明 | 简要 |
---|---|---|
cv2.THRESH_BINARY | 二进制阈值化 | 较亮的更亮,较暗的更暗 |
cv2.THRESH_BINARY_INV | 反二进制阈值化 | 亮的变暗,暗的变亮 |
cv2.THRESH_TRUNC | 截断阈值化 | 降低亮度 |
cv2.THRESH_TOZERO_INV | 反阈值化为 0 | 亮的变黑 |
cv2.THRESH_TOZERO | 阈值化为 0 | 暗的变黑 |
import cv2
# 读取图片-与python文件相同目录, 原图
img = cv2.imread("image2.png", cv2.IMREAD_UNCHANGED)
# 二进制二值化
th,binary = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
# 反二进制二值化
th,binary_inv = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV)
# 截断阈值化
th,trunc = cv2.threshold(img, 127, 255, cv2.THRESH_TRUNC)
# 反二值化为 0
th,tozero_inv = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO_INV)
# 二值化为 0
th,tozero = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO)
# 显示图像
cv2.imshow("binary", binary)
cv2.imshow("binary_inv", binary_inv)
cv2.imshow("trunc", trunc)
cv2.imshow("tozero_inv", tozero_inv)
cv2.imshow("tozero", tozero)
cv2.waitKey(0)
cv2.destroyAllWindows()
七、图像平滑处理
1.均值滤波
-
图像上,任意一点的像素值,都是周围 N*N 个像素值的均值
例如:下图中,对红色点进行均值滤波处理,会由周围 5*5 的所有像数值相加(包括红点的像数值),然后除以25,最终得到红色点的均值
-
周围的 N*N 个像素称为 核
红点周围的 5*5 个点,每个点的像素值乘以 1/25,然后所有值相加,得到红点的均值
- 均值滤波图像:对图像内的所有像素点,逐个采用核进行处理,得到结果图像
-
blur 函数
语法:reval = cv2.blur(img, ksize)
参数:img 表示图像;ksize 表示核大小,以元组表示 (宽度, 高度)
-
效果:可将带雪花白点干扰的图像,处理为平滑无干扰的图像
import cv2
# 读取图片-与python文件相同目录, 原图
img = cv2.imread("image2.png", cv2.IMREAD_UNCHANGED)
# 均值滤波
bval = cv2.blur(img, (5, 5))
# 显示图像
cv2.imshow("bval", bval)
cv2.waitKey(0)
cv2.destroyAllWindows()
2.方框滤波
-
boxFilter 函数
语法:reval = cv2.boxFilter(img, depth, ksize, normalize)
参数:reval 表示处理结果,img 表示图像,depth 表示目标图像的深度( -1 表示与原始图像一致),ksize 表示核大小,normalize 表示归一化属性
-
normalize = 1 表示与均值滤波的处理相同,normalize = 0 表示核内的像素值相加(255 白色),可以控制核大小,避免图像空白
import cv2
# 读取图片-与python文件相同目录, 原图
img = cv2.imread("image2.png", cv2.IMREAD_UNCHANGED)
# 方框滤波,不做归一化处理
bval0 = cv2.boxFilter(img, -1, (2, 2), normalize = 0)
# 方框滤波,归一化处理
bval1 = cv2.boxFilter(img, -1, (5, 5), normalize = 1)
# 显示图像
cv2.imshow("bval0", bval0)
cv2.imshow("bval1", bval1)
cv2.waitKey(0)
cv2.destroyAllWindows()
3.高斯滤波
-
让临近的像素具有更高的重要度,图像上某一点,对周围像素计算加权平均值,距离点较近的像素具有较大的权重值
例如:对红点进行高斯滤波处理,在 3*3 的核中,距离红点最近的像素是上下左右的像素,所以权重值最大,其它像素较远,所以权重值会减少,最终红点值为核内所有的像素值乘以权重值之和
-
GaussianBlur 函数
语法:reval = cv2.GaussianBlur(img, ksize, sigmaX)
参数:img 表示图像,ksize 表示核大小,必须为奇数,sigmaX 表示X方向方差,控制权重【sigmaX=0 时,sigmaX = 0.3 * ((ksize-1) * 0.5 - 1) + 0.8】,Y方向方差默认与X方向方差一致
import cv2
# 读取图片-与python文件相同目录, 原图
img = cv2.imread("image2.png", cv2.IMREAD_UNCHANGED)
# 高斯滤波
reval = cv2.GaussianBlur(img, (5, 5), 0)
# 显示图像
cv2.imshow("reval", reval)
cv2.waitKey(0)
cv2.destroyAllWindows()
4.中值滤波
-
对核内的像素值进行排序,去排序中间位置的像数值作为中值滤波的像数值
-
medianBlur 函数
语法: reval = cv2.medianBlur(img, ksize)
参数:img 表示图像;ksize 表示核大小,必须是比 1 大的奇数
import cv2
# 读取图片-与python文件相同目录, 原图
img = cv2.imread("image2.png", cv2.IMREAD_UNCHANGED)
# 高斯滤波
reval = cv2.medianBlur(img, 5)
# 显示图像
cv2.imshow("reval", reval)
cv2.waitKey(0)
cv2.destroyAllWindows()
八、形态学处理
- 形态学处理一般只要针对的是
二值图像
- 一般有两个输入对象,二值图像和卷积核
1.图像腐蚀
-
图像中,以卷积核的中心点为中心,个像素扫描原始图像,如果卷积核覆盖的区域全都是 1(白色),则保留 1,如果卷积核覆盖的区域有 0(黑色),则将和中心点的像素设为 0
-
效果:将粗线条处理为细线条,或者去除毛边
-
erode 函数
语法:reval = cv2.erode(img, kernel, iterations)
参数:img 表示图像;kernel 表示卷积核;iterations 表示迭代次数,默认 1 次腐蚀
import cv2
import numpy as np
# 读取图片-与python文件相同目录, 原图
img = cv2.imread("image2.png", cv2.IMREAD_GRAYSCALE)
# 创建 5*5 值为 1 的卷积核
kernel = np.ones((5, 5), np.uint8)
# 腐蚀处理
reval = cv2.erode(img, kernel)
# 显示图像
cv2.imshow("reval", reval)
cv2.waitKey(0)
cv2.destroyAllWindows()
2.图像膨胀
- 膨胀是腐蚀的逆操作
- 一般的,可以先用腐蚀处理图像,去除噪点,再使用膨胀处理,使其恢复原来形状(以去除噪点)
-
图像中,以卷积核的中心点为中心,个像素扫描原始图像,如果卷积核覆盖的区域有 1(白色),则设置为 1,如果卷积核覆盖的区域全都是 0(黑色),则将和中心点的像素保留为 0
-
dilate 函数
语法:reval = cv2.dilate(img, kernel, iterations)
参数:img 表示图像;kernel 表示卷积核;iterations 表示迭代次数
import cv2
import numpy as np
# 读取图片-与python文件相同目录, 原图
img = cv2.imread("image3.png", cv2.IMREAD_UNCHANGED)
# 创建 5*5 值为 1 的卷积核
kernel = np.ones((5, 5), np.uint8)
# 膨胀处理
reval = cv2.dilate(img, kernel)
# 显示图像
cv2.imshow("reval", reval)
cv2.waitKey(0)
cv2.destroyAllWindows()
3.开运算
- 处理过程:先腐蚀,后膨胀;即 开运算(image) = 膨胀(腐蚀(image))
-
morphologyEx 函数
知识点:在形态学处理中,除了腐蚀和膨胀,其它的处理方法均为形态学扩展,统一使用 morphologyEx 函数实现
语法:opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
参数:img 表示图像;cv2.MORPH_OPEN 表示开运算常量;kernel 表示卷积核
import cv2
import numpy as np
# 读取图片-与python文件相同目录, 原图
img = cv2.imread("image3.png", cv2.IMREAD_UNCHANGED)
# 创建 5*5 值为 1 的卷积核
kernel = np.ones((5, 5), np.uint8)
# 开运算处理
reval = cv2.morphologyEx(img, cv2.MORPH_OPEN,kernel)
# 显示图像
cv2.imshow("reval", reval)
cv2.waitKey(0)
cv2.destroyAllWindows()
4.闭运算
- 处理过程:先膨胀,后腐蚀;即 闭运算(image) = 腐蚀(膨胀(image))
-
morphologyEx 函数
语法:opening = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
参数:img 表示图像;cv2.MORPH_CLOSE 表示闭运算常量;kernel 表示卷积核
import cv2
import numpy as np
# 读取图片-与python文件相同目录, 原图
img = cv2.imread("image3.png", cv2.IMREAD_UNCHANGED)
# 创建 5*5 值为 1 的卷积核
kernel = np.ones((5, 5), np.uint8)
# 闭运算处理
reval = cv2.morphologyEx(img, cv2.MORPH_CLOSE,kernel)
# 显示图像
cv2.imshow("reval", reval)
cv2.waitKey(0)
cv2.destroyAllWindows()
5.梯度运算
- 处理过程:第一步生成膨胀图像,第二步生成腐蚀图像,第三步膨胀图像的像素值减去腐蚀图像的像素值,最终得到轮廓图像,即 梯度(image) = 膨胀(image) - 腐蚀(image)
- 像素值:0 表示黑色,1 表示白色;1 - 1 = 0 即白色减白色等于黑色
-
morphologyEx 函数
语法:opening = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel)
参数:img 表示图像;cv2.MORPH_GRADIENT 表示梯度运算常量;kernel 表示卷积核
import cv2
import numpy as np
# 读取图片-与python文件相同目录, 原图
img = cv2.imread("image3.png", cv2.IMREAD_UNCHANGED)
# 创建 5*5 值为 1 的卷积核
kernel = np.ones((5, 5), np.uint8)
# 梯度运算处理
reval = cv2.morphologyEx(img, cv2.MORPH_GRADIENT,kernel)
# 显示图像
cv2.imshow("reval", reval)
cv2.waitKey(0)
cv2.destroyAllWindows()
6.礼帽运算
- 先进行开运算,后使用原始图像减去开运算图像;即 礼帽(image) = image - 开运算(image)
- 像素值:0 表示黑色,1 表示白色;1 - 1 = 0 表示白色减白色等于黑色;1 - 0 = 1 表示白色减黑色等于白色
-
morphologyEx 函数
语法:opening = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)
参数:img 表示图像;cv2.MORPH_TOPHAT 表示礼帽运算常量;kernel 表示卷积核
import cv2
import numpy as np
# 读取图片-与python文件相同目录, 原图
img = cv2.imread("image3.png", cv2.IMREAD_UNCHANGED)
# 创建 5*5 值为 1 的卷积核
kernel = np.ones((5, 5), np.uint8)
# 礼帽运算处理
reval = cv2.morphologyEx(img, cv2.MORPH_TOPHAT,kernel)
# 显示图像
cv2.imshow("reval", reval)
cv2.waitKey(0)
cv2.destroyAllWindows()
7.黑帽运算
- 先进行闭运算,后使用闭运算图像减去原始图像;即 黑帽(image) = 闭运算(image) - image
- 像素值:0 表示黑色,1 表示白色;1 - 1 = 0 表示白色减白色等于黑色;1 - 0 = 1 表示白色减黑色等于白色
-
morphologyEx 函数
语法:opening = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel)
参数:img 表示图像;cv2.MORPH_BLACKHAT 表示黑帽运算常量;kernel 表示卷积核
import cv2
import numpy as np
# 读取图片-与python文件相同目录, 原图
img = cv2.imread("image3.png", cv2.IMREAD_UNCHANGED)
# 创建 5*5 值为 1 的卷积核
kernel = np.ones((5, 5), np.uint8)
# 黑帽运算处理
reval = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT,kernel)
# 显示图像
cv2.imshow("reval", reval)
cv2.waitKey(0)
cv2.destroyAllWindows()
九、图像梯度
- 梯度、边界
1.sobel算子理论基础
-
X 轴方向的梯度,计算水平方向有没有边界,梯度值大说明存在边界,否则不存在
例如:P5 点的 X 轴方向的梯度值,使用右边值减去左边值;由于 X 轴方向上,P4 和 P6 距离 P5 最近,对应权重最大为 2,其它为 1;如果两边的值差别比较大,P5 相应的会比较大,说明边界存在,否则边界不存在(比如值为 0)
-
Y 轴方向的梯度,计算垂直方向有没有边界,梯度值大说明存在边界,否则不存在
例如:P5 点的 Y 轴方向的梯度值,使用下边值减去上边值;由于Y 轴方向上,P2 和 P8 距离 P5 最近,对应权重最大为 2,其它为 1;如果两边的值差别比较大,P5 相应的会比较大,说明边界存在,否则边界不存在(比如值为 0)
- 最终,P5 像素点的梯度值是,水平方向梯度值的平方加上垂直方向的梯度值平方开根号;而为了方便计算,一般可以简化为,水平方向梯度值的绝对值加上垂直方向梯度值的绝对值
2.sobel算子及其函数使用
-
Sobel 函数
语法:reval = cv2.Sobel(img, ddepth, dx, dy, [ksize])
参数:img 表示图像;ddepth 表示处理结果图像的深度(设置为 -1 表示让结果与原始图像保持一致);dx 表示 X 轴方向(如果只计算 X 轴:dx=1,dy=0);dy 表示 Y 轴方向(如果只计算 Y 轴:dx=0,dy=1);ksize 表示核大小
-
实际操作中,计算梯度值可能会出现负数,通常处理的图像是
np.uint8
类型,如果结果为该类型,所有的负数会自动截取为 0,发现信息丢失(例如:两个边界,却只获得一个边界),所以,在计算时需要使用cv2.CV_64F
,取绝对值后,在转换为np.uint8
(cv2.CV_8U) 类型 -
convertScaleAbs 函数
语法:reval = cv2.convertScaleAbs(img, [, alpha[, beta]])
参数:img 表示图像;alpha 表示系数;beta 表示修正值
作用:将图像转为 256 色位图
内部逻辑:结果图像 = 调整(原始图像 * alpha + beta)
-
在 sobel 计算中,同时计算梯度值的方式有两种
-
第一种:dx=1,dy=1 ;即 reval = cv2.Sobel(img, ddepth, 1, 1)
-
第二种(推荐):分别计算 dx 和 dy,然后相加
X 轴梯度值:dx = cv2.Sobel(img, ddepth, 1, 0)
Y 轴梯度值:dy = cv2.Sobel(img, ddepth, 0, 1)
最终梯度值:reval = dx + dy ;也可加入系数:reval = dx * 系数1 + dy * 系数2
-
addWeighted 函数
语法:reval = cv2.addWeighted(img1, alpha, img2, beta, gamma)
参数:img1 表示图像1;alpha 表示图像1的系数;img2 表示图像2;beta 表示图像2的系数;gamma 表示修正值
效果:计算两幅图像的权重和
import cv2
import numpy as np
# 读取图片-与python文件相同目录, 原图
img = cv2.imread("image4.png", cv2.IMREAD_UNCHANGED)
# (第一种方式) 计算图像梯度值
sobelxy1 = cv2.Sobel(img, cv2.CV_64F, 1, 1)
sobelxy1 = cv2.convertScaleAbs(sobelxy1)
# (第二种方式) 计算图像梯度值
# X轴梯度值
sobelx = cv2.Sobel(img, cv2.CV_64F, 1, 0)
# 将 cv2.CV_64F 转回 uint8
sobelx = cv2.convertScaleAbs(sobelx)
# Y轴梯度值
sobely = cv2.Sobel(img, cv2.CV_64F, 0, 1)
# 将 cv2.CV_64F 转回 uint8
sobely = cv2.convertScaleAbs(sobely)
# 图像梯度值
sobelxy2 = cv2.addWeighted(sobelx, 0.5, sobely, 0.5, 0)
# 显示图像
cv2.imshow("original", img)
cv2.imshow("sobelxy1", sobelxy1)
cv2.imshow("sobelx", sobelx)
cv2.imshow("sobely", sobely)
cv2.imshow("sobelxy2", sobelxy2)
cv2.waitKey(0)
cv2.destroyAllWindows()
3.scharr算子及其函数使用
- scharr 算子是 sobel 算子的升级版,主要是解决使用 3*3 的sobel 算子时,可能不太精确的问题;主要改进是卷积核的系数
-
Scharr 函数
语法:reval = cv2.Scharr(img, ddpeth, dx, dy)
参数:img 表示图像;ddepth 表示处理结果图像的深度(设置为 -1 表示让结果与原始图像保持一致;但是为了处理负数问题,需要将深度设置为
cv2.CV_64F
,然后在转换为 uint8 类型 ,详情查看 sobel 算子笔记);dx 表示 X 轴方向;dy 表示 Y 轴方向注意:dx >=0 && dy >=0 && dx+dy == 1 ,即 dx 和 dy 不能同时设置为 1
-
两个方向的梯度计算方式
dx = cv2.Scharr(img, ddpeth, 1, 0)
dy = cv2.Scharr(img, ddpeth, 0, 1)
dxy = dx + dy --> cv2.addWeighted(dx, alpha, dy, beta, gamma)
-
如果将 Sobel 函数的 ksize 设置 -1 ,表示使用 Sobel 算子的升级版 Scharr 算子,即 ksize = -1 的 Sobel 算子等于 Scharr 算子
import cv2
import numpy as np
# 读取图片-与python文件相同目录, 原图
img = cv2.imread("image4.png", cv2.IMREAD_UNCHANGED)
# 计算图像梯度值
# X轴梯度值
scharrx = cv2.Scharr(img, cv2.CV_64F, 1, 0)
# 将 cv2.CV_64F 转回 uint8
scharrx = cv2.convertScaleAbs(scharrx)
# Y轴梯度值
scharry = cv2.Scharr(img, cv2.CV_64F, 0, 1)
# 将 cv2.CV_64F 转回 uint8
scharry = cv2.convertScaleAbs(scharry)
# 图像梯度值
sobelxy = cv2.addWeighted(scharrx, 0.5, scharry, 0.5, 0)
# 显示图像
cv2.imshow("original", img)
cv2.imshow("sobelx", scharrx)
cv2.imshow("sobely", scharry)
cv2.imshow("sobelxy", sobelxy)
cv2.waitKey(0)
cv2.destroyAllWindows()
4.sobel算子和scharr算子的比较
- sobel 算子和 scharr 算子的卷积核大小相同,所以计算复杂度一样
- scharr 算子的精确度优于 sobel 算子,具体原因是临近像素的权重分配,最直观的体现是,scharr 算子可以找到更精细的边界,而 sobel 算子是无法检测到的
5.laplacian算子的使用
- 拉普拉斯算子类似与二阶 sobel 导数,如下为公式和卷积核及权重
- 例如:计算P5 像素值
- sobel 算子、scharr 算子和 laplacian 算子对比
-
Laplacian 函数
语法:reval = cv2.Laplacian(img, ddepth)
参数:img 表示图像;ddepth 表示处理图像的深度(设置为 -1 表示让结果与原始图像保持一致;但是为了处理负数问题,需要将深度设置为
cv2.CV_64F
,然后在转换为 uint8 类型 ,详情查看 sobel 算子笔记)
import cv2
import numpy as np
# 读取图片-与python文件相同目录, 原图
img = cv2.imread("image4.png", cv2.IMREAD_UNCHANGED)
# 计算图像梯度值
reval = cv2.Laplacian(img, cv2.CV_64F)
# 将 cv2.CV_64F 转回 uint8
reval = cv2.convertScaleAbs(reval)
# 显示图像
cv2.imshow("original", img)
cv2.imshow("reval", reval)
cv2.waitKey(0)
cv2.destroyAllWindows()
十、边缘检测
1.Canny边缘检测原理
-
Canny 边缘检测步骤:
去噪 --> 梯度(大小和方向) --> 非极大值抑制 --> 滞后阈值
-
第一步:去噪
- 由于边缘检测容易受到噪声的影响;因此在边缘检测前,需要先进行去噪处理
- 一般使用高斯滤波器进行去噪处理(平滑处理)
-
第二步:梯度
- 对平滑后的图像进行 sobel 算子的计算,得到梯度值和方向
- 梯度方向:垂直、水平和两个对角线
-
第三步:非极大值抑制
-
在获得梯度和方向后,遍历图像,去除不是边界的像素点
-
去除方法:逐个遍历像素点,判断当前像素点是否是周围像素点中具有相同方向梯度的最大值
例如:图中黄色部分,第一列方向向上中,7 最大;第一列方向向上中,8 最大,以此类推,得到边界;其它值被抑制为 0
-
-
第四步:滞后阈值
-
在计算梯度值时,获取到滞后阈值1为极小值(minVal)和滞后阈值2为极大值(maxVal)
例如:梯度值 > maxVal 保留边界;梯度值 < minVal 剔除边界;minVal < 梯度值 < maxVal 如果点与边界相连,则保留,否则剔除,如 C 与 A 相连则保留,B 则剔除
-
2.Canny函数及使用
-
Canny 函数
语法:reval = cv2.Canny(img, threshold1, threshold2)
参数:reval 表示边界图像;img 表示图像;threshold1 表示阈值1;threshold2 表示阈值2;阈值越小,图像边界越多
import cv2
import numpy as np
# 读取图片-与python文件相同目录, 原图
img = cv2.imread("image2.png", cv2.IMREAD_UNCHANGED)
# Canny边缘检测
reval = cv2.Canny(img, 20, 50)
# 显示图像
cv2.imshow("original", img)
cv2.imshow("reval", reval)
cv2.waitKey(0)
cv2.destroyAllWindows()
十一、图像金字塔
1.基础理论
- 同一图像的不同分辨率的子图集合
- 生成图像金字塔(高斯金字塔)有两种方式
- 向下采样
-
像素减少,缩小图像
-
实现步骤:
(1)对图像进行高斯核卷积(高斯滤波中使用下图卷积核)
(2)删除所有的偶数行和列
- 向上采样
-
像素增多,放大图像
-
实现步骤:
(1)在每个方向上扩大为原来的 2 倍,新增的行和列以 0 填充
(2)使用与 向下采样 一样的卷积核乘以4,作为新增像素的的新值
- 注意:向上采样、向下采样不是互逆操作,经过这两种采样操作后,无法恢复原有图像
2.pyrDown函数及使用
-
pyrDown 函数
语法:reval = cv2.pyrDown(img)
参数:reval 表示向下取样结果;img 表示图像
效果:图像缩小为原图的 1/ 4
import cv2
import numpy as np
# 读取图片-与python文件相同目录, 原图
img = cv2.imread("image2.png", cv2.IMREAD_UNCHANGED)
# 向下取样
reval = cv2.pyrDown(img)
reval2 = cv2.pyrDown(reval)
# 显示图像
cv2.imshow("original", img)
cv2.imshow("reval", reval)
cv2.imshow("reval2", reval2)
cv2.waitKey(0)
cv2.destroyAllWindows()
3.pyrUp函数及使用
-
pyrUp 函数
语法:reval = cv2.pyrUp(img)
参数:reval 表示向上取样结果;img 表示图像
效果:图像放大为原图的 4 倍,清晰度降低
import cv2
import numpy as np
# 读取图片-与python文件相同目录, 原图
img = cv2.imread("image.png", cv2.IMREAD_UNCHANGED)
# 向上取样
reval = cv2.pyrUp(img)
reval2 = cv2.pyrUp(reval)
# 显示图像
cv2.imshow("original", img)
cv2.imshow("reval", reval)
cv2.imshow("reval2", reval2)
cv2.waitKey(0)
cv2.destroyAllWindows()
4.取样可逆性研究
- 一个图像先向下取样,后向上取样,图像恢复为原来大小,清晰度下降
- 一个图像先向上取样,后向下取样,图像恢复为原来大小,清晰度下降
5.拉普拉斯金字塔
-
在高斯金字塔的基础上构建
-
拉普拉斯金字塔处理的图像可进行复原操作
-
公式:Li = Gi - PyrUp( PyrDown( Gi ) )
参数:Gi 表示原始图像;Li 表示拉普拉斯金字塔图像 ;其中 Gi 和 Li 中的 i 表示层数
import cv2
import numpy as np
# 读取图片-与python文件相同目录, 原图
img = cv2.imread("image2.png", cv2.IMREAD_UNCHANGED)
# 第 0 层拉普拉斯金字塔
imgd = cv2.pyrDown(img)
imgdu = cv2.pyrUp(imgd)
reval = img - imgdu
# 第 1 层拉普拉斯金字塔
imgd1 = imgd
img1d = cv2.pyrDown(imgd1)
img1du = cv2.pyrUp(img1d)
reval1 = imgd1 - img1du;
# 显示图像
cv2.imshow("original", img)
cv2.imshow("reval", reval)
cv2.imshow("reval1", reval1)
cv2.waitKey(0)
cv2.destroyAllWindows()
十二、图像轮廓
1.图像轮廓
- 轮廓是什么?
- 边缘检测可以测出边缘,但是,边缘时不连续的
- 将边缘连接为一个整体,构成轮廓
- 计算图像轮廓注意事项:
- 对象时二值图像(需要将彩色图像或灰度图像处理为二值图像),所以需要预先进行阈值分割或边缘检测处理
- 查询轮廓需要更改原始图像,因此,通常需要对原始图像进行拷贝操作
- 在 opencv 中,是从黑色背景中查找白色对象,因此,对象必须是白色,背景必须是黑色
-
findContours() 函数
作用:查找图像轮廓
语法:image, contours,hierarchy = cv2.findContours(img, mode, method)
参数 说明 image 修改后的图像(二值化图像) contours 图像轮廓 hierarchy 图像的拓扑信息(轮廓层次) img 原始图像 mode 轮廓检索模式(详情查看下方表格) method 轮廓的近似方法(详情查看下方表格) mode 检索轮廓模式:
常量 说明 cv2.RETR_EXTERNAL 只检测外轮廓 cv2.ERTR_LIST 检测的轮廓不建立等级关系 cv2.RETR_CCOMP 建立两个等级的轮廓,上面的一层为外边界,里面的一层为内孔的边界信息。如果内孔内部哎呦一个连通物体,这个物体的边界,也在顶层 cv2.RETR_TREE 建立一个等级树结构的轮廓 method 轮廓的近似方法:
常量 说明 cv2.CHAIN_APPROX_NONE 存储所有轮廓点,相邻的两个点的像素位置差不超过1,即 max(abs(x1-x2) , abs(y2-y1)) == 1 cv2.CHAIN_APPROX_SIMPLE 压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点坐标,例如一个矩形只需要4个点来保持轮廓信息 cv2.CHAIN_APPROX_TC89_L1 使用 tehChinl chain 近似算法 cv2.CHAIN_APPROX_TC89_KCOS 使用 teh-Chinl chain 近似算法 -
drawContours() 函数
作用:将查找到的轮廓,绘制到图像上
语法:reval = cv2.drawContours(img, contours, contourIdx, color [, thickness])
参数 说明 reval 绘制了轮廓的结果图像 img 原始图像 contours 需要绘制的边缘数组(轮廓) contourIdx 需要绘制阿边缘索引,如果全部绘制则为 -1 color 绘制的颜色,为 BGR 格式的 Scalar thickness (可选)表示绘制的密度,即绘制轮廓时所使用的画笔粗细
import cv2
# 读取图片-与python文件相同目录, 原图
img = cv2.imread("image4.png", cv2.IMREAD_UNCHANGED)
# 彩色图像转为灰度图像
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 使用二进制阈值化处理,将灰度图像转为二值图像
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# 查找轮廓
contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# 绘制轮廓
tmp = img.copy()
reval = cv2.drawContours(tmp, contours, -1, (0, 0, 255), 1)
# 显示图像
cv2.imshow("original", img)
cv2.imshow("reval", reval)
cv2.waitKey(0)
cv2.destroyAllWindows()
十三、直方图
1.直方图的概念
-
灰度直方图中,横坐标表示图像中各个像素点的 灰度级 ,纵坐标表示具有该灰度级的像素 个数 ,例如 8 位位图中,灰度级是 0 至 255 ,即 256 个灰度级
-
归一化直方图中,横坐标表示图像中各个像素点的 灰度级 ,纵坐标表示出现这个灰度级的 概率
-
DIMS:使用参数的数量
dims=1 :灰度直方图,仅考虑灰度的情况(其它的可能是亮度等)
-
BINS:参数子集的数量
灰度级的数量,bins=256
也可是整合像素值之后的数量,如 0-15 为一组,16-31 为一组,即 bins=16
-
RANGE:统计灰度值的范围,一般为8位位图 [0, 255]
2.绘制直方图
- matplotlib.pyplot 提供了类似于 matlab 的绘图框架
import matplot.pyplot as plt
-
hist 函数
功能:根据数据源和像素级绘制直方图
语法:hist(数据源, 像素级}
参数:数据源:图像,必须是一维数组;像素级:一般是 256,即 [0, 255]
-
ravel 函数
功能:将二维图像数据转换为一维图像数据
语法:reval = img.ravel()
import cv2
import matplotlib.pyplot as plt
# 读取图片-与python文件相同目录, 原图
img = cv2.imread("image2.png", cv2.IMREAD_UNCHANGED)
# 绘制直方图
plt.hist(img.ravel(), 256)
plt.show()
3.使用OpenCV统计直方图
-
calcHist 函数
功能:计算默认横坐标为 [0, 255] 的灰度级,直接计算纵坐标灰度级的像素个数
语法:reval = cv2.calcHist(imgs, channels, mask, histSize, ranges, accumulate)
参数 说明 reval 直方图,二维数组(256行 1 列) imgs 图像 channels 指定通道;灰度图像 = [0],彩色图像=[0],[1],[2] 分别对应B,G,R mask 掩码图像(仅计算某一部分图像);统计整幅图像的直方图,设为None;统计图像某一部分的直方图时,需要掩码图像 histSize BINS的数量,如:[265] ranges 像素值范围,如:[0, 255] accumulate 累计标识;默认值为 false,如果为 true,则可计算多幅图像的直方图
import cv2
import numpy as np
# 读取图片-与python文件相同目录, 原图
img = cv2.imread("image2.png", cv2.IMREAD_UNCHANGED)
# 统计直方图
reval = cv2.calcHist([img], [0], None, [256], [0, 255])
print(type(reval)) # 类型
print(reval.size) # 大小
print(reval.shape) # 形状
print(reval) # 直方图
4.绘制OpenCV统计直方图
- 绘制 0 至 5 ,sin 函数的直方图
import numpy as np
import matplotlib.pyplot as plt
# 创建范围为 0 - 5(不包括5),步长为 0.1
x = np.arange(0, 5, 0.1)
y = np.sin(x)
plt.plot(x, y)
plt.show()
- 指定值,绘制直方图(这里 X 轴数据可省略)
import numpy as np
import matplotlib.pyplot as plt
# X 轴数据
x = [0, 1, 2, 3, 4, 5]
# Y 轴数据
y = [2, 10, 30, 25, 10, 20]
# plt.plot(x, y) # 正常使用 X 轴和Y 轴的数据
# plt.plot(y) # 省略 X 轴数据
plt.plot(y, color='r') # 指定颜色
plt.show()
- 绘制图像的直方图
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读取图片-与python文件相同目录, 原图
img = cv2.imread("image2.png", cv2.IMREAD_UNCHANGED)
# 统计直方图
revalB = cv2.calcHist([img], [0], None, [256], [0, 255])
revalG = cv2.calcHist([img], [1], None, [256], [0, 255])
revalR = cv2.calcHist([img], [2], None, [256], [0, 255])
# 绘制直方图
plt.plot(revalB, color='b')
plt.plot(revalG, color='g')
plt.plot(revalR, color='r')
plt.show()
5.使用掩膜直方图
- 白色部分透明,黑色部分不透明
- 生成掩膜图像
- 生成一个与原始图像相同大小的全黑图像
- 在全黑图像中,指定行和列,设置值为 255(白色)
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读取图片-与python文件相同目录, 灰度图
img = cv2.imread("image2.png", cv2.IMREAD_GRAYSCALE)
# 创建掩膜图像
# 值为 0 的全黑图像
mask = np.zeros(img.shape, np.uint8)
# 设置 200- 400 行,200 - 400 列为白色
mask[200:400, 200:400] = 255
# 统计直方图
revalM = cv2.calcHist([img], [0], mask, [256], [0, 255])
reval = cv2.calcHist([img], [0], None, [256], [0, 255])
#绘制直方图
plt.plot(revalM)
plt.plot(reval)
plt.show()
6.掩膜原理及演示
-
bitwise_and 函数
语法:reval = cv2.bitwise_and(img1, img2)
参数:reval 表示结果图像;img1 表示原始图像;img2 表示掩膜图像
import cv2
import numpy as np
# 读取图片-与python文件相同目录, 灰度图
img = cv2.imread("image2.png", cv2.IMREAD_GRAYSCALE)
# 创建掩膜图像
# 值为 0 的全黑图像
mask = np.zeros(img.shape, np.uint8)
# 设置 200- 400 行,200 - 400 列为白色
mask[200:400, 200:400] = 255
# 被掩膜后的图像
reval = cv2.bitwise_and(img, mask)
# cv2.imshow("mask", mask)
cv2.imshow("reval", reval)
cv2.waitKey(0)
cv2.destroyAllWindows()
7.直方图均衡化原理
-
基础理论
前提:如果一副图像占有全部的灰度级,并且均匀分布
结论:该图像具有高对比度和多变的灰度色调
图像表现:图像细节丰富,质量更高
-
算法
- 计算累计直方图
- 将累计直方图进行区间换算
- 在累计直方图中,概率相近的原始值,会被处理为相同的值
-
下图中,为 3 位位图,灰度级位 1 - 7,首先进行统计直方图
- 归一化处理中,图像像素为 7*7 = 49,所以,像素 0 的个数 9/49 = 0.18,以此类推
- 累计直方图中,0 的值和归一化直方图一致,1 的值为归一化 0+1 的值,即 0.18+0.18=0.37(四舍五入),以此类推;最后一个为 1,所有百分比的和
- 将百分比乘以灰度级,得到结果,即 0.18*7= 1(四舍五入);最终将原始图像的灰度级 0 转为 1,1 转为 3,以此类推;最终将 1 和 2 合并为 3,4 和 5 合并为 5
- 通过合并像素级得到均衡化直方图
8.直方图均衡化函数
-
equalizeHist 函数
语法:reval = cv2.equalizeHist(img)
参数:reval 表示结果图像;img 表示原始图像
import cv2
import matplotlib.pyplot as plt
# 读取图片-与python文件相同目录, 灰度图
img = cv2.imread("image2.png", cv2.IMREAD_GRAYSCALE)
# 直方图均衡化
ravel = cv2.equalizeHist(img)
# 绘制直方图
plt.hist(img.ravel(), 256)
plt.show()
# plt.figure() # 创建新的图像
plt.hist(ravel.ravel(), 256)
plt.show()
- 显示直方图均衡化的图像
import cv2
import matplotlib.pyplot as plt
# 读取图片-与python文件相同目录, 灰度图
img = cv2.imread("image2.png", cv2.IMREAD_GRAYSCALE)
# 直方图均衡化
reval = cv2.equalizeHist(img)
cv2.imshow("img", img)
cv2.imshow("reval", reval)
cv2.waitKey(0)
cv2.destroyAllWindows()
9.subplot函数的使用
-
subplot 函数
功能:同时显示多个窗口
语法:plt.subplot(nrows, ncols, plot_nuber)
参数:nrows 表示行数;ncols 表示列数;plot_number 表示窗口序号
特例:每个参数小于 10 时,可省略逗号,如:plt.subplot(234)
import cv2
import matplotlib.pyplot as plt
# 读取图片-与python文件相同目录, 灰度图
img = cv2.imread("image2.png", cv2.IMREAD_GRAYSCALE)
# 直方图均衡化
reval = cv2.equalizeHist(img)
# 1 行 2 列 序号 1
plt.subplot(121)
plt.hist(img.ravel(), 256)
# 1 行 2 列 序号 2
plt.subplot(122)
plt.hist(reval.ravel(), 256)
plt.show()
10.imshow函数的使用
-
matplot.pyplot.imshow 函数
语法:plt.imshow(img, cmap=None)
参数:img 表示绘制的图像;cmap 表示 colormap;颜色图谱,默认RGB(A)
显示:灰度图像:cmap 默认通道为 RGB(A) ,需要使用参数
cmap=plt.cm.gray
;彩色图像:如果使用 opencv 读取图像,默认通道为 BGR,所以需要调整通道为 RGB(A)
import cv2
import matplotlib.pyplot as plt
# 读取图片-与python文件相同目录
imgA = cv2.imread("image2.png", cv2.IMREAD_GRAYSCALE)
imgB = cv2.imread("image2.png", cv2.IMREAD_UNCHANGED)
# BGR to RGB
new_imgB = cv2.cvtColor(imgB, cv2.COLOR_BGR2RGB)
# 1 行 2 列 序号 1
plt.subplot(121)
plt.imshow(imgA, cmap=plt.cm.gray) # 灰度图
plt.axis('off') # 关闭坐标轴
# 1 行 2 列 序号 2
plt.subplot(122)
plt.imshow(new_imgB)
plt.axis('off') # 关闭坐标轴
plt.show()
11.直方图均衡化对比
- 一个图像中,对比原始图像和直方图均衡化图像,以及对比原始图像直方图和均衡化直方图
import cv2
import matplotlib.pyplot as plt
# 读取图片-与python文件相同目录
img = cv2.imread("image2.png", cv2.IMREAD_GRAYSCALE)
# 直方图均衡化
equ = cv2.equalizeHist(img)
# 2 行 2 列 序号 1
plt.subplot(221)
plt.imshow(img, cmap=plt.cm.gray)
plt.axis('off') # 关闭坐标轴
# 2 行 2 列 序号 2
plt.subplot(222)
plt.imshow(equ, cmap=plt.cm.gray)
plt.axis('off') # 关闭坐标轴
# 2 行 2 列 序号 3
plt.subplot(223)
plt.hist(img.ravel(), 256)
# 2 行 2 列 序号 4
plt.subplot(224)
plt.hist(equ.ravel(), 256)
plt.show()
十四、傅里叶变换
1.傅里叶变换的理论基础
**任何连续周期信号,可以由一组适当的正弦曲线组合而成。— 傅里叶 **
-
时间:动态
-
频率:静态
-
相位:时间差,不是同时开始的一组余弦函数,在叠加时要体现开始的时间
-
下图中,将复杂的时间角度正弦函数图像,转换为简洁的频率角度的图像,相互可逆
2.numpy实现傅里叶变换
-
numpy.fft.fft2 函数
功能:实现傅里叶变换,返回一个复数数值(Complex ndarray)
-
numpy.fft.fftshift 函数
功能:将零频率分量移到频谱中心;将傅里叶变换后的结果(频谱)中左上角的低频移动到中心点
-
np.log 函数
功能:设置频谱的范围,如将傅里叶变换的结果映射到 [0, 255] 区间,使得频谱图像可以显示
语法:20 * np.log( np.abs(fshift) )
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读取图片-与python文件相同目录
img = cv2.imread("image2.png", cv2.IMREAD_GRAYSCALE)
# 计算傅里叶变换
fnum = np.fft.fft2(img)
# 移动低频到中心点
fshift = np.fft.fftshift(fnum)
# 频谱映射区间
reval = 20 * np.log(np.abs(fshift))
# 1 行 2 列 序号 1
plt.subplot(121)
plt.imshow(img, cmap=plt.cm.gray)
plt.title('original')
plt.axis('off') # 关闭坐标轴
# 1 行 2 列 序号 2
plt.subplot(122)
plt.imshow(reval, cmap=plt.cm.gray)
plt.title('reval')
plt.axis('off') # 关闭坐标轴
plt.show()
- 注意:
- 傅里叶变换后得到低频和高频信息,针对低频和高频信息的处理,实现不同的目的
- 傅里叶变换过程是可逆的,即图像经过傅里叶变换后,能够恢复到原始图像
- 在频域对图像进行处理,会反映在逆变换图像上
3.numpy实现逆傅里叶变换
-
完整过程:原始图像 --> 得到图像频谱 --> 处理最高频、最低频信息 --> 还原图像
-
如果只保留低频信息,即为低通滤波,去除边界;如果只保留高频信息,即为高通滤波,保留边界,细节模糊
-
numpy.fft.ifft2 函数
功能:实现逆傅里叶变换,返回一个复数数组(complex ndarray)
-
numpy.fft.ifftshift 函数
功能:fftshift 函数的逆函数
-
np.abs(ifftshift)
功能:设置值的范围,如 [0, 255]
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读取图片-与python文件相同目录
img = cv2.imread("image2.png", cv2.IMREAD_GRAYSCALE)
# 计算傅里叶变换
fnum = np.fft.fft2(img)
# 移动低频到中心点
fshift = np.fft.fftshift(fnum)
# 移动低频到左上角
ishift = np.fft.ifftshift(fshift)
# 逆傅里叶变换
img2 = np.fft.ifft2(ishift)
# 频谱映射区间 (绝对值)
reval = np.abs(img2)
# 1 行 2 列 序号 1
plt.subplot(121)
plt.imshow(img, cmap=plt.cm.gray)
plt.title('original')
plt.axis('off') # 关闭坐标轴
# 1 行 2 列 序号 2
plt.subplot(122)
plt.imshow(reval, cmap=plt.cm.gray)
plt.title('reval')
plt.axis('off') # 关闭坐标轴
plt.show()
4.高通滤波演示
-
低频对应图像内变化缓慢的灰度分量,颜色趋于一致
-
高频对应图像内变化越来越快的灰度分量,由灰度的尖锐过渡造成
-
滤波:接受(通过)或拒绝一定频率的分量
-
低通滤波器:通过低频的滤波器;即衰减高频而通过低频,将模糊一幅图像
-
高通滤波器:通过高频的滤波器;即衰减低频而通过高频,将增强尖锐的细节,导致图像对比度减低
-
频域滤波
- 修改傅里叶变换的高频或低频信息,以达到特殊目的,然后再通过逆傅里叶变换返回到图像域
- 特殊目的:图像增强、图像去噪、边缘检测、特征提取、压缩、加密 等
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读取图片-与python文件相同目录
img = cv2.imread("image2.png", cv2.IMREAD_GRAYSCALE)
# 计算傅里叶变换
fnum = np.fft.fft2(img)
# 移动低频到中心点
fshift = np.fft.fftshift(fnum)
# 高频滤波器:将低频信息设置为 0
rows,cols = img.shape # 形状
crows, ccols = int(rows/2),int(cols/2) # 中心点
fshift[crows-10:crows+10, ccols-10:ccols+10] = 0 # 中心点+10 的正方形范围的频率设置为 0
# 移动低频到左上角
ishift = np.fft.ifftshift(fshift)
# 逆傅里叶变换
img2 = np.fft.ifft2(ishift)
# 频谱映射区间 (绝对值)
reval = np.abs(img2)
# 1 行 2 列 序号 1
plt.subplot(121)
plt.imshow(img, cmap=plt.cm.gray)
plt.title('original')
plt.axis('off') # 关闭坐标轴
# 1 行 2 列 序号 2
plt.subplot(122)
plt.imshow(reval, cmap=plt.cm.gray)
plt.title('reval')
plt.axis('off') # 关闭坐标轴
plt.show()
5.OpenCV实现傅里叶变换
-
dft 函数
语法:reval = cv2.dtf(img, flags)
参数 说明 reval 返回结果,双通道,第一个通道是实数部分,第二个通道是虚数部分 img 图像,需要转换为 np.float32 格式 flags 转换标识; flags=cv2.DFT_COMPLEX_OUTPUT,输出一个复数阵列(振幅、频率) -
magnitude 函数
功能:计算幅值
语法:reval = cv2.magnitude(p1, p2)
参数:p1 表示浮点型 X 坐标值,实数部分;p2 表示浮点型 Y 坐标值,虚数部分
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读取图片-与python文件相同目录
img = cv2.imread("image2.png", cv2.IMREAD_GRAYSCALE)
# 计算傅里叶变换
fnum = cv2.dft(np.float32(img), flags=cv2.DFT_COMPLEX_OUTPUT)
# 移动低频到中心点
fshift = np.fft.fftshift(fnum)
# 计算幅值 # 0 通道,1 通道
reval = 20 * np.log(cv2.magnitude(fshift[:,:,0], fshift[:,:,1]))
# 1 行 2 列 序号 1
plt.subplot(121)
plt.imshow(img, cmap=plt.cm.gray)
plt.title('original')
plt.axis('off') # 关闭坐标轴
# 1 行 2 列 序号 2
plt.subplot(122)
plt.imshow(reval, cmap=plt.cm.gray)
plt.title('reval')
plt.axis('off') # 关闭坐标轴
plt.show()
6.OpenCV实现逆傅里叶变换
-
idft 函数:
功能:逆傅里叶变换
语法:reval = cv2.idft(idata)
参数:reval 表示返回结果,取决于原始数据的类型和大小;idata 表示原始数据,实数或虚数均可
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读取图片-与python文件相同目录
img = cv2.imread("image2.png", cv2.IMREAD_GRAYSCALE)
# 计算傅里叶变换
fnum = cv2.dft(np.float32(img), flags=cv2.DFT_COMPLEX_OUTPUT)
# 移动低频到中心点
fshift = np.fft.fftshift(fnum)
# 移动低频到左上角
ishift = np.fft.ifftshift(fshift)
# 逆傅里叶变换
img2 = cv2.idft(ishift)
# 计算幅值 # 0 通道,1 通道
reval = cv2.magnitude(img2[:,:,0], fshift[:,:,1])
# 1 行 2 列 序号 1
plt.subplot(121)
plt.imshow(img, cmap=plt.cm.gray)
plt.title('original')
plt.axis('off') # 关闭坐标轴
# 1 行 2 列 序号 2
plt.subplot(122)
plt.imshow(reval, cmap=plt.cm.gray)
plt.title('reval')
plt.axis('off') # 关闭坐标轴
plt.show()
7.低通滤波示例
- 通过掩膜图像,构建低通滤波器图像
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读取图片-与python文件相同目录
img = cv2.imread("image2.png", cv2.IMREAD_GRAYSCALE)
# 计算傅里叶变换
fnum = cv2.dft(np.float32(img), flags=cv2.DFT_COMPLEX_OUTPUT)
# 移动低频到中心点
fshift = np.fft.fftshift(fnum)
# 构建低通滤波图像
rows, cols = img.shape
crows, ccols = int(rows/2),int(cols/2) # 中心点
mask = np.zeros((rows, cols, 2), np.uint8)
mask[crows-10:crows+10, ccols-10:ccols+10] = 1
fshift = fshift * mask
# 移动低频到左上角
ishift = np.fft.ifftshift(fshift)
# 逆傅里叶变换
img2 = cv2.idft(ishift)
# 计算幅值 # 0 通道,1 通道
reval = cv2.magnitude(img2[:,:,0], fshift[:,:,1])
# 1 行 2 列 序号 1
plt.subplot(121)
plt.imshow(img, cmap=plt.cm.gray)
plt.title('original')
plt.axis('off') # 关闭坐标轴
# 1 行 2 列 序号 2
plt.subplot(122)
plt.imshow(reval, cmap=plt.cm.gray)
plt.title('reval')
plt.axis('off') # 关闭坐标轴
plt.show()
十五、其它
1.PyCharm无法使用复制粘贴快捷键
问题:PyCharm 编辑器中无法使用 Ctrl+C 和 Ctrl+V 进行复制和粘贴的快捷键操作
原因:安装和正在使用 vim 插件
解决方法:关闭 vim 插件
-
File – Settings… – Plugins – Installed 选项卡 – 取消 Idea Vim 的勾选 – 点击 Apply 按钮
-
重启编辑器
2.too many values to unpack
- 报错:ValueError: too many values to unpack (expected 3)
- 场景:获取拆分图像时,接收返回值的参数数量不对,split 有 4 个返回值,分别是 BGRA