真是惭愧,上一篇笔记还是一年前写的,现在opencv已经出到版本4了,今年专栏文章会尽量保证不断更不停更。文章中理论性的东西会相对较少,更偏向于结合工程中的实际应用来学习,以具体代码来实现相关功能。另外,每篇文章基本上是个人结合官方文档和Google到的相关资料,尽可能规范且易于理解和实现地总结整理得出,展示的所有代码均实际跑过。为了保证兼容性,对于旧的文章也会作相关更新。当然有错误和不准确的地方也请读者在评论区或私信指出,不胜感激。
先简单介绍一下,OpenCV4相对于OpenCV3的一些新特性。
- OpenCV现在基于C++ 11库,需要符合C++ 11 标准的编译器。所需的最低CMake版本已提升至3.5.1。
- 很多来自OpenCV 1.x的C API已被删除。
- 在core模块中的部分功能(如在XML,YAML或JSON中存储和加载结构化数据)已在C++中完全重新实现,并且也删除了C API。
- 添加了新的模块G-API,它可以作为非常有效的基于图形的图像处理 pipeline的引擎。
- dnn模块使用OpenVINO™工具包R4中的Deep Learning Deployment Toolkit进行了更新。请参阅指南如何构建和使用支持DLDT的OpenCV。
- dnn模块现在包括实验性Vulkan backend,并支持ONNX格式的网络。
- Kinect Fusion算法已针对CPU和GPU(OpenCL)进行实现和优化。
- QR码检测器和解码器已添加到objdetect模块中。
- 非常高效且高质量的DIS密集光流算法已从opencv_contrib转移到video模块。
- 更多细节可以在之前的宣布中找到:更多细节可以在之前的宣布中找到:4.0-alpha,4.0-beta,4.0-rc和更新日志
除了以上特性需要注意之外,基本的使用与先前版本是完全兼容的,在一般的图像处理操作上没有影响。以后的笔记都与时俱进,若无特殊说明,均是基于OpenCV4。关于OpenCV4的安装与3一样,参考上一篇笔记。
先简单介绍下位图的概念,因为一些基本的图像操作都是基于位图和像素点的。
位图(Bitmap),又称栅格图(英语:Raster graphics)或点阵图,是使用像素阵列(Pixel-array/Dot-matrix点阵)来表示的图像。
位图的像素都分配有特定的位置和颜色值。每个像素的颜色信息由RGB组合或者灰度值表示。
根据位深度,可将位图分为1、4、8、16、24及32位图像等。每个像素使用的信息位数越多,可用的颜色就越多,颜色表现就越逼真,相应的数据量越大。例如,位深度为 1 的像素位图只有两个可能的值(黑色和白色),所以又称为二值位图。位深度为 8 的图像有 28(即 256)个可能的值。位深度为 8 的灰度模式图像有 256 个可能的灰色值。
RGB图像由三个颜色通道组成。8 位/通道的 RGB 图像中的每个通道有 256 个可能的值,这意味着该图像有 1600 万个以上可能的颜色值。有时将带有 8 位/通道 (bpc) 的 RGB 图像称作 24 位图像(8 位 x 3 通道 = 24 位数据/像素)。通常将使用24位RGB组合数据位表示的的位图称为真彩色位图。
下面结合Python3工具,给出一些图像的基本操作代码和效果图。
主要内容
- 读入并显示图像
- 获取图像高、宽和通道数
- 获取图像像素信息数
- 图像缩放
- 图像保存
- 彩图转灰度图
- 图像旋转
- 图像翻转
- 彩色图像三通道分离与合并
- 直接操作像素
1.读入并显示图像
import cv2
import numpy as np
def cv2_imread(file_path, flag=1):
"""解决包含中文的路径cv2.imread无法打开的问题的函数"""
return cv2.imdecode(np.fromfile(file_path, dtype=np.uint8), flag)
if __name__ == "__main__":
img = cv2_imread("C:\\Users\\11537\\Desktop\\新垣结衣.jpg") #读入图像,第一个参数选一张自己计算机内的图片的路径
cv2.imshow("orginal image", img) #显示图像
cv2.waitKey(0) #在键盘敲入字符前程序处于等待状态
cv2.destroyAllWindows() #关闭所有窗口
2. 获取图像高、宽和通道数
图像的高即是位图对应的矩阵行数,宽即是位图对应的矩阵列数,通道数表示每一个像素点包含的信息维数。灰度图通道数为1,彩色图像通道数为3(代表红绿蓝一个通道)
在python中,我们可以很直接的用读入图片的shape成员变量得到图像的这三个信息,其返回的是一个三元组,依次分别为高、宽、通道数。
import cv2
import numpy as np
def cv2_imread(file_path, flag=1):
"""解决包含中文的路径cv2.imread无法打开的问题的函数"""
return cv2.imdecode(np.fromfile(file_path, dtype=np.uint8), flag)
if __name__ == "__main__":
img = cv2_imread("C:\\Users\\11537\\Desktop\\新垣结衣.jpg")
print(img.shape) #img.shape返回一个三元组,打印出这个三元组
print(f"图像的高为:{img.shape[0]}")
print(f"图像的宽为:{img.shape[1]}")
print(f"图像的通道数为:{img.shape[2]}")
3. 获取图像像素信息数
跟2类似,图像的size成员变量直接表示图像所装载的信息数,其等于图像的行数、列数、通道数的乘积,即img.size = img.shape[0]*img.shape[1]*img.shape[2]
4. 图像缩放
使用cv2.resize方法。
语法为:cv2.resize(src, dsize[, dst[, fx[, fy[, interpolation]]]]) → dst
src:原图像
dst:目标图像
dsize:目标图像大小。
fx:可选项,水平轴上的比例因子。
fy:可选项,垂直轴上的比例因子。
其中可选项interpolation(插值)选项有:
其中缩小图像用INTER_AREA更好,放大图像用 INTER_CUBIC更好。
一个缩小图像的代码实例。resize中有两个坑需要注意,一个是第二个目标图像大小参数,这个二元组表示的是依次表示图像的宽和高,而不是shape中的高和宽的次序!另外,元组中的元素必须是整型。
import cv2
import numpy as np
def cv2_imread(file_path, flag=1):
"""解决包含中文的路径cv2.imread无法打开的问题的函数"""
return cv2.imdecode(np.fromfile(file_path, dtype=np.uint8), flag)
if __name__ == "__main__":
img = cv2_imread("C:\\Users\\11537\\Desktop\\新垣结衣.jpg")
dst1 = cv2.resize(img, (960, 600), interpolation=cv2.INTER_AREA) #等比缩小两倍
dst2 = cv2.resize(img, (img.shape[1]//4, img.shape[0]//4), interpolation=cv2.INTER_AREA) #等比缩小四倍
cv2.imshow("Original image", img);
cv2.imshow("Reduced by twice", dst1);
cv2.imshow("Reduced by four times", dst2);
cv2.waitKey(0) #在键盘敲入字符前程序处于等待状态
cv2.destroyAllWindows() #关闭所有窗口
5. 图像保存
很容易想到,有imread必有imwrite,所以保存图像可用imwrite(filename, img)函数。
即对于上面示例的两个缩放图像,可以用以下两行代码分别保存
imwrite("C:\\Users\\11537\\Desktop\\dst1.jpg", dst1)
imwrite("C:\\Users\\11537\\Desktop\\dst2.jpg", dst2)
但是,这函数有个坑,同样与cv2.read类似,对于包含中文的路径,保存会出现文件名乱码现象。所以推荐用以下方案。
cv2.imencode('.jpg', dst1)[1].tofile('C:\\Users\\11537\\Desktop\\dst1.jpg')
cv2.imencode('.jpg', dst1)[1].tofile('C:\\Users\\11537\\Desktop\\dst1.jpg')
6. 彩图转灰度图
import cv2
import numpy as np
def cv2_imread(file_path, flag=1):
"""解决包含中文的路径cv2.imread无法打开的问题的函数"""
return cv2.imdecode(np.fromfile(file_path, dtype=np.uint8), flag)
if __name__ == "__main__":
img = cv2_imread("C:\\Users\\11537\\Desktop\\新垣结衣.jpg")
dst = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
dst = cv2.resize(dst, (dst.shape[1]//2, dst.shape[0]//2), interpolation=cv2.INTER_AREA)
cv2.imshow("dst", dst)
cv2.waitKey(0) #在键盘敲入字符前程序处于等待状态
cv2.destroyAllWindows() #关闭所有窗口
7. 图像旋转
opencv中对图像的旋转主要是先通过getRotationMatrix2D函数得到图像的旋转矩阵,然后再通过仿射变换函数warpAffine得到旋转后的图像。
函数语法:
cv2.getRotationMatrix2D(center, angle, scale)
cv2.warpAffine(src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]]) → dst
对于getRotationMatrix2D:
center:旋转中心点
angle:旋转角度
scale:图像缩放因子
对于warpAffine:
src:输入图像
M:2 X 3 的变换矩阵.
dsize:目标图像大小
dst:目标图像
flags:目标图像的插值方法
borderMode:图像边界的处理方式
borderValue:当图像边界处理方式为BORDER_CONSTANT 时的填充值
import cv2
import numpy as np
def cv2_imread(file_path, flag=1):
"""解决包含中文的路径cv2.imread无法打开的问题的函数"""
return cv2.imdecode(np.fromfile(file_path, dtype=np.uint8), flag)
if __name__ == "__main__":
img = cv2_imread("C:\\Users\\11537\\Desktop\\新垣结衣.jpg")
img = cv2.resize(img, (img.shape[1]//2, img.shape[0]//2), interpolation=cv2.INTER_AREA)
M = cv2.getRotationMatrix2D((img.shape[1]/2, img.shape[0]/2),90,1)
dst = cv2.warpAffine(img,M,(img.shape[1], img.shape[0]))
cv2.imshow("dst", dst)
cv2.waitKey(0) #在键盘敲入字符前程序处于等待状态
cv2.destroyAllWindows() #关闭所有窗口
8. 图像翻转
使用cv2.flip函数。
cv2.flip(src, flipCode[, dst]) → dst
其中,flipCode参数为翻转模式,flipCode=0垂直翻转,flipCode>0水平翻转,flipCode<0水平垂直翻转。
import cv2
import numpy as np
def cv2_imread(file_path, flag=1):
"""解决包含中文的路径cv2.imread无法打开的问题的函数"""
return cv2.imdecode(np.fromfile(file_path, dtype=np.uint8), flag)
if __name__ == "__main__":
img = cv2_imread("C:\\Users\\11537\\Desktop\\新垣结衣.jpg")
img = cv2.resize(img, (img.shape[1]//4, img.shape[0]//4), interpolation=cv2.INTER_AREA)
dst1 = cv2.flip(img, 0) #垂直翻转
dst2 = cv2.flip(img, 1) #水平翻转
dst3 = cv2.flip(img, -1) #水平垂直翻转
cv2.imshow("Original image", img)
cv2.imshow("dst1", dst1)
cv2.imshow("dst2", dst2)
cv2.imshow("dst3", dst3)
cv2.waitKey(0) #在键盘敲入字符前程序处于等待状态
cv2.destroyAllWindows() #关闭所有窗口
9. 彩色图像三通道分离与合并
分离用split函数,合并用merge函数。注意单元像素元组顺序依次为BGR而不是RGB!
import cv2
import numpy as np
def cv2_imread(file_path, flag=1):
"""解决包含中文的路径cv2.imread无法打开的问题的函数"""
return cv2.imdecode(np.fromfile(file_path, dtype=np.uint8), flag)
if __name__ == "__main__":
img = cv2_imread("C:\\Users\\11537\\Desktop\\新垣结衣.jpg")
img = cv2.resize(img, (img.shape[1]//4, img.shape[0]//4), interpolation=cv2.INTER_AREA)
b, g, r = cv2.split(img)
merged = cv2.merge([b, g, r])
cv2.imshow("R", r)
cv2.imshow("G", g)
cv2.imshow("B", b)
cv2.imshow("Merged", merged)
cv2.waitKey(0) #在键盘敲入字符前程序处于等待状态
cv2.destroyAllWindows() #关闭所有窗口
10. 直接操作像素
对于彩色图调用img[row,col]可获取该像素的一个三元组(g, b ,r),对于灰度图调用img[row,col]可获取该像素的灰度值。同理也可直接赋值。例如:
img[50, 60] = (0,0,255) #将50行60列的相素变为红色
img[50, 60, 2] = 255 #仅改变50行60列的R通道值,赋为255
img[50, 60][2] = 255 #另一种写法,效果同上
另外,Python还支持一种类似于切片的赋值,使用也非常方便。
例如:img[50:60, 100:200, 1] = 0 #将50至59行,100至199列,G通道值赋为0
一 个必须注意的坑点:
当我们读入一张图片后赋给img变量,并需对其进行像素的直接操作,但我们也需要保留一张原始图像进行其他处理操作。这时我们可能会写出这样的代码。
img = cv2_imread(filepath)
img2 = img
#对img进行像素操作,得到已修改过的目标图像img,并保存
#对img2进行其他操作,得到目标图像dst,并保存
这是一种典型的错误操作,问题出在第二行,Python基础比较好的读者可能知道其实img2 = img并不是真正意义上的复制操作,而只是一个引用传递,img和img2指向的是同一块内存单元(换句话说是指向的同一张图),故在对img2操作时,实际上是对已修改过的img进行操作,而非原始图像。
正确代码如下:
img = cv2_imread(filepath)
img2 = img.copy()
#对img进行像素操作,得到已修改过的目标图像img,并保存
#对img2进行其他操作,得到目标图像dst,并保存
用img2 = img.copy() 代替img2 = img即可。
下一篇笔记记录总结有关于图像二值化处理相关内容。
评论(0)
您还未登录,请登录后发表或查看评论