1. OpenCV
1.1 opencv概念
- OpenCV是一个基于BSD许可(开源)发行的跨平台计算机视觉库
- 可以运行在Linux、Windows、Android和Mac OS操作系统上
- 它轻量级而且高效 – 有一系列C函数和少量 C++ 类构成
- 同时提供了 Python、Ruby、MATLAB等语言的接口
- 实现了图像处理和计算机视觉方面的很多通用算法
opencv:
- 是一个第三方库,处理图形和视频
- 是 c 和 c++ 语言实现
- 可以运行在多种平台上: 跨平台
- 可以被多种语言直接调用
- 实现图像处理方法多种通用的算法
基础知识:
熟悉python语言编程 c/c++编程基础 矩阵运算
1.2 开发环境搭建
-
windows下用python语言
-
IDEA: pycharm
-
安装opencv-python库:
pip install opencv-python
2. 主要内容
2.1 读取图片
import cv2 as cvimg = cv.imread("../pics/20.jpg")cv.imshow("img", img)cv.waitKey() # 等待用户操作
cv.destroyAllWindows() # 释放窗口
注意: cv.imread会将图片读成矩阵格式
2.2 main
- 演示一个opencv项目
- 图形图像处理基础知识
- 图像几何变换: 大小, …
- 图像平滑处理
- 图形形态学操作
- 轮廓监测
- 图像对象识别(机器学习)
3. 图像操作基础函数
3.1 图像处理的基础知识
3.1.1 读取图片
格式: img = imread(图片路径名字, 读取方式(0: 灰度图片, 1: 彩色图片))
参数1: 读取的图片的路径
参数2: 读取的方式, 灰度(0)还是彩色(1)
返回值: 图片的矩阵形式
功能: 打开文件,读取文件内容, 将文件转换成矩阵形式
import cv2 as cv# 读取路径为../pics/20.jpg, 读取方式是一个灰度
img = cv.imread("../pics/20.jpg", 0)
3.1.2 创建一个窗口
cv.namedWindow("窗口名字")
3.1.3 在指定的窗口中显示图片
cv.imshow("窗口名字", 图片的矩阵格式)
3.1.4 让窗口等待一定时间
cv.waitKey(时间)# 时间单位是ms(0表示无限制等待,直到用户按任意键结束)
cv.destroyAllWindows()# 释放所有窗口# 释放指定窗口
# cv.destroyWindow("窗口名字")
4. 图像表示
图像处理基本函数: imread()、imshow()、waitKey()、destroyAllWindows()、imwrite()
处理图像就是操作img
img是通过什么方式表示一副图像?
图像的表示:
-
二值图像: 图像中仅有两种颜色: 黑或白
图像: 由多个像素点构成,按照行列分布
用一个矩阵表示图像,
例如 图像 512 * 512 大小,则用 512 * 512(二维矩阵) 矩阵表示图像
像素点是黑色,则对应的矩阵值是 0
像素点是白色,则对应的矩阵值是 255
每一个像素点用一个字节来存储, 矩阵(二维数组) 每一个元素取值范围 0~255
-
灰度图像: 图像中颜色, 共计由256级 0~255
黑色 – 白色
图像 --> 用矩阵表示
矩阵中每一个元素存储图像中对应的像素点的值
5. 彩色图像表示
-
图像的表示方法:
img = cv.imread()
-
二值图像: 0(黑色) 、 255(白色)
-
灰度图像: 黑色 -> 白色 共计分了256等级 ( 0 黑色 --> 255 白色)
-
彩色图像:
- 彩色图像是更常见的一类图像,能表现更加丰富的细节信息
- 神经生理学实验发现: 在视网膜上存在三种不同的颜色感受器能够识别三种不同的颜色(红色、绿色 和蓝色,即三基色)
- 自然界中常见的各种色光都可以通过三基色按照一定的比例混合构成
- 在RGB色彩空间,存在R(红色)通道,G(绿色)通道和B(蓝色)通道,共三个通道
- 每一个色彩通道值得范围都在[0~255]之间,利用三个色彩通道组合表示颜色
结论:
- 彩色图像由R、G、B三个通道构成
- 每个通道都可以理解位一个独立得灰度图像
- 一个彩色图像对应三个矩阵
- 用一个三位数组表示一副RGB色彩空间得彩色图像
【图像表示方法总结】:
- 一般情况下, 在RGB得色彩空间,图像得通道是R-G-B,但是在opencv中,通道得顺序是 B-G-R
- 在图像处理中可以根据需要对图像得通道顺序进行转换,也可以根据需要对不同色彩空间的图像进行类型转换
- 将灰度图像转为二值图像
- 将彩色图像转为灰度图像
【像素处理】:
- 像素是构成图像的基本单位,像素处理是图像的基本操作
- 通过位置索引的形式对图像内的元素进行访问、处理
一、二值图像及灰度图像
- opencv中,最小的数据类型是无符号的8位数,没有二值图像这种数据类型,使用0表示黑色,使用255表示白色
- 将二值图像理解位特殊的灰度图像,处理方式和灰度图像一致
- 灰度图像是一个二维数组,通过使用img[i, j]的形式访问其中像素点,表示第i行和第j列上的像素点对应的像素值
小栗子: 读取一个灰度图像,对其中的像素访问和修改
import cv2 as cvimg = cv.imread("../pics/20.jpg")
img[0:100, 0:100] = 0cv.imshow("img", img)cv.waitKey(0)
cv.destroyAllWindows()
5.1 彩色图像的修改
- RGB模式的彩色图像在读入进行处理时: 按照行的方式一次读取RGB图像的 B 通道、G通道、R通道像素点, 存储为一个三位数组
- 一幅 R行*C列的原始RGB图像,以BGR模式的三维数组形式存储
- img[0,0,0]表示第0行第0列B通道像素值
- img[0,0]表示第0行第0列像素点的BGR值,例如数值为: [0, 0, 255]
【栗子】: 使用numpy生成一个蓝色(绿色、红色)的 300 *300 的矩阵
import numpy as np
import cv2 as cv# 蓝色矩阵
blue = np.zeros((300, 300, 3), dtype=np.uint8)
blue[:, :, 0] = 255
print("blue")
cv.imshow("blue", blue)# 绿色矩阵
green = np.zeros((300, 300, 3), dtype=np.uint8)
green[:, :, 1] = 255
print("green")
cv.imshow("green", green)# 红色矩阵
red = np.zeros((300, 300, 3), dtype=np.uint8)
red[:, :, 2] = 255
print("red")
cv.imshow("red", red)cv.waitKey(0)
cv.destroyAllWindows()
【栗子2】: 画一个条纹状的 蓝绿红 300 * 300 的图像
import numpy as np
import cv2 as cvbgr = np.zeros((300, 300, 3), dtype=np.uint8)
# 蓝色
bgr[:, 0:100, 0] = 255
# 绿色
bgr[:, 100:200, 1] = 255
# 红色
bgr[:, 200:300, 2] = 255cv.imshow("bgr", bgr)cv.waitKey(0)
cv.destroyAllWindows()
5.2 图像操作小结
打印某一个像素bgr的值
img = cv.imread("../pics/20.jpg", 1)
# 读取第100行,100列像素位置的bgr值
(b, g, r) = img[100, 100]
print(b, g ,r)
【栗子】: 在图像(彩色)中画一个水平和垂直线
import cv2 as cvimg = cv.imread("../pics/20.jpg", 1)# B通道
img[:, 0, 0] = 0
img[0, :, 0] = 0# G通道
img[:, 0, 1] = 0
img[0, :, 1] = 0# R通道
img[:, 0, 2] = 0
img[0, :, 2] = 0print(img.shape)cv.imshow("img", img)cv.waitKey(0)
cv.destroyAllWindows()
【栗子】: 增强图像的像素值
import cv2 as cvimg = cv.imread("../pics/20.jpg", 0)
(row, col) = img.shapecv.imshow("pre", img)for i in range(0,row):for j in range(0,col):tmp = img[i][j] +90if(tmp > 255):tmp = tmp-255img[i][j] = tmpcv.imshow("after", img)cv.waitKey(0)
5.3 使用numpy.array访问像素
使用numpy.array提供的item()和itemset()函数访问和修改像素.
两个函数经过优化处理,能够大幅度提高处理效率
比直接用索引访问快的多、可读性更好
-
访问灰度图像:
- item(行, 列)
- itemset((行,列), 值)
-
访问彩色图像:
- item(行,列,通道)
- itemset(三元组索引, 新值)
【栗子1】: 修改和访问灰度图像
import numpy as npimg = np.random.randint(10, 99, size=[5, 5], dtype=np.uint8)
print(img)
print(img.item(3, 2))
img.itemset((3, 2), 255)
print(img.item(3, 2))
【栗子2】: 生成一个灰度图像,其中的像素值为随机数
import numpy as np
import cv2 as cvimg = np.random.randint(0,255, size=[256, 256], dtype=np.uint8)
cv.imshow("img", img)cv.waitKey(0)
cv.destroyAllWindows()
【栗子3】: 读取一个灰度图片,并对其进行像素值访问
import numpy as np
import cv2 as cvimg = cv.imread("../pics/20.jpg", 0)cv.imshow("before", img)for i in range(0,255):for j in range(10,40):img.itemset((i,j), 255)cv.imshow("after", img)cv.waitKey(0)
cv.destroyAllWindows()
【栗子4】: 生成一个彩色图像
# 生成一个256 * 256的随机彩色图像
import numpy as np
import cv2 as cvimg = np.random.randint(0, 255, size=[256, 256, 3], dtype=np.uint8)cv.imshow("img", img)cv.waitKey(0)
cv.destroyAllWindows()
5.4 感兴趣区域(ROI)
处理图像过程中,可能回对图像某一个特定区域感兴趣,该区域称为感兴趣区域(Region of Interest, ROI)
设定感兴趣区域后,就可以对该区域进行整体操作:
- 将一个感兴趣的区域A赋值给变量B
- 将变量B赋值给另一个区域C,达到区域C内赋值道区域A的目的
【栗子】: 对脸部打码
import cv2 as cv
import numpy as npimg = cv.imread("../../pics/20.jpg", 0)# head = img[15:215, 150:330]
mask = np.random.randint(0, 255, size=[200, 180], dtype=np.uint8)img[15:215, 150:330] = mask
cv.imshow("img", img)cv.waitKey(0)
cv.destroyAllWindows()
5.5 通道操作
RGB图在opencv中按照BGR的形式存储,在图像处理过程中根据实际,对通道进行拆分和合并
通道拆分:
- 通过索引拆分:
b = img[:,:,0]
g = img[:,:,1]
r = img[:,:,2]
- 通过函数拆分:
b,g,r = cv.splite(img)
5.5.1 通道的拆分
【栗子】: 将彩色图像中的RGB通道分离处理,并单独成图显示
import cv2 as cvimg = cv.imread("../../pics/20.jpg")b, g, r = cv.split(img)
cv.imshow("b", b)
cv.imshow("g", g)
cv.imshow("r", r)cv.waitKey(0)
cv.destroyAllWindows()
5.5.2 通道的合并
【栗子】: 将分离出来的bgr通道合并成 rgb通道并显示出来
import cv2 as cvimg = cv.imread("../../pics/20.jpg")b, g, r = cv.split(img)rgb = cv.merge([r, g, b])cv.imshow("rgb", rgb)
cv.imshow("bgr", img)cv.waitKey(0)
cv.destroyAllWindows()
5.6 图像属性
shape:
- 如果是彩色图像,返回包括 行数、列数、通道个数
- 如果是二值图像或者灰度图像: 返回 行数、列数
size: 返回图像的大小: 行数 * 列数 * 通道个数
dtype: 返回图像数据类型
import cv2 as cvimg = cv.imread("../../pics/20.jpg")
# 矩阵的维度
print(img.shape)
# 所占字节数
print(img.size)
# 元素的类型
print(img.dtype)img_grey = cv.imread("../../pics/20.jpg", 0)
# 矩阵的维度
print(img_grey.shape)
# 所占字节数
print(img_grey.size)
# 元素的类型
print(img_grey.dtype)
6. 图像运算
- 图像的加法运算、位运算
- 使用基础运算实现: 位平面分解、图像异或加密、数字水印、脸部打码/解码 等功能
6.1 图像的加法运算
import cv2 as cv# 读取一个灰度图像
img = cv.imread("../../pics/20.jpg", 0)img[:,:] += 20cv.imshow("img", img)cv.waitKey(0)
cv.destroyAllWindows()
6.1.1 使用cv.add进行加法运算
-
处理图像过程中,进程需要对图像进行加法运算.可以它通过"+"或者 cv2.add()函数实现
-
用加号预运算符对图像a和图像b进行求和运算规则如下:
a+b = mod(a+b, 255)
- 像素值之和大于255处理为255的模,否则像素值为加的结果
-
使用函数cv2.add()计算像素值和规则:
- 计算结果 = cv2.add(图像a, 图像b)
- 像素之和大于255处理为最大值255,否则像素值为加的结果
【栗子】: 使用add添加像素
import cv2 as cvimg = cv.imread("../../pics/20.jpg")cv.imshow("before", img)
img = cv.add(img, 90)cv.imshow("img", img)
cv.waitKey(0)
cv.destroyAllWindows()
【栗子】: 使用add进行两张图片相加
import cv2 as cvimg_16 = cv.imread("../../pics/16.jpg")
img_18 = cv.imread("../../pics/18.jpg")
img_20 = cv.imread("../../pics/20.jpg")
shape_500_300_20 = img_20[0:300, 0:500]
shape_500_300_16 = img_16[0:300, 0:500]comb = cv.add(shape_500_300_20, shape_500_300_16)cv.imshow("comb", comb)cv.waitKey(0)
cv.destroyAllWindows()
注意: add函数是 两个相加,如果值超过255就取255 .
6.1.2 图像图像加权和
-
加权和: 计算两幅图像的像素之和时,将每幅图像权重考虑进来,使用公式
dst = saturate(src1 * a + src2 * b + r)
-
对应opencv()中的函数:
dst = cv2.addWeighted(src1, a, src2, b, r)
import cv2 as cvimg_16 = cv.imread("../../pics/16.jpg")
img_20 = cv.imread("../../pics/20.jpg")
dali = img_20[0:300, 0:500]
mao = img_16[0:300, 0:500]comb = cv.addWeighted(dali, 0.1, mao, 0.9, 0)cv.imshow("comb", comb)cv.waitKey(0)
cv.destroyAllWindows()
6.2 图像的位运算
按位逻辑运算在图形处理中是一种非常重要的处理方式:
函数 | 作用 |
---|---|
cv.bitwise_and() | 按位与 |
cv.bitwise_or() | 按位或 |
cv.bitwise_xor() | 按位异或 |
cv.bitwise_not() | 按位取反 |
【掩模】:
使用掩模参数时,操作只会在掩模值为空的像素点上执行,在opencv中的函数如下:
img3 = cv.add(img1, img2, mask=mask)
import numpy as npimg = cv.imread("../../pics/20.jpg")w, h, c = img.shape
mask = np.zeros((w, h), dtype=np.uint8)mask[40:160, 80:160] = 255
mask[40:200, 40:80] = 255exam = cv.bitwise_and(img, img, mask=mask)cv.imshow("mask", mask)
cv.imshow("exam", exam)cv.waitKey(0)
cv.destroyAllWindows()
6.2.1 位运算的作用
位平面分解:
-
概念: 将灰度图中处于同一比特位上的二进制像素值进行组合,得到一幅二进制图像,该图像称为灰度图像的一个位平面
-
在8位灰度图中,每一个像素使用8位二进制来表示,其值范围【0~255】
- 表示为:
value = a7* 2^7 + a6 * 2^6 + a5 * 2^5 + a4* 2^4 + a3 * 2^3 + a2 * 2^2 + a1 *2^1 + a0 * 2^0
- a0~a7的可能值是0或者1 ,各个权重不一样,a7权重最高,a0最低。a7的值对图像影响最大,而a0得值对图像影响最小
- 表示为:
-
通过提取灰度像素点二进制像素值得每一个比特位组合,可以得到8个位平面,组成8个图像.
- a7权重最高,所构成得位平面与原始图像相关性最高,最类似a0权重最低,所构成得位平面与原始图像相关性最低,该平面是杂乱无章
6.2.2 位平面分解的步骤
- 图像预处理:
- 读取原始图像,获取原始图像的宽度和高度
- 构造提取矩阵
- 使用按位与操作将一个数值指定位上的数字提取出来
- 提取第0位: xxxxxxxx & 0000 0001 --> 0000 000x
- 提取第3位: xxxxxxxx & 0000 1000 --> 0000 x000
- 提取第5位: xxxxxxxx & 0001 0000 --> 000x 0000
- 建立一个值为 2^n的Mat作为提取矩阵,与原始图像进行按位与运算 —> 提取第n个位平面
- 使用按位与操作将一个数值指定位上的数字提取出来
import numpy as np
import cv2 as cvimg = cv.imread("../../pics/20.jpg", 0)w, h = img.shapemat = np.ones((w, h), dtype=np.uint8)rest0 = cv.bitwise_and(img, mat) * 128cv.imshow("img", img)
cv.imshow("rest0", rest0)cv.waitKey(0)
cv.destroyAllWindows()
-
提取位平面
- 将灰度图像与提取矩阵进行按位与运算,得到各个平面
-
阈值处理:
- 通过计算得到位平面是一个二值图
- 直接显示得到一张黑色图像,默认显示是8位灰度级,像素值小显示位黑色
- 提取位平面后,让图像能够以黑白颜色显示出来,必须进行阈值处理
- 二值命名进行阈值处理: 将其中大于零的值处理为255
6.2.3 图像的加密与解密
-
通过按位异或运算可以实现图像的加密和解密
-
原始图像与神秘图像进行按位异或,可以实现加密
-
将加密后的图像与密钥图像再次按位异或,实现解密
【栗子】: 生成一个随机的密钥,给图片加密.
import cv2 as cv
import numpy as npimg = cv.imread("../../pics/20.jpg")
dst = np.random.randint(0, 255, img.shape, dtype=np.uint8)# 给图像加密
img = cv.bitwise_xor(img, dst)cv.imshow("withkey", img)# 图像解密
img = cv.bitwise_xor(img, dst)
cv.imshow("unshiftkey", img)cv.waitKey(0)
cv.destroyAllWindows()
6.2.4 数字水印
概念
- 最低有效位: 指的是一个二进制数中的第0位(最低位)
- 最低有效位信息隐藏: 将一个需要隐藏的二值图像信息嵌入载体图像最低有效位.将载体图像的最低有效位替换为当前需要隐藏的二值图像,实现二值图像隐藏的目的
- 二值图像处于载体图像的最低有效位上,对这个图像影响非常不明显,具有较高的隐蔽性
- 需要将载体图像的最低有效位层提取出来,即可得到嵌入在该位上的二值图像达到提取秘密信息的目的
- 这种信息隐蔽也被称为数字水印, 通过该方式可以实现: 信息隐藏、版权认证、身份认证等功能
- 如果嵌入式信息是秘密信息,则实现实心的隐藏
- 如果嵌入式载体图像内的信息是版权信息,实现版权认证
- 如果嵌入式载体图像内的信息是身份信息,实现数字签名
- 被嵌入载体图像内的信息也被称为数字水印信息
将一幅二值图像嵌入到一个载体图像的第0个位平面
将载体图像的第0个位平面提取出来,获得二值图像的信息
原理
-
嵌入过程: 将载体图像的第0个位平面替换位数字水印信息(一幅二值图像)
-
提取过程: 将载体图像的最低有效位所构成的第0个位平面提取出来,得到数字水印信息
-
嵌入式过程:
- 载体图像为灰度图像,数字水印为二值图像,则直接操作
- 二者均为彩色图像,需要先进行通道分解、图层分解后进行操作
实现
嵌入水印图像:
- 将原始图像每个像素的最低位全部清0
- 水印图像 --> 二值图像 --> 二进制二值图像
- 将水印二进制图像嵌入到原始图像第0个位平面
提取水印图像:
- 将载体图像的第0个位平面提取出来 --> 二值图像(1, 0)
- 二值图像 --> 转成一幅二值图像(255, 0)
- 将载体图像的第0个位平面全部置0
import cv2 as cv
import numpy as np# 载体图像
img = cv.imread("../../pics/16.jpg", 0)
r, c = img.shape# 将第0个位平面全部置为0
t254 = np.ones((r, c), dtype=np.uint8) * 254
exam = cv.bitwise_and(img, t254)# 读取数字水印
watermark = cv.imread("../../pics/18.jpg", 0)# 将水印 --> 二值图像
w = watermark[:, :] > 1
watermark[w] = 1# 水印嵌入载体图像
e = cv.bitwise_or(exam, watermark)# 水印的提取
r,c = e.shape
t1 = np.ones((r,c), dtype=np.uint8)
wm = cv.bitwise_and(e, t1)w = wm[:,:] > 0
wm[w] = 255cv.imshow("wm", wm)cv.waitKey(0)
cv.destroyAllWindows()