真是惭愧,上一篇笔记还是一年前写的,现在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-alpha4.0-beta4.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. 读入并显示图像
  2. 获取图像高、宽和通道数
  3. 获取图像像素信息数
  4. 图像缩放
  5. 图像保存
  6. 彩图转灰度图
  7. 图像旋转
  8. 图像翻转
  9. 彩色图像三通道分离与合并
  10. 直接操作像素

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即可。

下一篇笔记记录总结有关于图像二值化处理相关内容。

参考资料

  1. OpenCV 4.0 - OpenCV library

2. zh.wikipedia.org/wiki/%