参考:
Opencv(Open Source Computer Vision Library)是一个基于开源发行的跨平台计算机视觉库,它实现了图像处理和计算机视觉方面的很多通用算法,已成为计算机视觉领域最有力的研究工具。在这里我们要区分两个概念:图像处理和计算机视觉的区别:图像处理侧重于“处理”图像–如增强,还原,去噪,分割等等;而计算机视觉重点在于使用计算机来模拟人的视觉,因此模拟才是计算机视觉领域的最终目标。
OpenCV用C++语言编写,它具有C ++,Python,Java和MATLAB接口,并支持Windows,Linux,Android和Mac OS, 如今也提供对于C#、Ch、Ruby,GO的支持。
OpenCV发展历史
OpenCV 0.X
OpenCV于1999年由Intel建立,如今由Willow Garage公司提供支持。
1999年1月,CVL项目启动。主要目标是人机界面,能被UI调用的实时计算机视觉库,为Intel处理器做了特定优化。
2000年6月,第一个开源版本OpenCV alpha 3发布。
2000年12月,针对linux平台的OpenCV beta 1发布。
OpenCV 1.X
OpenCV 最初基于C语言开发,API也都是基于C的,面临内存管理、指针等C语言固有的麻烦。
2006年10月, 正式发布OpenCV 1.0版本,同时支持mac os系统和一些基础的机器学习方法,如神经网络、随机森林等,来完善对图像处理的支持。
2009年9月,OpenCV 1.2(beta2.0)发布。
OpenCV 2.X
当C++流行起来,OpenCV 2.x发布,其尽量使用C++而不是C,但是为了向前兼容,仍保留了对C API的支持。
2009年9月2.0 beta发布,主要使用CMake构建。
2010年3月:2.1发布
2010年12月6日,OpenCV 2.2发布
2011年8月,OpenCV 2.3发布。
2012年4月2日,发布OpenCV 2.4.
OpenCV 3.X
随着3.x的发布,1.x的C API将被淘汰不再被支持,以后C API可能通过C++源代码自动生成。3.x与2.x不完全兼容,与2.x相比,主要的不同之处在于OpenCV 3.x 的大部分方法都使用了OpenCL加速。
2014年8月, 3.0 alpha发布,除大部分方法都使用OpenCL加速外,3.x默认包含以及使用IPP(一套跨平台的软件函数库)
2017年8月,发布3.3版本,OpenCV开始支持C++ 11构建,同时加强对神经网络的支持。
OpenCV 4.X
2018年10月4.0.0发布,OpenCV开始需要支持C++11的编译器才能编译,同时对几百个基础函数使用"wide universal intrinsics"重写,极大改善了opencv处理图像的性能。
本章将帮助您迈出使用OpenCV学习图像处理和计算机视觉的第一步。你将通过一些简单的例子学习一些重要的课程。在本章中,您将了解以下内容:
windows安装opencv:
# 清华源安装opencv
pip install opencv-python -i https://pypi.tuna.tsinghua.edu.cn/simple
或者查看官网安装方式(conda安装)
import cv2
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
from IPython.display import Image
我们将使用以下图片作为示例,使用ipython图像函数来加载和显示图像。
# Display 18x18 pixel image.
Image(filename='checkerboard_18x18.png')
# Display 84x84 pixel image.
Image(filename='checkerboard_84x84.jpg')
cv2.imread
读取图像 OpenCV可以使用使用cv2.imread函数读取不同类型的图像(JPG、PNG等)。您可以加载灰度图像、彩色图像,也可以使用Alpha通道加载图像。其语法为:
retval = cv.imread(filename[, flags])
读取模式:ImreadModes
cv2.imread()
从指定文件加载图像并返回该图像的矩阵。参数说明:
retval
:读取的 OpenCV 图像,nparray 多维数组。如果无法读取图像(文件丢失,权限不正确,格式不支持或无效),该函数返回一个空矩阵None(此时不报错)。filename
:读取图像的文件路径和文件名,绝对路径相对路径都行。flags
:读取图片的方式,可选项
1
或cv2.IMREAD_COLOR:始终将图像转换为 3 通道BGR彩色图像,默认方式0
或cv2.IMREAD_GRAYSCALE:始终将图像转换为单通道灰度图像。-1
或cv2.IMREAD_UNCHANGED:按原样返回加载的图像(使用Alpha通道)2
或cv2.IMREAD_ANYDEPTH:在输入具有相应深度时返回16位/ 32位图像,否则将其转换为8位4
或cv2.IMREAD_ANYCOLOR:以任何可能的颜色格式读取图像BGR
格式,而 PIL、PyQt、matplotlib 等库使用的是 RGB
格式。# 将图像读取为灰度图
cb_img = cv2.imread("checkerboard_18x18.png",0) # 就是上面那张18*18的图像
# 打印图像数据(像素值),2D numpy数组
# 每个像素值为8位[0,255]
print(cb_img)
[[ 0 0 0 0 0 0 255 255 255 255 255 255 0 0 0 0 0 0]
[ 0 0 0 0 0 0 255 255 255 255 255 255 0 0 0 0 0 0]
[ 0 0 0 0 0 0 255 255 255 255 255 255 0 0 0 0 0 0]
[ 0 0 0 0 0 0 255 255 255 255 255 255 0 0 0 0 0 0]
[ 0 0 0 0 0 0 255 255 255 255 255 255 0 0 0 0 0 0]
[ 0 0 0 0 0 0 255 255 255 255 255 255 0 0 0 0 0 0]
[255 255 255 255 255 255 0 0 0 0 0 0 255 255 255 255 255 255]
[255 255 255 255 255 255 0 0 0 0 0 0 255 255 255 255 255 255]
[255 255 255 255 255 255 0 0 0 0 0 0 255 255 255 255 255 255]
[255 255 255 255 255 255 0 0 0 0 0 0 255 255 255 255 255 255]
[255 255 255 255 255 255 0 0 0 0 0 0 255 255 255 255 255 255]
[255 255 255 255 255 255 0 0 0 0 0 0 255 255 255 255 255 255]
[ 0 0 0 0 0 0 255 255 255 255 255 255 0 0 0 0 0 0]
[ 0 0 0 0 0 0 255 255 255 255 255 255 0 0 0 0 0 0]
[ 0 0 0 0 0 0 255 255 255 255 255 255 0 0 0 0 0 0]
[ 0 0 0 0 0 0 255 255 255 255 255 255 0 0 0 0 0 0]
[ 0 0 0 0 0 0 255 255 255 255 255 255 0 0 0 0 0 0]
[ 0 0 0 0 0 0 255 255 255 255 255 255 0 0 0 0 0 0]]
import urllib.request as request
response = request.urlopen("https://profile.csdnimg.cn/8/E/F/0_youcans")
imgUrl = cv2.imdecode(np.array(bytearray(response.read()), dtype=np.uint8), -1)
plt.imshow(imgUrl)
TypeError: Image data of dtype object cannot be converted to float
imgFile = "images/测试图01.png" # 带有中文的文件路径和文件名
img = cv2.imread(imgFile, flags=1) # 不支持中文路径和文件名,读取失败,但不会报错,打印img没有反应
plt.imshow(img) # 报错TypeError: Image data of dtype object cannot be converted to float
使用 imdecode 可以读取带有中文的文件路径和文件名
img = cv2.imdecode(np.fromfile(imgFile, dtype=np.uint8), -1)
plt.imshow(img)
在AI stadio平台创建的notebook中,可以直接使用cv2.imread正确读取中文名图像。
imgBGR = cv2.imread("../images/imgLena.tif", 1)
print("img shape is ", imgBGR.shape)
print("img size is ", imgBGR.size,'\n')
img_gry = cv2.imread("../images/imgLena.tif", 0)
print("img_gry shape is: ", img_gry.shape)
print("img_gry size is ", img_gry.size)
print("Data type of img is ", imgBGR.dtype)
img shape is (599, 1440, 3)
img size is 2587680
img_gry shape is: (599, 1440)
img_gry size is 862560
Data type of img is uint8
imshow
函数用于在指定窗口中显示图像,其其语法为:None=cv2.imshow(winname,img)
。
winname
为窗口名,img
是 OpenCV 图像,nparray 多维数组注意:
destroyWindow()
函数关闭指定的显示窗口,也可以用 destroyAllWindows()
函数关闭所有的显示窗口。示例:打开python自带的IDLE,输入以下代码:
import os
>>> os.chdir('E:\CV\opencv-python-free-course-code')
>>> print (os.getcwd())
E:\CV\opencv-python-free-course-code
>>> import cv2
>>> img=cv2.imread("images/imgLena.tif",-1)
>>> cv2.imshow("winname", img)
电脑自动弹出窗口winname,以原尺寸(1280×768)打开图片
namedWindow函数:用于创建一个具有合适名称和大小的窗口,以在屏幕上显示图像和视频。(默认情况下,图像以其原始大小显示)
函数语法为:None = cv.namedWindow( winname[, flags] )
参数:
window_name:将显示图像/视频的窗口的名称
flag: 表示窗口大小是自动设置还是可调整。
返回值:它不返回任何东西
图像窗口将在 waitKey()
函数所设定的时长(毫秒)后自动关闭,waitKey(0)
表示窗口显示时长为无限。
import cv2
path = '../images/imgLena.tif'
image = cv2.imread(path)
# 使用namedWindow()函数,创建名为Display的窗口
# flag为WINDOW_AUTOSIZE, 表示自动调整窗口大小,目测是大小为图片原尺寸
cv2.namedWindow("Display", cv2.WINDOW_AUTOSIZE)
cv2.imshow('Display', image)
# 等待按键命令, 1000ms 后自动关闭
key = cv2.waitKey(0)
cv2.destroyAllWindows() # 取消所有窗口
cv2.WINDOW_NORMAL
,可手动调节窗口大小(弹出的窗口可以拉伸),也可按指定大小的窗口显示图像cv2.namedWindow("Demo1", cv2.WINDOW_NORMAL) # 手动调节窗口
cv2.resizeWindow("Demo2", 400, 300) # 指定窗口大小
cv2.imshow('Demo1', image)
cv2.imshow('Demo2', image)
cv2.destroyAllWindows()
retval = numpy.hstack((img1, img2, …))
# 水平拼接retval = numpy.vstack((img1, img2, …))
# 垂直拼接imgFile1 = "../images/imgLena.tif" # 读取文件的路径
img1 = cv2.imread(imgFile1, flags=1) # flags=1 读取彩色图像(BGR)
imgFile2 = "../images/imgGaia.tif" # 读取文件的路径
img2 = cv2.imread(imgFile2, flags=1) # # flags=1 读取彩色图像(BGR)
imgStack = np.hstack((img1, img2)) # 相同大小图像水平拼接
cv2.imshow("Demo4", imgStack) # 在窗口 "Demo4" 显示图像 imgStack
key = cv2.waitKey(0) # 等待按键命令, 1000ms 后自动关闭
matplotlib.pyplot.imshow(img[, cmap])
img
:图像数据,nparray 多维数组,对于 openCV(BGR)格式图像要先进行格式转换(OpenCV 使用 BGR 格式,matplotlib/PyQt 使用 RGB 格式)cmap
:颜色图谱(colormap),默认为 RGB(A) 颜色空间
cmap=‘gray’
进行参数设置import matplotlib.pyplot as plt
plt.imshow(cb_img) # 之前读的灰度图,没有设置灰度显示
# Set color map to gray scale for proper rendering.
plt.imshow(cb_img, cmap='gray')
另一个例子:
# 将图像读为灰度图
cb_img_fuzzy = cv2.imread("checkerboard_fuzzy_18x18.jpg",0)
print(cb_img_fuzzy)
plt.imshow(cb_img_fuzzy,cmap='gray')
[[ 0 0 15 20 1 134 233 253 253 253 255 229 130 1 29 2 0 0]
[ 0 1 5 18 0 137 232 255 254 247 255 228 129 0 24 2 0 0]
[ 7 5 2 28 2 139 230 254 255 249 255 226 128 0 27 3 2 2]
[ 25 27 28 38 0 129 236 255 253 249 251 227 129 0 36 27 27 27]
[ 2 0 0 4 2 130 239 254 254 254 255 230 126 0 4 2 0 0]
[132 129 131 124 121 163 211 226 227 225 226 203 164 125 125 129 131 131]
[234 227 230 229 232 205 151 115 125 124 117 156 205 232 229 225 228 228]
[254 255 255 251 255 222 102 1 0 0 0 120 225 255 254 255 255 255]
[254 255 254 255 253 225 104 0 50 46 0 120 233 254 247 253 251 253]
[252 250 250 253 254 223 105 2 45 50 0 127 223 255 251 255 251 253]
[254 255 255 252 255 226 104 0 1 1 0 120 229 255 255 254 255 255]
[233 235 231 233 234 207 142 106 108 102 108 146 207 235 237 232 231 231]
[132 132 131 132 130 175 207 223 224 224 224 210 165 134 130 136 134 134]
[ 1 1 3 0 0 129 238 255 254 252 255 233 126 0 0 0 0 0]
[ 20 19 30 40 5 130 236 253 252 249 255 224 129 0 39 23 21 21]
[ 12 6 7 27 0 131 234 255 254 250 254 230 123 1 28 5 10 10]
[ 0 0 9 22 1 133 233 255 253 253 254 230 129 1 26 2 0 0]
[ 0 0 9 22 1 132 233 255 253 253 254 230 129 1 26 2 0 0]]
import matplotlib.pylab as pylab
pylab.imshow(cb_img)
pylab
:结合了pyplot和numpy,将numpy导入了其命名空间中,对交互式使用来说比较方便,既可以画图又可以进行简单的计算,pylab表现的和matlab更加相似pyplot
:相比pylab更加纯粹,如果只是打印图片,使用这个就行。# 读取并显示 Coca-Cola logo.
Image("coca-cola-logo.png")
# 读取图像,并打印其属性
coke_img = cv2.imread("coca-cola-logo.png",1)
print("Image size is ", coke_img.shape)
print("Data type of image is ", coke_img.dtype)
print("")
Image size is (700, 700, 3)
Data type of image is uint8
plt.imshow(coke_img)
# What happened?
上面显示的颜色与实际图像不同。这是因为matplotlib需要RGB格式的图像,而OpenCV则以BGR格式存储图像。因此,为了正确显示,我们需要反转图像的通道。
coke_img_channels_reversed = coke_img[:, :, ::-1]
plt.imshow(coke_img_channels_reversed)
cv2.split
:将多通道arrays划分为多个单通道array。(spilt函数文档)cv2.merge
:将多个array合并为一个多通道数组arrays。所有输入矩阵的大小必须相同。
- 直接用 imshow 显示返回的单通道对象,将被视为 (width, height) 形状的灰度图像
- 如果要正确显示某一颜色分量,需要增加另外两个通道值(置 0)转换为 BGR 三通道格式,再用 imshow 才能显示为拆分通道的颜色。
# 将图像拆分为B,G,R components
img_NZ_bgr = cv2.imread("New_Zealand_Lake.jpg",cv2.IMREAD_COLOR) # IMREAD_COLOR就是flag=1,默认读取彩色图像
b,g,r = cv2.split(img_NZ_bgr)
# 显示各个通道
plt.figure(figsize=[20,5])
plt.subplot(141);plt.imshow(r,cmap='gray');plt.title("Red Channel");
plt.subplot(142);plt.imshow(g,cmap='gray');plt.title("Green Channel");
plt.subplot(143);plt.imshow(b,cmap='gray');plt.title("Blue Channel");
# 将单通道扩展为三通道
imgZeros = np.zeros_like(img_NZ_bgr) # 创建与 img1 相同形状的黑色图像
imgZeros[:,:,1] = g # 在黑色图像模板添加绿色分量 g
cv2.imshow("channel G", imgZeros) # 扩展为 BGR 通道
# 将各个通道合并到BGR图像中
imgMerged = cv2.merge((b,g,r))
imgStack = np.stack((b, g, r), axis=2) # 效果和cv2.merge等价,但是操作更简单
# 显示合并后的图像
plt.subplot(144);plt.imshow(imgMerged[:,:,::-1]);plt.title("Merged Output");
色彩空间是指通过多个(通常为 3个或4个)颜色分量构成坐标系来表示各种颜色的模型系统。色彩空间中的每个像素点均代表一种颜色,各像素点的颜色是多个颜色分量的合成或描述。
彩色图像可以根据需要映射到某个色彩空间进行描述。在不同的工业环境或机器视觉应用中,使用的色彩空间各不相同。
常见的色彩空间包括:GRAY 色彩空间(灰度图像)、XYZ 色彩空间、YCrCb 色彩空间、HSV 色彩空间、HLS 色彩空间、CIELab 色彩空间、CIELuv 色彩空间、Bayer 色彩空间等。
计算机显示器采用 RGB 色彩空间,数字艺术创作经常采用 HSV/HSB 色彩空间,机器视觉和图像处理系统大量使用 HSl、HSL色彩空间。各颜色分量的含义分别为:
RGB
:红色(Red)、绿色(Green)、蓝色(Blue);HSV/HSB
:色调(Hue)、饱和度(Saturation)和明度(Value/Brightness);HSl
:色调(Hue)、饱和度(Saturation)和灰度(Intensity);HSL
:包括色调(Hue)、饱和度(Saturation)和亮度(Luminance/Lightness)。 RGB
模型是一种加性色彩系统,色彩源于红、绿、蓝三基色。用于CRT显示器、数字扫描仪、数字摄像机和显示设备上,是当前应用最广泛的一种彩色模型。
R G B ⟷ G R A Y RGB \longleftrightarrow GRAY RGB⟷GRAY
GRAY 表示灰度图像,通常指 cv_8U 灰度图,有256个灰度级:0-255。
RGB → GRAY: g r a y = 0.299 ∗ R + 0.587 ∗ G + 0.114 ∗ B gray = 0.299*R + 0.587*G + 0.114*B gray=0.299∗R+0.587∗G+0.114∗B
R G B ⟷ H S V RGB \longleftrightarrow HSV RGB⟷HSV
更多颜色空间转换公式,详见 OpenCV官方文档: OpenCV: Color conversions 。
使用函数 cv.cvtColor()可以进行色彩空间类型转换,将图像从一个色彩空间转换到另一个色彩空间。例如,在进行图像的特征提取、距离计算时,往往先将图像从 RGB 色彩空间转换为灰度色彩空间。函数语法为:
cvtColor(src, code[, dst[, dstCn]]) -> dst
示例:
# 读取原始图像
imgBGR = cv2.imread("../images/imgLena.tif", flags=1) # 读取为BGR彩色图像
print(imgBGR.shape)
imgRGB = cv2.cvtColor(imgBGR, cv2.COLOR_BGR2RGB) # BGR 转换为 RGB, 用于 PyQt5, matplotlib
imgGRAY = cv2.cvtColor(imgBGR, cv2.COLOR_BGR2GRAY) # BGR 转换为灰度图像
imgHSV = cv2.cvtColor(imgBGR, cv2.COLOR_BGR2HSV) # BGR 转换为 HSV 图像
imgYCrCb = cv2.cvtColor(imgBGR, cv2.COLOR_BGR2YCrCb) # BGR转YCrCb
imgHLS = cv2.cvtColor(imgBGR, cv2.COLOR_BGR2HLS) # BGR 转 HLS 图像
imgXYZ = cv2.cvtColor(imgBGR, cv2.COLOR_BGR2XYZ) # BGR 转 XYZ 图像
imgLAB = cv2.cvtColor(imgBGR, cv2.COLOR_BGR2LAB) # BGR 转 LAB 图像
imgYUV = cv2.cvtColor(imgBGR, cv2.COLOR_BGR2YUV) # BGR 转 YUV 图像
# 调用matplotlib显示处理结果
titles = ['BGR', 'RGB', 'GRAY', 'HSV', 'YCrCb', 'HLS', 'XYZ', 'LAB', 'YUV']
images = [imgBGR, imgRGB, imgGRAY, imgHSV, imgYCrCb,
imgHLS, imgXYZ, imgLAB, imgYUV]
plt.figure(figsize=(10, 8))
for i in range(9):
plt.subplot(3, 3, i + 1), plt.imshow(images[i], 'gray')
plt.title(titles[i])
plt.xticks([]), plt.yticks([])
plt.tight_layout()
plt.show()
h,s,v = cv2.split(imgHSV)
# Show the channels
plt.figure(figsize=[20,5])
plt.subplot(141);plt.imshow(h,cmap='gray');plt.title("H Channel");
plt.subplot(142);plt.imshow(s,cmap='gray');plt.title("S Channel");
plt.subplot(143);plt.imshow(v,cmap='gray');plt.title("V Channel");
plt.subplot(144);plt.imshow(imgRGB);plt.title("Original");
h_new = h+10
img_merged = cv2.merge((h_new,s,v))
img_rgb = cv2.cvtColor(img_merged, cv2.COLOR_HSV2RGB)
# Show the channels
plt.figure(figsize=[20,5])
plt.subplot(141);plt.imshow(h,cmap='gray');plt.title("H Channel");
plt.subplot(142);plt.imshow(s,cmap='gray');plt.title("S Channel");
plt.subplot(143);plt.imshow(v,cmap='gray');plt.title("V Channel");
plt.subplot(144);plt.imshow(img_rgb);plt.title("Modified");
函数 cv2.imwrite() 用于将图像保存到指定的文件。其语法为:retval = imwrite(filename, img[, params])
参数说明:
filename
:要保存的文件的路径和名称,包括文件扩展名
img
:要保存的 OpenCV 图像,nparray 多维数组
params
:不同编码格式的参数,可选项。
retval
:返回值,保存成功返回 True,否则返回 False。
注意:
# cv2读取的原始BGR图,保存后发现是RGB图,再次读取还是BGR图
imgBGR = cv2.imread("../images/imgLena.tif", flags=1) # 读取为BGR彩色图像
cv2.imwrite('../images/SaveBGR.png', imgBGR)
SaveBGR=cv2.imread('SaveBGR.png',1)
plt.imshow(SaveBGR)
# 转换为RGB格式的图像,发现保存后图片是BRG格式,但是cv2默认方式打开,打印后是RGB格式
imgRGB = cv2.cvtColor(imgBGR, cv2.COLOR_BGR2RGB) # BGR 转换为 RGB
cv2.imwrite('../iamges/SaveRGB.png', imgRGB)
SaveRGB=cv2.imread('SaveRGB.png',1)
plt.imshow(SaveRGB)
可见:图像的原始格式和cv2默认读取的格式是倒序的,和cv2保存和格式也是倒序的。只不过一般我们读取的图片是RGB格式,所以cv2默认读取是BGR格式。如果图片本身就是BGR格式,默认读取就是正常的RGB格式。
saveFile = "../images/测试图.jpg" # 带有中文的保存文件路径
# cv2.imwrite(saveFile, img3) # imwrite 不支持中文路径和文件名,读取失败,但不会报错!
img_write = cv2.imencode(".jpg", imgBGR)[1].tofile(saveFile)
retval = numpy.hstack((img1, img2, …)) # 水平拼接
retval = numpy.vstack((img1, img2, …)) # 垂直拼接
无拷贝
相当于引用,浅拷贝
只是对原变量内存地址的拷贝,深拷贝
是对原变量(ndarray数组)的所有数据的拷贝。在本章中,我们将介绍如何执行图像转换,包括:
import cv2
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
from IPython.display import Image
%matplotlib inline
像素
是构成数字图像的基本单位,像素处理是图像处理的基本操作。对像素的访问、修改,可以使用Numpy 方法直接访问数组元素
。
# Read image as gray scale.
cb_img = cv2.imread("checkerboard_18x18.png",0)
# Set color map to gray scale for proper rendering.
plt.imshow(cb_img, cmap='gray')
print(cb_img)
[[ 0 0 0 0 0 0 255 255 255 255 255 255 0 0 0 0 0 0]
[ 0 0 0 0 0 0 255 255 255 255 255 255 0 0 0 0 0 0]
[ 0 0 0 0 0 0 255 255 255 255 255 255 0 0 0 0 0 0]
[ 0 0 0 0 0 0 255 255 255 255 255 255 0 0 0 0 0 0]
[ 0 0 0 0 0 0 255 255 255 255 255 255 0 0 0 0 0 0]
[ 0 0 0 0 0 0 255 255 255 255 255 255 0 0 0 0 0 0]
[255 255 255 255 255 255 0 0 0 0 0 0 255 255 255 255 255 255]
[255 255 255 255 255 255 0 0 0 0 0 0 255 255 255 255 255 255]
[255 255 255 255 255 255 0 0 0 0 0 0 255 255 255 255 255 255]
[255 255 255 255 255 255 0 0 0 0 0 0 255 255 255 255 255 255]
[255 255 255 255 255 255 0 0 0 0 0 0 255 255 255 255 255 255]
[255 255 255 255 255 255 0 0 0 0 0 0 255 255 255 255 255 255]
[ 0 0 0 0 0 0 255 255 255 255 255 255 0 0 0 0 0 0]
[ 0 0 0 0 0 0 255 255 255 255 255 255 0 0 0 0 0 0]
[ 0 0 0 0 0 0 255 255 255 255 255 255 0 0 0 0 0 0]
[ 0 0 0 0 0 0 255 255 255 255 255 255 0 0 0 0 0 0]
[ 0 0 0 0 0 0 255 255 255 255 255 255 0 0 0 0 0 0]
[ 0 0 0 0 0 0 255 255 255 255 255 255 0 0 0 0 0 0]]
# 打印第一个黑色box的第一个像素
print(cb_img[0,0])
# 打印第一个黑色box右边的第一个白色像素
print(cb_img[0,6])
0
255
cb_img_copy = cb_img.copy()
cb_img_copy[2,2] = 200
cb_img_copy[2,3] = 200
cb_img_copy[3,2] = 200
cb_img_copy[3,3] = 200
# Same as above
# cb_img_copy[2:3,2:3] = 200
plt.imshow(cb_img_copy, cmap='gray')
print(cb_img_copy)
[[ 0 0 0 0 0 0 255 255 255 255 255 255 0 0 0 0 0 0]
[ 0 0 0 0 0 0 255 255 255 255 255 255 0 0 0 0 0 0]
[ 0 0 200 200 0 0 255 255 255 255 255 255 0 0 0 0 0 0]
[ 0 0 200 200 0 0 255 255 255 255 255 255 0 0 0 0 0 0]
[ 0 0 0 0 0 0 255 255 255 255 255 255 0 0 0 0 0 0]
[ 0 0 0 0 0 0 255 255 255 255 255 255 0 0 0 0 0 0]
[255 255 255 255 255 255 0 0 0 0 0 0 255 255 255 255 255 255]
[255 255 255 255 255 255 0 0 0 0 0 0 255 255 255 255 255 255]
[255 255 255 255 255 255 0 0 0 0 0 0 255 255 255 255 255 255]
[255 255 255 255 255 255 0 0 0 0 0 0 255 255 255 255 255 255]
[255 255 255 255 255 255 0 0 0 0 0 0 255 255 255 255 255 255]
[255 255 255 255 255 255 0 0 0 0 0 0 255 255 255 255 255 255]
[ 0 0 0 0 0 0 255 255 255 255 255 255 0 0 0 0 0 0]
[ 0 0 0 0 0 0 255 255 255 255 255 255 0 0 0 0 0 0]
[ 0 0 0 0 0 0 255 255 255 255 255 255 0 0 0 0 0 0]
[ 0 0 0 0 0 0 255 255 255 255 255 255 0 0 0 0 0 0]
[ 0 0 0 0 0 0 255 255 255 255 255 255 0 0 0 0 0 0]
[ 0 0 0 0 0 0 255 255 255 255 255 255 0 0 0 0 0 0]]
img_NZ_bgr = cv2.imread("New_Zealand_Boat.jpg",cv2.IMREAD_COLOR)
img_NZ_rgb = img_NZ_bgr[:,:,::-1] # 通道维度进行反转,也就是BGR→RGB
plt.figure(figsize=[20,5])
plt.subplot(141);plt.imshow(img_NZ_rgb);plt.title("RGB");
plt.subplot(142);plt.imshow(img_NZ_bgr);plt.title("BGR");
Numpy 多维数组的切片是原始数组的
浅拷贝
,切片修改后原始数组也会改变。推荐采用.copy()
进行深拷贝
,得到原始图像的副本。
# 裁剪中心200×300大小的图像
cropped_region = img_NZ_rgb[200:400, 300:600].copy()
plt.imshow(cropped_region)
resize函数可以调整图像的大小,其语法为:dst = resize( src, dsize[, dst[, fx[, fy[, interpolation]]]] )
参数说明:
scr/ dst
:输入输出图像,二者类型相同dsize
: 输出图像的大小,二元元组 (width, height)fx, fy
:x 轴、y 轴上的缩放比例,可选项(dsize = None时)interpolation
:插值方法,整型,可选项
fx, fy
指定缩放比例# 使用`fx, fy`指定缩放比例
resized_cropped_region_2x = cv2.resize(cropped_region,None,fx=2, fy=2)
# 使用dsize指定输出图像大小
resized_cropped_region = cv2.resize(cropped_region, dsize=(100,200), interpolation=cv2.INTER_AREA)
# 保持高宽比的同时调整大小
desired_width = 100
aspect_ratio = desired_width / cropped_region.shape[1]
desired_height = int(cropped_region.shape[0] * aspect_ratio)
dim = (desired_width, desired_height)
# Resize image
keep_aspect_ratio_region = cv2.resize(cropped_region, dsize=dim, interpolation=cv2.INTER_AREA)
plt.figure(figsize=[20,5])
plt.subplot(141);plt.imshow(resized_cropped_region_2x);plt.title("resized_cropped_region_2x");
plt.subplot(142);plt.imshow(resized_cropped_region);plt.title("(resized_cropped_region");
plt.subplot(143);plt.imshow(keep_aspect_ratio_region);plt.title("aspect ratio");
resized_cropped_region_2x = resized_cropped_region_2x[:,:,::-1]
cv2.imwrite("resized_cropped_region_2x.png", resized_cropped_region_2x)
# 显示resize图片真实大小
Image(filename='resized_cropped_region_2x.png')
函数cv2.flip可以进行图像的翻转,包括水平翻转(沿x轴)、垂直翻转(沿y轴)和水平垂直翻转(两个轴同时翻转),函数语法为:dst=flip(src, flipCode[, src])
scr/ dst
:输入输出图像flipCode
:指定如何翻转的标志;0表示围绕x轴翻转,正值(例如,1)表示围绕y轴翻转。负值(例如-1)表示围绕两个轴翻转。img = cv2.imread("../images/Fractal03.png") # 读取彩色图像(BGR)
imgFlip1 = cv2.flip(img, 0) # 垂直翻转
imgFlip2 = cv2.flip(img, 1) # 水平翻转
imgFlip3 = cv2.flip(img, -1) # 水平和垂直翻转
plt.figure(figsize=(9, 6))
plt.subplot(221), plt.axis('off'), plt.title("Original")
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) # 原始图像
plt.subplot(222), plt.axis('off'), plt.title("Flipped Horizontally")
plt.imshow(cv2.cvtColor(imgFlip2, cv2.COLOR_BGR2RGB)) # 水平翻转
plt.subplot(223), plt.axis('off'), plt.title("Flipped Vertically")
plt.imshow(cv2.cvtColor(imgFlip1, cv2.COLOR_BGR2RGB)) # 垂直翻转
plt.subplot(224), plt.axis('off'), plt.title("Flipped Horizontally & Vertically")
plt.imshow(cv2.cvtColor(imgFlip3, cv2.COLOR_BGR2RGB)) # 水平垂直翻转
plt.show()
在本章中,我们将介绍如何使用OpenCV注释图像。我们将学习如何对图像执行以下注释:
当您想在演示文稿中注释结果或演示应用程序时,这些工具非常有用。注释在开发和调试期间也很有用。
# Import libraries
import cv2
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
%matplotlib inline
import matplotlib
matplotlib.rcParams['figure.figsize'] = (9.0, 9.0)
from IPython.display import Image
# 第三章的火箭发射场图不知道为啥算违规,所以这里拿第四章的图来用
image = cv2.imread("Apollo_11_Launch.jpg", cv2.IMREAD_COLOR)
# 显示图片
plt.imshow(image[:,:,::-1])
使用cv2.line函数可以在图片上画条线,其语法为:img = cv2.line(img, pt1, pt2, color[, thickness[, lineType[, shift]]])
。
参数说明:
imageLine = image.copy()
# 线段从(200,100)到(400,100),颜色为黄色(OpenCV使用BGR格式)
cv2.line(imageLine, (200, 50), (400, 50), (255, 255, 0), thickness=2, lineType=cv2.LINE_AA);
plt.imshow(imageLine[:,:,::-1])
使用 cv2.circle函数可以在图片上画个圆,其语法为:img = cv2.circle(img, center, radius, color[, thickness[, lineType[, shift]]])
。其中 center, radius, color分别表示圆的圆心、半径和颜色。
imageCircle = image.copy()
cv2.circle(imageCircle, (290,485), 30, (0, 0, 255), thickness=2, lineType=cv2.LINE_AA);
plt.imshow(imageCircle[:,:,::-1])
使用cv2.rectangle函数可以在图像上绘制矩形,函数语法为:img = cv2.rectangle(img, pt1, pt2, color[, thickness[, lineType[, shift]]])
。其中,pt1,pt2
为矩形顶点,通常使用左上角和右下角顶点。
imageRectangle = image.copy()
cv2.rectangle(imageRectangle, (350, 310), (550,430), (255, 0, 255), thickness=2, lineType=cv2.LINE_8);
plt.imshow(imageRectangle[:,:,::-1])
使用cv2.putText函数可以在图像上添加文本注释(不支持中文字符),其语法为img = cv2.putText(img, text, org, fontFace, fontScale, color[, thickness[, lineType[, bottomLeftOrigin]]])
。参数说明如下:
imageText = image.copy()
text = "Apollo 11 Saturn V Launch, July 16, 1969"
fontFace = cv2.FONT_HERSHEY_PLAIN # 字体类型
# 2.3是字体比例因子,(0,255,0)表示绿色,线条厚度为2
cv2.putText(imageText,text,(150,580),fontFace, 2.3,(0,255,255),2,cv2.LINE_AA);
# Display the image
plt.imshow(imageText[:,:,::-1])
在图像中添加中文字符,可以使用 python+opencv+PIL 实现,或使用 python+opencv+freetype 实现。
imgBGR = cv2.imread("../images/imgLena.tif") # 读取彩色图像(BGR)
from PIL import Image, ImageDraw, ImageFont
if (isinstance(imgBGR, np.ndarray)): # 判断是否 OpenCV 图片类型
imgPIL = Image.fromarray(cv2.cvtColor(imgBGR, cv2.COLOR_BGR2RGB))
text = "OpenCV2021, 中文字体"
pos = (50, 20) # (left, top),字符串左上角坐标
color = (255, 255, 255) # 字体颜色
textSize = 40
drawPIL = ImageDraw.Draw(imgPIL)
fontText = ImageFont.truetype("font/simsun.ttc", textSize, encoding="utf-8")
drawPIL.text(pos, text, color, font=fontText)
imgPutText = cv2.cvtColor(np.asarray(imgPIL), cv2.COLOR_RGB2BGR)
cv2.imshow("imgPutText", imgPutText) # 显示叠加图像 imgAdd
key = cv2.waitKey(0) # 等待按键命令
图像处理技术利用数学运算获得不同的结果。通常,我们使用一些基本操作可以得到图像的简单增强。在本章中,我们将介绍:
# Import libraries
import cv2
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
%matplotlib inline
from IPython.display import Image
下面用opencv读取一张新西兰海岸照
img_bgr = cv2.imread("New_Zealand_Coast.jpg",cv2.IMREAD_COLOR)
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
# Display 18x18 pixel image.
Image(filename='New_Zealand_Coast.jpg')
函数 cv2.add()用于图像的加法运算,其语法为dst=cv2.add(src1, src2 [, dst[, mask[, dtype]])
scr1, scr2
:进行加法运算的图像,或一张图像与一个 numpy array 标量mask
:掩模图像,8位灰度格式;掩模图像数值为 0 的像素,输出图像对应像素的各通道值也为 0(被mask位置像素输出为0)。可选项,默认值为 None。dtype
:图像数组的深度,即每个像素值的位数,可选项需要注意的是,OpenCV 加法和 numpy 加法之间有区别:cv2.add() 是饱和运算(相加后如大于 255 则结果为 255),而 Numpy 加法是模运算。
本节讨论图像加法的简单操作——图像与标量相加,这会导致图像亮度的增加或减少,因为我们最终会对每个像素值增加或减少相同的值。(亮度会全局地增加/减少)
matrix = np.ones(img_rgb.shape, dtype = "uint8") * 50
img_rgb_brighter = cv2.add(img_rgb, matrix)
img_rgb_darker = cv2.subtract(img_rgb, matrix)
# Show the images
plt.figure(figsize=[18,5])
plt.subplot(131); plt.imshow(img_rgb_darker); plt.title("Darker");
plt.subplot(132); plt.imshow(img_rgb); plt.title("Original");
plt.subplot(133); plt.imshow(img_rgb_brighter);plt.title("Brighter");
另外,图像也可以与常数相加。下面进行常数相加和标量相加的对比:
Value =70 # 常数
# Scalar = np.array([[50., 100., 150.]]) # 标量
Scalar = np.ones((1, 3), dtype="float") * Value # 标量
imgAddV = cv2.add(img_bgr , Value) # OpenCV 加法: 图像 + 常数
imgAddS = cv2.add(img_bgr , Scalar) # OpenCV 加法: 图像 + 标量
print("Shape of scalar", Scalar)
for i in range(1, 6):
x, y = i*10, i*10
print("(x,y)={},{}, img_bgr:{}, imgAddV:{}, imgAddS:{}"
.format(x,y,img_bgr [x,y],imgAddV[x,y],imgAddS[x,y]))
# 打印图像中的6个点,可以看到相加后像素值的变化
Shape of scalar [[70. 70. 70.]]
(x,y)=10,10, img_bgr:[184 179 170], imgAddV:[254 179 170], imgAddS:[254 249 240]
(x,y)=20,20, img_bgr:[185 179 172], imgAddV:[255 179 172], imgAddS:[255 249 242]
(x,y)=30,30, img_bgr:[189 182 173], imgAddV:[255 182 173], imgAddS:[255 252 243]
(x,y)=40,40, img_bgr:[187 181 174], imgAddV:[255 181 174], imgAddS:[255 251 244]
(x,y)=50,50, img_bgr:[193 188 179], imgAddV:[255 188 179], imgAddS:[255 255 249]
plt.figure(figsize=[18,5])
plt.subplot(131), plt.title("1. img1"), plt.axis('off')
plt.imshow(cv2.cvtColor(img_bgr , cv2.COLOR_BGR2RGB)) # 显示 img_bgr(RGB)
plt.subplot(132), plt.title("2. img + constant"), plt.axis('off')
plt.imshow(cv2.cvtColor(imgAddV, cv2.COLOR_BGR2RGB)) # 显示 imgAddV(RGB)
plt.subplot(133), plt.title("3. img + scalar"), plt.axis('off')
plt.imshow(cv2.cvtColor(imgAddS, cv2.COLOR_BGR2RGB)) # 显示 imgAddS(RGB)
plt.show()
img1 = cv2.imread("../images/imgB1.jpg") # 读取彩色图像(BGR)
img2 = cv2.imread("../images/imgB3.jpg") # 读取彩色图像(BGR)
imgAddCV = cv2.add(img1, img2) # OpenCV 加法: 饱和运算
imgAddNP = img1 + img2 # # Numpy 加法: 模运算
plt.subplot(221), plt.title("1. img1"), plt.axis('off')
plt.imshow(cv2.cvtColor(img1, cv2.COLOR_BGR2RGB)) # 显示 img1(RGB)
plt.subplot(222), plt.title("2. img2"), plt.axis('off')
plt.imshow(cv2.cvtColor(img2, cv2.COLOR_BGR2RGB)) # 显示 img2(RGB)
plt.subplot(223), plt.title("3. cv2.add(img1, img2)"), plt.axis('off')
plt.imshow(cv2.cvtColor(imgAddCV, cv2.COLOR_BGR2RGB)) # 显示 imgAddCV(RGB)
plt.subplot(224), plt.title("4. img1 + img2"), plt.axis('off')
plt.imshow(cv2.cvtColor(imgAddNP, cv2.COLOR_BGR2RGB)) # 显示 imgAddNP(RGB)
plt.show()
cv2.add()
饱和加法的结果,图 4 是 numpy
取模加法的结果。 函数 cv2.addWeight() 用于图像的加权加法运算,可以实现图像的叠加和混合。其语法为:dst=cv2.addWeighted(src1, alpha, src2, beta, gamma[, dst[, dtype]])
。简单理解就是: d s t = s r c 1 ∗ a l p h a + s r c 2 ∗ b e t a + g a m m a dst = src1 * alpha + src2 * beta + gamma dst=src1∗alpha+src2∗beta+gamma
alpha/beta
:第一、二张图像 的权重,通常取为 0~1 之间的浮点数gamma
: 灰度系数,图像校正的偏移量,用于调节亮度dtype
:输出图像的深度,即每个像素值的位数,可选项,default=src1.depth() img1 = cv2.imread("../images/imgGaia.tif") # 读取图像 imgGaia
img2 = cv2.imread("../images/imgLena.tif") # 读取图像 imgLena
imgAddW1 = cv2.addWeighted(img1, 0.2, img2, 0.8, 0) # 加权相加, a=0.2, b=0.8
imgAddW2 = cv2.addWeighted(img1, 0.5, img2, 0.5, 0) # 加权相加, a=0.5, b=0.5
imgAddW3 = cv2.addWeighted(img1, 0.8, img2, 0.2, 0) # 加权相加, a=0.8, b=0.2
plt.subplot(131), plt.title("1. a=0.2, b=0.8"), plt.axis('off')
plt.imshow(cv2.cvtColor(imgAddW1, cv2.COLOR_BGR2RGB)) # 显示 img1(RGB)
plt.subplot(132), plt.title("2. a=0.5, b=0.5"), plt.axis('off')
plt.imshow(cv2.cvtColor(imgAddW2, cv2.COLOR_BGR2RGB)) # 显示 imgAddV(RGB)
plt.subplot(133), plt.title("3. a=0.8, b=0.2"), plt.axis('off')
plt.imshow(cv2.cvtColor(imgAddW3, cv2.COLOR_BGR2RGB)) # 显示 imgAddS(RGB)
plt.show()
不同尺寸图像的相加,可先将二者调整到同一尺寸,方法见本章4.6节。
就像加法会导致亮度变化一样,乘法也可以用来提高图像的对比度。
对比度是图像像素值的差异。将像素值与常数相乘可以使差值变大或变小(如果乘法因子小于1)。
matrix1 = np.ones(img_rgb.shape) * 0.5
matrix2 = np.ones(img_rgb.shape) * 1.5
img_rgb_darker = np.uint8(cv2.multiply(np.float64(img_rgb), matrix1))
img_rgb_brighter = np.uint8(cv2.multiply(np.float64(img_rgb), matrix2))
# Show the images
plt.figure(figsize=[18,5])
plt.subplot(131); plt.imshow(img_rgb_darker); plt.title("Lower Contrast");
plt.subplot(132); plt.imshow(img_rgb); plt.title("Original");
plt.subplot(133); plt.imshow(img_rgb_brighter);plt.title("Higher Contrast");
右边图会发现图像的某些区域看到奇怪的颜色,这是因为相乘后某些像素值>255,已经溢出,这该如何处理呢?
img_rgb_higher = np.uint8(np.clip(cv2.multiply(np.float64(img_rgb), matrix2),0,255))
# Show the images
plt.figure(figsize=[18,5])
plt.subplot(131); plt.imshow(img_rgb_lower); plt.title("Lower Contrast");
plt.subplot(132); plt.imshow(img_rgb); plt.title("Original");
plt.subplot(133); plt.imshow(img_rgb_higher);plt.title("Higher Contrast");
阈值
就是临界值。根据阈值可将图像划分为多个区域,或提取图像中的目标物体,是最基本的阈值处理方法。图像阈值处理简单、直观,计算速度快,是图像处理的基础操作,在图像分割中处于核心地位。
例如,图像由暗色背景上的亮目标组成,目标像素和背景像素的灰度值组合构成两种主要模式,可以通过设定适当的阈值 T,将图像的像素划分为两类:灰度值大于 T 的像素集是目标,小于 T 的像素集是背景。
当 T 是应用于整幅图像的常数,称为全局阈值处理
;当 T 对于整幅图像发生变化时,称为可变阈值处理
。有时,对应于图像中任一点的 T 值取决于该点的邻域的限制,称为局部阈值处理
。
OpenCV 提供了函数 cv.threshold 可以对图像进行阈值处理。此函数可以将灰度图像转换为二值图像(将灰度大于阈值的像素点置为255,小于阈值的像素点置为0,得到二值图像,可以突出图像轮廓,把目标从背景中分割出来)。或用于消除噪声,即过滤出值太小或太大的像素。
二值图像在图像处理中有很多用例,最常见的用例之一是创建掩码。图像蒙版允许我们对图像的特定部分进行处理,使其他部分保持完整。
cv2.threshold函数语法为:retval, dst = cv2.threshold( src, thresh, maxval, type[, dst] )
参数说明:
dst
:阈值变化后的图像,与src具有相同大小和类型以及通道数的输出数组。src
:输入数组(多通道,8位或32位浮点,文档确实这么写的)。thresh
:阈值。retval
:阈值,浮点型maxval
:用于THRESH_BINARY
和THRESH_MINARY_INV
阈值类型的最大值,一般取 255。type
:阈值类型(请参见阈值类型)。
cv2.THRESH_BINARY
或 cv2.THRESH_BINARY_INV
时输出为二值图像,其它变换类型时进行阈值处理但并不是二值处理。特殊值THRESH_OTSU或THRESH_TRIANGLE可以与上述值之一组合。在这些情况下,函数使用Otsu或Triangle算法确定最佳阈值,并使用它代替指定的阈值。Otsu和Triangle方法仅用于8位单通道图像。
当图像中存在高斯噪声时,通常难以通过全局阈值将图像的边界完全分开。如果图像的边界是在局部对比下出现的,不同位置的阈值也不同,使用全局阈值的效果将会很差。如果图像的直方图存在明显边界,容易找到图像的分割阈值;但如果图像直方图分界不明显,则很难找到合适的阈值,甚至可能无法找到固定的阈值有效地分割图像。
img_read = cv2.imread("building-windows.jpg", 0) # 灰度图
retval, img_thresh = cv2.threshold(img_read, 100, 255, cv2.THRESH_BINARY)
# Show the images
plt.figure(figsize=[18,5])
plt.subplot(121); plt.imshow(img_read, cmap="gray"); plt.title("Original");
plt.subplot(122); plt.imshow(img_thresh, cmap="gray"); plt.title("Thresholded");
print(retval,img_thresh.shape)
(572, 800) 100.0 (572, 800)
当图像中的目标和背景的灰度分布较为明显时,可以对整个图像使用固定阈值进行全局阈值处理。 为了获得适当的全局阈值,可以基于灰度直方图进行迭代计算(详见【youcans 的 OpenCV 例程200篇】159. 图像分割之全局阈值处理),另一种改进算法是OTSU 方法(又称大津算法)。使用最大化类间方差(intra-class variance)作为评价准则,基于对图像直方图的计算,可以给出类间最优分离的最优阈值。
任取一个灰度值 T,可以将图像分割为两个集合 F 和 B,集合 F、B 的像素数的占比分别为 pF、pB,集合 F、B 的灰度值均值分别为 mF、mB,图像灰度值为 m,定义类间方差为:
I C V = p F ∗ ( m F − m ) 2 + p B ∗ ( m B − m ) 2 ICV = p_F * (m_F - m)^2 + p_B * (m_B - m)^2 ICV=pF∗(mF−m)2+pB∗(mB−m)2
使类间方差 ICV 最大化的灰度值 T 就是最优阈值。因此,只要遍历所有的灰度值,就可以得到使 ICV 最大的最优阈值 T。
OpenCV 提供了函数 cv.threshold 可以对图像进行阈值处理,将参数 type 设为 cv.THRESH_OTSU
,就可以使用使用 OTSU 算法进行最优阈值分割。
img = cv2.imread("../images/Fig1039a.tif", flags=0)
deltaT = 1 # 预定义值
histCV = cv2.calcHist([img], [0], None, [256], [0, 256]) # 灰度直方图
grayScale = range(256) # 灰度级 [0,255]
totalPixels = img.shape[0] * img.shape[1] # 像素总数
totalGray = np.dot(histCV[:,0], grayScale) # 内积, 总和灰度值
T = round(totalGray/totalPixels) # 平均灰度
while True:
numC1, sumC1 = 0, 0
for i in range(T): # 计算 C1: (0,T) 平均灰度
numC1 += histCV[i,0] # C1 像素数量
sumC1 += histCV[i,0] * i # C1 灰度值总和
numC2, sumC2 = (totalPixels-numC1), (totalGray-sumC1) # C2 像素数量, 灰度值总和
T1 = round(sumC1/numC1) # C1 平均灰度
T2 = round(sumC2/numC2) # C2 平均灰度
Tnew = round((T1+T2)/2) # 计算新的阈值
print("T={}, m1={}, m2={}, Tnew={}".format(T, T1, T2, Tnew))
if abs(T-Tnew) < deltaT: # 等价于 T==Tnew
break
else:
T = Tnew
# 阈值处理
ret1, imgBin = cv2.threshold(img, T, 255, cv2.THRESH_BINARY) # 阈值分割, thresh=T
ret2, imgOtsu = cv2.threshold(img, T, 255, cv2.THRESH_OTSU) # 阈值分割, thresh=T
print(ret1, ret2)
plt.figure(figsize=(7,7))
plt.subplot(221), plt.axis('off'), plt.title("Origin"), plt.imshow(img, 'gray')
plt.subplot(222, yticks=[]), plt.title("Gray Hist") # 直方图
histNP, bins = np.histogram(img.flatten(), bins=255, range=[0, 255], density=True)
plt.bar(bins[:-1], histNP[:])
plt.subplot(223), plt.title("global binary(T={})".format(T)), plt.axis('off')
plt.imshow(imgBin, 'gray')
plt.subplot(224), plt.title("OTSU binary(T={})".format(round(ret2))), plt.axis('off')
plt.imshow(imgOtsu, 'gray')
plt.tight_layout()
plt.show()
全局阈值处理还有一些其它改进方法,比如处理前先对图像进行平滑、基于边缘信息改进全局阈值处理等等。
噪声和非均匀光照等因素对阈值处理的影响很大,例如光照复杂时 Otsu
算法等全局阈值分割方法的效果往往不太理想,需要使用可变阈值处理。
可变阈值是指对于图像中的每个像素点或像素块有不同的阈值,如果该像素点大于其对应的阈值则认为是前景。可变阈值处理的基本方法,是对图像中的每个点,根据其邻域的性质计算阈值。标准差和均值是对比度和平均灰度的描述,在局部阈值处理中非常有效。
局部阈值分割可以根据图像的局部特征进行处理,与图像像素位置、灰度值及邻域特征值有关。
cv.adaptiveThreshold为自适应阈值的二值化处理函数,可以通过比较像素点与周围像素点的关系动态调整阈值。函数语法:dst = cv.adaptiveThreshold( src, maxValue, adaptiveMethod, thresholdType, blockSize, C[, dst] )
。可以根据参数 adaptiveMethod确定自适应阈值的计算方法。
参数说明:
dst
:与src大小和类型相同的目标图像。src
:8位单通道图像。maxValue
:为满足条件的像素指定的非零值,详见阈值类型说明。adaptiveMethod
:要使用的自适应阈值算法
thresholdType
:阈值类型,必须是THRESH_BINARY或THRESH_MINARY_INV。
cv2.THRESH_BINARY
:大于阈值时置 maxValue,否则置 0cv2.THRESH_BINARY_INV
:大于阈值时置 0,否则置 maxValueblockSize
:用于计算像素阈值的像素邻域的尺寸,可以取3、5、7,依此类推。C
: 偏移量,从平均值或加权平均值中减去该常数。 假设您想构建一个可以读取(解码)乐谱的应用程序。这类似于文本文档的光学字符识别(OCR,其目标是识别文本字符)。
在这两种应用程序中,处理管道的第一步是隔离文档图像中的重要信息(将其与背景分离)。这项任务可以通过阈值技术来完成。让我们看一个例子:
# 正常读取图像
img_read = cv2.imread("Piano_Sheet_Music.png", 0)
# 全局阈值1
retval, img_thresh_gbl_1 = cv2.threshold(img_read,50, 255, cv2.THRESH_BINARY)
# 全局阈值2
retval, img_thresh_gbl_2 = cv2.threshold(img_read,130, 255, cv2.THRESH_BINARY)
# 自适应阈值
img_thresh_adp = cv2.adaptiveThreshold(img_read, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 11, 7)
# Show the images
plt.figure(figsize=[18,15])
plt.subplot(221); plt.imshow(img_read, cmap="gray"); plt.title("Original");
plt.subplot(222); plt.imshow(img_thresh_gbl_1,cmap="gray"); plt.title("Thresholded (global: 50)");
plt.subplot(223); plt.imshow(img_thresh_gbl_2,cmap="gray"); plt.title("Thresholded (global: 130)");
plt.subplot(224); plt.imshow(img_thresh_adp, cmap="gray"); plt.title("Thresholded (adaptive)");
位运算包括:cv2.bitwise_and()
. 、cv2.bitwise_or()
、 cv2.bitwise_xor()
和 cv2.bitwise_not()
。比如and位运算语法为:dst = cv2.bitwise_and( src1, src2[, dst[, mask]] )
正常读取一张矩形图和一张圆形图:
img_rec = cv2.imread("rectangle.jpg", 0)
img_cir = cv2.imread("circle.jpg", 0)
plt.figure(figsize=[20,5])
plt.subplot(121);plt.imshow(img_rec,cmap='gray')
plt.subplot(122);plt.imshow(img_cir,cmap='gray')
print(img_rec.shape,img_cir.shape)
(200, 499) (200, 499)
下面依次进行and、or、xor操作
img_and = cv2.bitwise_and(img_rec, img_cir, mask = None)
img_or= cv2.bitwise_or(img_rec, img_cir, mask = None)
img_xor= cv2.bitwise_xor(img_rec, img_cir, mask = None)
img_not=cv2.bitwise_not(img_rec, img_cir, mask = None)
#plt.imshow(result,cmap='gray')
plt.figure(figsize=[10,5])
plt.subplot(221); plt.imshow(img_and,cmap="gray"); plt.title("AND");
plt.subplot(222); plt.imshow(img_or,cmap="gray"); plt.title("OR");
plt.subplot(223); plt.imshow(img_xor,cmap="gray"); plt.title("XOR");
plt.subplot(224); plt.imshow(img_not,cmap="gray"); plt.title("NOT");
两张图像直接进行加法运算后图像的颜色会改变,通过加权加法实现图像混合后图像的透明度会改变,都不能实现图像的叠加。
实现图像的叠加,需要综合运用图像阈值处理、图像掩模、位操作和图像加法的操作。下面展示如何用背景图像填充可口可乐Logo的白色字母。
Image(filename='Logo_Manipulation.png')
1.读取Logo图片
img_bgr = cv2.imread("coca-cola-logo.png")
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
logo_w,logo_h = img_rgb.shape[0],img_rgb.shape[1]
print(img_rgb.shape)
(700, 700, 3)
img_background_bgr = cv2.imread("checkerboard_color.png")
img_background_rgb = cv2.cvtColor(img_background_bgr, cv2.COLOR_BGR2RGB)
# 调整图片宽度,并保持高宽比不变
aspect_ratio = logo_w / img_background_rgb.shape[1]
dim = (logo_w, int(img_background_rgb.shape[0] * aspect_ratio))
# 背景图resize到和Logo一样大小
img_background_rgb = cv2.resize(img_background_rgb, dim, interpolation=cv2.INTER_AREA)
print(img_background_rgb.shape)
(700, 700, 3)
img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2GRAY) # RGB转灰度图
# 使用全局阈值创建Logo二值mask
retval, img_mask = cv2.threshold(img_gray,127,255,cv2.THRESH_BINARY)
print(img_mask.shape)
img_mask_inv = cv2.bitwise_not(img_mask)
plt.figure(figsize=[8,8])
plt.subplot(221); plt.imshow(img_rgb); plt.title("RGB");
plt.subplot(222); plt.imshow(img_background_rgb); plt.title("Background");
plt.subplot(223); plt.imshow(img_mask,cmap="gray"); plt.title("Mask");
plt.subplot(224); plt.imshow(img_mask_inv,cmap="gray"); plt.title("Mask_inv");
# 在logo字母上加上彩色背景
img_background = cv2.bitwise_and(img_background_rgb, img_background_rgb, mask=img_mask)
plt.imshow(img_background)
# Isolate foreground (red from original image) using the inverse mask
img_foreground = cv2.bitwise_and(img_rgb, img_rgb, mask=img_mask_inv)
plt.imshow(img_foreground)
result = cv2.add(img_background,img_foreground)
plt.imshow(result)
cv2.imwrite("logo_final.png", result[:,:,::-1])
plt.figure(figsize=[15,5])
plt.subplot(141); plt.imshow(img_background); plt.title("Add_Background");
plt.subplot(142); plt.imshow(img_foreground); plt.title("Foreground");
plt.subplot(143); plt.imshow(result,cmap="gray"); plt.title("Result");
在构建应用程序时,保存工作的演示视频以及许多应用程序本身可能需要保存视频剪辑变得很重要。例如,在一个调查应用程序中,您可能必须在看到异常情况时立即保存视频剪辑。
在本章中,我们将描述如何使用openCV以avi
和mp4
格式保存视频。
cv2.VideoCapture
:
cap(捕获对象) = cv2.VideoCapture(“摄像头ID号”)
。"
摄像头ID号
"就是摄像头的ID号码,默认值为-1,表示随机选取一个摄像头。
如果电脑有多个摄像头,则用数字0表示第一个,用数字1表示第二个,以此类推。所以,如果电脑只有一个摄像头,既可以用0,也可以用-1来作为摄像头的ID号。
cap(捕获对象) = cv2.VideoCapture(“filename”)
ret,frame = cap.read()
:按帧读取视频
# import the library
import cv2
import matplotlib.pyplot as plt
%matplotlib inline
source = './race_car.mp4' # source = 0 for webcam
cap = cv2.VideoCapture(source)
if (cap.isOpened()== False):
print("Error opening video stream or file")
ret, frame = cap.read()
print(frame.shape)
plt.imshow(frame[...,::-1]) # 等价于plt.imshow(frame[:,:,::-1]),显示视频的第一帧
(1080, 1728, 3)
下面的操作可以直接在当前notebook中播放视频:
from IPython.display import HTML
HTML("""
""")
要编写视频,需要使用正确的参数创建视频编写器对象。函数语法为:VideoWriter object= cv.VideoWriter( filename, fourcc, fps, frameSize )
参数说明:
filename
:输出视频文件的名称。fourcc
:用于压缩帧的4字符编解码器代码。例如:
VideoWriter
::fourcc(‘P’,‘I’,‘M’,‘1’)是MPEG-1
编解码器VideoSwriter
::foorcc(‘M’,‘J’,‘P’,‘G’)是jpeg
编解码器。fps
:创建的视频流的帧速。frameSize
:视频帧的大小。# 获取每帧图像的默认分辨率,将分辨率从浮点型转为整型
frame_width = int(cap.get(3))
frame_height = int(cap.get(4))
# 定义编解码器并创建VideoWriter对象。
out_avi = cv2.VideoWriter('race_car_out.avi',cv2.VideoWriter_fourcc('M','J','P','G'), 10, (frame_width,frame_height))
out_mp4 = cv2.VideoWriter('race_car_out.mp4',cv2.VideoWriter_fourcc(*'XVID'), 10, (frame_width,frame_height))
读取赛车视频中帧,并将其写入上一步中创建的两个对象,并在任务完成后释放对象。
# Read until video is completed
while(cap.isOpened()):
# Capture frame-by-frame
ret, frame = cap.read()
if ret == True:
# Write the frame to the output files
out_avi.write(frame)
out_mp4.write(frame)
# Break the loop
else:
break
# 完成所有操作后,释放VideoCapture和VideoWriter对象
# 否则本地打开视频会显示文件正在被使用
cap.release()
out_avi.release()
out_mp4.release()
原视频一共7s,其流帧速应该是ftp=30,创建的两个新的视频是21s(ftp=10)。
第八章是图像对齐,主要是通过在两张图中找到关键点,再进行对齐,效果如下图所示:
代码量比较少,就放出来了。我主要也不是搞这个,就没有仔细研究了。
import cv2
import glob
import matplotlib.pyplot as plt
import math
imagefiles = glob.glob("boat/*")
imagefiles.sort()
images = []
for filename in imagefiles:
img = cv2.imread(filename)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
images.append(img)
num_images = len(images)
# Display Images
plt.figure(figsize=[30,10])
num_cols = 3
num_rows = math.ceil(num_images / num_cols)
for i in range(0, num_images):
plt.subplot(num_rows, num_cols, i+1)
plt.axis('off')
plt.imshow(images[i])
# Stitch Images
stitcher = cv2.Stitcher_create()
status, result = stitcher.stitch(images)
if status == 0:
plt.figure(figsize=[30,10])
plt.imshow(result)
效果图:
感觉就是处理异常曝光的图片,不研究,有兴趣的可以去看源码。
本章将学习:
什么是目标追踪?
目标追踪是跟踪视频序列中的对象。使用视频序列的帧和边界框来初始化跟踪算法,以指示我们感兴趣跟踪的目标的位置。追踪算法为所有后续帧输出一个边界框。
from IPython.display import HTML
HTML("""
""")
# Import modules
import cv2
import sys
import os
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from IPython.display import HTML
import urllib
video_input_file_name = "race_car.mp4"
def drawRectangle(frame, bbox):
p1 = (int(bbox[0]), int(bbox[1]))
p2 = (int(bbox[0] + bbox[2]), int(bbox[1] + bbox[3]))
# 以p1/p2为左上右下角点画矩形,颜色为蓝色。线条宽度为2,类型为1
cv2.rectangle(frame, p1, p2, (255,0,0), 2, 1)
def displayRectangle(frame, bbox):
plt.figure(figsize=(20,10))
frameCopy = frame.copy()
drawRectangle(frameCopy, bbox)
frameCopy = cv2.cvtColor(frameCopy, cv2.COLOR_RGB2BGR)
plt.imshow(frameCopy); plt.axis('off')
def drawText(frame, txt, location, color = (50,170,50)):
# 添加注释,字体类型cv2.FONT_HERSHEY_SIMPLEX,字体比例=1,宽度为3
cv2.putText(frame, txt, location, cv2.FONT_HERSHEY_SIMPLEX, 1, color, 3)
GOTURN
是 Generic Object Tracking Using Regression Networks 的缩写,是一种基于深度学习的跟踪算法。GOTURN
模型在数千个视频序列上进行训练,不需要在运行时执行任何学习。
如上图所示,GOTURN
使用从数千个视频中剪切的一对帧来训练的,即GOTURN
将两个裁剪的帧作为输入,并在第二帧中输出目标边界框。训练时:
GOTURN的网络结构
如上图所示,模型输入是两个帧。第一二行分别是当前帧和前一帧。
下面下载模型:
if not os.path.isfile('goturn.prototxt') or not os.path.isfile('goturn.caffemodel'):
print("Downloading GOTURN model zip file")
urllib.request.urlretrieve('https://www.dropbox.com/sh/77frbrkmf9ojfm6/AACgY7-wSfj-LIyYcOgUSZ0Ua?dl=1', 'GOTURN.zip')
# Uncompress the file
!tar -xvf GOTURN.zip
# Delete the zip file
os.remove('GOTURN.zip')
首先要安装tracker包,版本号要和自己的opencv-python版本一致
pip install opencv-contrib-python==4.5.2.54
# Set up tracker
tracker_types = ['BOOSTING', 'MIL','KCF', 'CSRT', 'TLD', 'MEDIANFLOW', 'GOTURN','MOSSE']
# Change the index to change the tracker type
tracker_type = tracker_types[2]
if tracker_type == 'BOOSTING':
tracker = cv2.legacy_TrackerBoosting.create()
elif tracker_type == 'MIL':
tracker = cv2.TrackerMIL_create()
elif tracker_type == 'KCF':
tracker = cv2.TrackerKCF_create()
elif tracker_type == 'CSRT':
tracker = cv2.legacy_TrackerCSRT.create()
elif tracker_type == 'TLD':
tracker = cv2.legacy_TrackerTLD.create()
elif tracker_type == 'MEDIANFLOW':
tracker = cv2.legacy_TrackerMedianFlow.create()
elif tracker_type == 'GOTURN':
tracker = cv2.TrackerGOTURN_create()
else:
tracker = cv2.legacy_TrackerMOSSE.create()
我还是卡在这一步,安装不了对应版本的tracker包,无法实例化tracker。下面只是给出代码,本人并没有跑通。
读取视频并定义输出视频文件
# 读取视频
video = cv2.VideoCapture(video_input_file_name)
ok, frame = video.read()
# Exit if video not opened
if not video.isOpened():
print("Could not open video")
sys.exit()
# 获取帧的宽高
else :
width = int(video.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(video.get(cv2.CAP_PROP_FRAME_HEIGHT))
video_output_file_name = 'race_car-' + tracker_type + '.mp4' # 设定输出视频文件名
video_out = cv2.VideoWriter(video_output_file_name,cv2.VideoWriter_fourcc(*'avc1'), 10, (width, height))
定义边界框
bbox = (1300, 405, 160, 120)
#bbox = cv2.selectROI(frame, False)
#print(bbox)
displayRectangle(frame,bbox)
初始化边界框
ok = tracker.init(frame, bbox)
while True:
ok, frame = video.read()
if not ok:
break
# Start timer
timer = cv2.getTickCount()
# Update tracker
ok, bbox = tracker.update(frame)
# Calculate Frames per second (FPS)
fps = cv2.getTickFrequency() / (cv2.getTickCount() - timer);
# Draw bounding box
if ok:
drawRectangle(frame, bbox)
else :
drawText(frame, "Tracking failure detected", (80,140), (0, 0, 255))
# Display Info
drawText(frame, tracker_type + " Tracker", (80,60))
drawText(frame, "FPS : " + str(int(fps)), (80,100))
# Write frame to video
video_out.write(frame)
video.release()
video_out.release()
# Tracker: KCF
HTML("""
""")
# Tracker: CSRT
HTML("""
""")
# Tracker: GOTURN
HTML("""
""")