图像处理中知识点01

本文记录一下在实现 DDRQM 过程中的一些 OpenCV 框架和 python 相关知识点。

1.cv2.filter2D():该函数表示在一张图像上应用相应的卷积核,完整的函数调用形式如下:

1
cv2.filter2D(src, ddepth, kernel[, dst[, anchor[, delta[, borderType]]]]) -> dst

该函数将任意的 linear filter 应用到一张图像上。当 filter 的部分孔径落在图像之外时,函数会根据 border 类型进行插值。该函数实际上计算的是相关(correlation),而不是卷积(convolution)。

例如,下面代码:

1
2
3
retval = cv2.getGaborKernel(ksize=(111,111), sigma=10, theta=60, lambd=10, gamma=1.2)
image1 = cv2.imread('src.jpg')
result = cv2.filter2D(image1,-1,retval)

表示将对应的 GaborGabor 滤波器应用在图像src.jpg上。

1
2
a=cv2.getGaborKernel((40,40), 1.69, 0, 3, 0.5)
b=Gabor_filter((40,40), 1.69, 0, 3, 0.5)

2.cv2.getGaborKernel():该函数为 GaborGabor 滤波器函数,其返回值为一个 GaborfilterGabor filter,具体形式为一个二维数组,完整的函数调用形式如下:

1
cv.getGaborKernel(ksize, sigma, theta, lambd, gamma[, psi[, ktype]]) -> retval

3.代码为:

1
result[x][y] = abs(depth_img[x][y] - 0.5*(depth_img[x1][y1]+depth_img[x2][y2]))

报错信息:RuntimeWarning: overflow encountered in ubyte_scalars

分析:可能是将两个unit8类型的值相加并将其存入一个unit8,导致数值溢出

解决方案:将unit8类型数值转换为int类型。

1
result[x][y] = abs(int(depth_img[x][y]) - 0.5*(int(depth_img[x1][y1])+int(depth_img[x2][y2])))

参考资料:

  1. RuntimeWarning: overflow encountered in ubyte_scalars

  2. Why I am getting “RuntimeWarning: overflow encountered in ubyte_scalars error” in python?


4.以下代码:

1
2
img = cv2.imread(path)
height, width, channels = img.shape

表示从路径path中读取图像文件并存入img对象中,通过type(img)可知该对象类型为<class 'numpy.ndarray'>img.shape返回的是一个三元元组,其值分别对应图像的heightwidthchannels,即图像形状并不是我们熟悉的width * height * channels的形式

可以通过以下代码:

1
img = cv2.transpose(img)

img转置为width * height * channels形状,通过我们熟悉的方式访问。

PS:imgchannels顺序为BGRB G R

参考资料:

  1. Opencv showing wrong width and height of image
  2. OpenCV-Python教程:几何空间变换~缩放、转置、翻转(resize,transpose,flip)
  3. Image file reading and writing

5.PILLOW vs OpenCV

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 读取和加载图片
# PILLOW
from PIL import Image
img = Image.open('NLPR1.jpg') # 打开图片
img.size # 获取图片尺寸 width * height
img_rgb = img.load() # 分配内存,并将图像像素值添加到img_rgb对象
img_rgb[width, height] # 通过width/height访问像素值,返回值为RGB模式
# OpenCV
import cv2
img = cv2.imread('NLPR1.jpg') # 打开并将图像像素值添加到img对象
img.shape # 获取图片尺寸 height * width * channels
img[height][width] #通过height/width访问像素值,返回值为BGR模式

# 读取和转换为灰度图
# PILLOW
img = Image.open('NLPR1.jpg') # 打开图片
img_gray = img.convert('L') # 转换为灰度图
pixels_gray = img_gray.load() # 分配内存,并将图像灰度值添加到img_gray对象
# OpenCV
# 直接读取灰度图
img = cv2.imread('NLPR1.jpg', 0) # 将图像转换为灰度图后读取
# 或者
img = cv2.imread('NLPR1.jpg', cv2.IMREAD_GRAYSCALE)
# 先读取彩色图,再转化为灰度图
img = cv2.imread('NLPR.jpg')
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 将BGR模式的彩色图像转换为灰度图

参考资料:

  1. Image.load()
  2. python opencv将图片转为灰度图
  3. imread()
  4. ImreadModes
  5. cvtColor
  6. ColorConversionCodes

6.cv2.imread()详解:用于读取图像

该函数的完整调用形式为:cv.imread(filename[, flags]) ->retvalretval为返回值。下面对各个参数做具体说明(说明针对C++函数版本):

  • filename:要读取的图像路径,可以为绝对路径或相对路径

  • flags:可以对图像采取的ImreadModes,常见的有cv2.IMREAD_GRAYSCALEcv.IMREAD_COLOR

调用实例:

1
img = cv2.imread('NLPR1.jpg') # 打开并将图像像素值添加到img对象

PS:该函数对RGB图的默认读取顺序为BGRBGR

参考资料:

  1. imread()
  2. ImreadModes

7.cv2.cvtColor详解:用于转换图像

该函数的完整调用形式为:cv.cvtColor(src, code[, dst[, dstCn]]) ->dstdst为返回值。下面对各个参数做具体说明(说明针对C++函数版本):

  • src:输入图像

  • dst:输出图像,和src有相同的sizedepth,即相同的heightwidthchannles

  • code:颜色空间转换模式ColorConversionCodes,常见的有cv2.BGR2GRAYcv2.BGR2RGB

  • dstCn:输出图像的channels数,该参数为0时,根据srcdst自动生成

调用实例:

1
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 将BGR模式的彩色图像转换为灰度图

参考资料:

  1. cvtColor
  2. ColorConversionCodes

8.cv2.resize()详解:用于放缩图像

该函数的完整调用形式为:cv.resize(src, dsize[, dst[, fx[, fy[, interpolation]]]]) ->dstdst为返回值。下面对各个参数做具体说明(说明针对C++函数版本):

  • src:输入图像

  • dst:输出图像,当dsize不为0(Python中为None)时,其size与dsize相同;dsize为零时,则与src.size()fxfy相同。其类型与src相同。

  • dsize:输出图像尺寸,当dsize为0或None时,计算方式为:

    dsize = Size(round(fx*sr.cols), round(fy*src.rows))

    要么dsize非零,要么fxfy非零

  • fx:沿着水平轴的放缩因子,当为0时,可以通过(double)dsize.width/src.cols计算

  • fy:沿着垂直轴的放缩因子,当为0时,可以通过(double)dsize.height/src.rows计算

  • interpolation:插值方法,常见的插值方法有cv2.INTER_NEARESTcv2.INTER_LINEAR,默认为cv2.INTER_LINEAR

调用实例:

1
2
3
4
img = cv2.imread('NLPR1.jpg')
img_resize = cv2.resize(img, (100, 100)) # 通过dsize 放缩
# 或者
img_resize = cv2.resize(img, None, fx=0.5, fy=0.3) # 通过 fx, fy 放缩

参考资料:

  1. resize()
  2. InterpolationFlags

9.cv2.transpose()详解:用于转置矩阵(数组)

该函数的完整调用形式为:cv.transpose(src[, dst]) ->dst。下面对各个参数做具体说明(说明针对C++函数版本):

  • src:输入数组
  • dst:和src相同类型的输出数组

其效果为:dst(i, j) = src(j, i)。调用实例:

1
img = cv2.transpose(img)

参考资料:

  1. transpose()

10.cv2.flip()详解:用于翻转图像(数组)

该函数的完整调用形式为:cv.flip(src, flipCode[, dst]) ->dst。下面对各个参数做具体说明(说明针对C++函数版本):

  • src:输入数组
  • dst:输出数组,和src具有相同类型和尺寸
  • flitCode:用来指定怎样翻转数组,0表示沿x轴翻转,正值如1表示沿y轴翻转,-1表示同时沿x轴和y轴翻转,即绕(0,0)翻转

调用实例:

1
img = cv2.flip(img, 0)

参考资料:

  1. flip()

11.cv2.imshow()详解:用于显示图像

该函数的完整调用形式为:cv.imshow(winname, mat) ->None。下面对各个参数做具体说明(说明针对C++函数版本):

  • winname:窗口名,显示在窗口顶栏
  • mat:要显示的矩阵

调用示例:

1
2
3
4
img = cv2.imread('NLPR.jpg')
cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

参考资料:

  1. imshow()

12.cv2.waitKey()详解:用于指定窗口打开时间

该函数的完整调用形式为:cv.waitKey([, delay]) ->retval。下面对各个参数做具体说明(说明针对C++函数版本):

  • delay:表示窗口的持续时间,为0时表示保持窗口打开,为其他正值时表示窗口持续的毫秒数

调用示例:

1
2
cv2.imshow('img', img)
cv2.waitKey(1000)

参考资料:

  1. waitKey()

13.cv2.destroyAllWindows()详解:用于关闭所有 HightGUI 窗口

该函数的完整调用形式为:cv.destroyAllWindows() ->None

调用实例:似乎不会关闭窗口

1
2
3
cv2.imshow('img', img)
cv2.waitKey(1000)
cv2.destroyAllWindows()

PS:cv2.destroyWindows(winname)用于关闭指定窗口

参考资料:

  1. destroyWindow()

14.python中获取当前目录路径和上级路径:

1
2
3
4
5
6
7
8
9
10
11
12
13
import os

print('***获取当前目录***')
print(os.getcwd())
print(os.path.abspath(os.path.dirname(__file__))) # __file__表示文件名

print '***获取上级目录***'
print(os.path.abspath(os.path.dirname(os.path.dirname(__file__))))
print(os.path.abspath(os.path.dirname(os.getcwd())))
print(os.path.abspath(os.path.join(os.getcwd(), "..")))

print '***获取上上级目录***'
print(os.path.abspath(os.path.join(os.getcwd(), "../..")))

参考资料:

  1. python获取当前目录路径和上级路径
  2. Python获取当前文件路径

15.cv2.GaussianBlur()详解:用于给图片添加高斯模糊

该函数的完整调用形式为:

cv.GaussianBlur(src, ksize, sigmaX[, dst[, sigmaY[, borderType]]]) ->dst

下面对各个参数做具体说明(说明针对C++函数版本):

  • src:输入图像,图像有不同的通道,会被分别处理
  • dst:输出图像,类型和尺寸与src相同
  • ksize:高斯核尺寸。ksize.widthksize.height必须为正奇数;否则ksize为0,此时其值通过sigmaXsigmaY计算
  • sigmaX:在X方向上的 Gaussian kernel standard deviation
  • sigmaY:在Y方向上的 Gaussian kernel standard deviation。为0时设为与sigmaX相等。
  • borderType:像素外插值方法,常见的有cv.BORDER_CONSTANTcv.BORDER_REPLICATE,不支持cv.BORDER_WRAP

为了完全控制对图片的操作而不需要管OpenCV后续对语义的修改,建议对ksize/sigmaX/sigmaY都进行指定。

调用实例:

1
2
3
depth_img = cv2.imread('NLPR2_depth.png', 0)
# 给深度图添加高斯模糊
depth_img = cv2.GaussianBlur(depth_img, (31, 31), sigmaX=0)

参考资料:

  1. GaussianBlur()
  2. BorderTypes
  3. 高斯模糊
  4. 高斯模糊的原理是什么,怎样在界面中实现

16.cv2.threshold():用于对每个数组元素应用固定水平的阈值。该函数通常用于从一个灰度图中得到二值图像或者去除噪音(即过滤掉太小或太大的值)。该函数支持几种阈值,通过类型参数来设置。

与此同时,THRESH_OTSUTHRESH_TRIANGLE可以联合上述的值来使用。这种情况下,函数通过 Otsu 或者 Triangle 算法来计算最优的阈值。

PS:目前,Otsu 算法和 Triangle 算法只在 8-bit 的单通道图像上实现

该函数的完整调用形式为:

cv.threshold(src, thresh, maxval, type[, dst]) ->retval, dst

  • src:输入数组(多通道,8-bit 或者 32-bit 浮点数)
  • dst:和src同尺寸、类型和通道的输出数组
  • thresh:阈值
  • maxval:在使用THRESH_BINARYTHRESH_BINARY_INV参数时的最大值
  • type:阈值类型,常见的有THRESH_BINARYTHRESH_BINARY_INV

参考资料:

  1. threshold()
  2. ThresholdTypes
  3. OpenCV-Python入门教程6-Otsu阈值法
  4. OTSU算法(大津法)原理解析

17.cv2.imwrite():用于存储图片到指定文件,图片类型取决于文件后缀名。其完整声明形式如下:

1
cv.imwrite(filename, img[, params]) -> retval
  • filename:存储文件名
  • img:图片数据对应的矩阵
  • params:成对的指定存储格式的参数。

参考资料:

  1. imwrite()

18.cv2.Canny():用于通过CannyCanny算法查找图像边缘。其完整声明形式如下:

1
2
3
4
5
6
7
8

void cv::Canny (InputArray image,
OutputArray edges,
double threshold1,
double threshold2,
int apertureSize = 3,
bool L2gradient = false
)

该函数在输入的图像中查找边缘并通过CannyCanny算法在输出中标记出它们。在threshold1和threshold2中最小的值将用于edge linking,最大值将用于查找初始的更为强烈/显著的边缘。具体见 Canny edge detector

  • image:8-bit的输入图片
  • edges:输出的 edge map,8-bit单通道,和image尺寸相同
  • threshold1:滞后过程(hysteresis procedure)的第一个阈值
  • threshold2:滞后过程的第二个阈值
  • apertureSizeSobelSobel操作子的孔径尺寸
  • L2gradient:a flag。表明是否使用更准确的L2L2范数(dI/dx)2+(dI/dy)2\sqrt{(dI/dx)^2+(dI/dy)^2}计算图像梯度大小(L2gradient=true),还是使用默认的L1L1范数dI/dx+dI/dy\sqrt{|dI/dx|+|dI/dy|}L2gradient=false)。

调用实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('messi5.jpg',0)
edges = cv2.Canny(img,100,200)

plt.subplot(121),plt.imshow(img,cmap = 'gray')
plt.title('Original Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(edges,cmap = 'gray')
plt.title('Edge Image'), plt.xticks([]), plt.yticks([])

plt.show()

参考资料:

  1. Canny()
  2. Canny Edge Detection in OpenCV
  3. Canny算子
  4. Canny edge detector
  5. Canny边缘检测
  6. Python实现Canny算子边缘检测

19.cv2.Sobel():使用SobelSobel算子计算图像导数。其完整声明形式如下:

1
2
3
4
5
6
7
8
9
10
void cv::Sobel(InputArray src,
OutputArray dst,
int ddepth,
int dx,
int dy,
int ksize = 3,
double scale = 1,
double delta = 0,
int borderType = BORDER_DEFAULT
)

该函数通过用合适的核与函数做卷积来计算图像导数。通常,该函数通过xorder=1, yorder=0, ksize=3xorder=0, yorder=1, ksize=3来计算图像的一阶xxyy导数,分别对应:

[101202101][121000121]\begin{bmatrix} -1 & 0 & 1 \\ -2 & 0 & 2 \\ -1 & 0 & 1 \end{bmatrix} 和 \begin{bmatrix} -1 & -2 & 1 \\ 0 & 0 & 0 \\ 1 & 2 & 1 \end{bmatrix}

这两个核。

  • src:输入图片
  • dst:相同尺寸和通道数的输出图片
  • ddepth:输出图片depth,见 combinations。(depth指图片的数据类型,例如对应图像梯度你想要16bit而不是8bit。当输入图片为8-bit类型时,将导致梯度裁剪(精度不够)
  • dx:梯度xx的order
  • dy:梯度yy的order
  • ksizeSobelSobel核尺寸,必须是1/3/5/7。
  • scale:计算梯度值时的可选因子,默认不提供
  • delta:在dst中存储结果之前默认加到结果上的可选delta值。
  • borderType:像素插值类型。

参考资料:

  1. Soble()
  2. Sobel算子
  3. 边缘检测

20.numpy.ndarray.flatten():返回一个坍缩成一维的数组的副本。

调用实例:

1
2
3
4
5
>>> a = np.array([[1,2], [3,4]])
>>> a.flatten()
array([1, 2, 3, 4])
>>> a.flatten('F')
array([1, 3, 2, 4])

参考资料:

  1. numpy.ndarray.flatten

21.math.atan(x)vsmath.atan2(y, x)

atan返回x对应的arc tangent,其结果所属区间为(π/2,π/2)(-\pi/2, \pi/2)

atan2(y, x)则返回atan(y/x),其结果所属区间为(π,π)(-\pi, \pi)

举例来说,atan(1)=atan2(1,1)=π/4=\pi/4atan2(-1,-1)=3π/4=-3\pi/4

参考文献:

  1. math.atan(x)

22.numpy.zeros:用于返回给定shapetype的用零填充的新的数组。其完整调用形式为:

1
numpy.zeros(shape, dtype=float, order='C', *, like=None)
  • shape:指定数组形状,如(2, )(2,3)
  • dtype:指定填充的数据类型,如dtype=int,默认为numpy.float64
  • order:指定行优先还是列优先,默认为order='C',行优先,order='F'表示列优先。
  • like:引用对象,用于创建非Numpy arrays类型的数组,可以兼容其他类型的数组。

调用实例:

1
2
3
4
>>> np.zeros(5)
array([ 0., 0., 0., 0., 0.])
>>> np.zeros((5,), dtype=int)
array([0, 0, 0, 0, 0])

PS:numpy.ones与之类似

参考资料:

  1. numpy.zeros
  2. numpy.ones

23.在进行灰度图转为BGR图img = cv2.cvtColor(gray, cv2.COLOR_GRAY2BGR)之前,需要确保灰度图gray中数值的类型满足转换要求,否则会出现如下错误:

image-20220626163339376

可以通过gray = np.uint8(gray)将灰度图转换为满足条件的格式:

参考资料:

  1. Opencv error -Unsupported depth of input image:

24.numpy.sum:计算给定axis上数组的元素之和。其完整调用形式为:

1
numpy.sum(a, axis=None, dtype=None, out=None, keepdims=<no value>, initial=<no value>, where=<no value>)[source]
  • a:数组类型的对象
  • axis:None、整数或整数类型的元组,可选。默认为None,将对所有元素求和。
  • dtype:数据类型,可选。
  • out:用于存放求和结果的输出数组,可选。

调用实例:

1
2
3
4
5
6
7
>>> a = np.ones((10,2), dtype=int)
>>> a.sum()
20
>>> a.sum(axis=0)
array([10, 10])
>>> a.sum(axis=0)/6
array([1.66666667, 1.66666667])

参考资料:

  1. numpy.sum

25.在算法实现中使用DFSDFS时出现以下报错:

1
RecursionError: maximum recursion depth exceeded in comparison

其出现原因为递归序列太长超过了系统设置,可以通过以下方式查看系统设置的最大递归深度:

1
2
3
>>> import sys
>>> print(sys.getrecursionlimit())
1000

此时可以通过两种方式解决该问题:

  • 设置更大的系统递归深度:sys.setrecursionlimit(1000*1000+10)
  • 优化算法,采用更高效的方式实现。(推荐)

参考资料:

  1. Maximum recursion depth exceeded in comparison
  2. Python infinite recursion with formula

26.当判断python字典中是否存在某个key时,has_key()方法在python中无法使用:

image-20220629171740572

其原因在于has_key()方法在Python3中被去除。

通过for i, j in img_depth_pair.keys:遍历字典键时,会出现如下报错:

image-20220629172819981

其原因在于dict.keys为不可迭代对象,此时应该通过以下方式遍历key和value:

1
2
3
for key in img_depth_pair:
print(key)
print(img_depth_pair[key])

此外,值得补充的是,列表不能作为python字典的键,而元组可以,这一特性源自python语言的设计。

参考资料:

  1. 使用 for 循环遍历 Python 字典的 3 种方法 !
  2. ‘dict’ object has no attribute ‘has_key’
  3. Why can’t I use a list as a dict key in python?
  4. Why Lists Can’t Be Dictionary Keys

27.在OpenCV中进行图像处理时,经常会出现类似于下列的数据类型的问题:

image-20220630103210112

下面是OpenCV中各种数据类型的参考列表:

image-20220630103342159

其中C1/C2/C3/C4表示通道数。0~30表示编号。

下面是一个更为详细的列表:

image-20220630103853172

参考资料:

  1. LIST OF MAT TYPE IN OPENCV
  2. OpenCV “cv::Mat” Data Types

28.在使用numpy中的dtype(data type)参数时,常疑惑于参数类型的选择,此处对常用的dtype类型做一个梳理:

image-20220630104939506

PS:上述列表并未展示所有的dtype类型,详见参考资料:

参考资料:

  1. Array types and conversions between types
  2. Data type objects dtype

29.在使用cv2.imshow('img', img)时,传入的imgdtype需要为np.uint8类型,否则展示的图片会出现奇怪的扭曲 (distortion):(numpy类型转换)

1
2
3
4
5
6
7
8
import numpy as np
import cv2
[...]
info = np.iinfo(data.dtype) # Get the information of the incoming image type
data = data.astype(np.float64) / info.max # normalize the data to 0 - 1
data = 255 * data # Now scale by 255
img = data.astype(dtype=np.uint8)
cv2.imshow("Window", img)

参考资料:

  1. Convert np.array of type float64 to type uint8 scaling values

30.在使用以下代码进行图片展示时:

1
2
3
cv2.imshow('edge', edge)
cv2.waitKey(0)
cv2.destroyAllWindows()

可能出现如下错误:

image-20220805231829547

此时可能是本地的OpenCV包出现了编译错误,可以通过重装解决:

  • pip uninstall opencv-python

  • pip install opencv-python