以下只列举常用的方法和常用的参数,例如枚举,只会将最常用的几个列举出来,详细的请参考官方文档。

对于C++默认使用了using namespace cv;

对于Python默认使用了import cv2 as cvimport numpy as np

OpenCV版本:3.4.7

读取图片

1
imread(filename, flags)
  1. filename:图片路径
  2. flags
    1. IMREAD_COLOR:默认值,返回3通道的BGR色彩图像
    2. IMREAD_GRAYSCALE:返回单通道灰色图片
    3. IMREAD_UNCHANGED:按原样返回。

C++

1
Mat src = imread("1.jpg", IMREAD_UNCHANGED);

对于C++来说,imread的返回值为Mat类,该类是OpenCV的自建类

Python

1
src = cv.imread("1.jpg", cv.IMREAD_UNCHANGED)

对于Python来说,imread的返回值为NumPy.ndarray

对于图像矩阵,C++都是自建的Mat类,而Python则是numpy.ndarray。后面不在赘述这个问题。

保存图片

1
imwrite(filename, img)
  1. filename:图片要保存的路径
  2. img:图像矩阵

比较简单,不做详细介绍。

该方法还有一个参数,可以参数编码,详细点击imwrite

显示图片

1
show(winname, src)
  1. winname:窗口的名称
  2. src:显示的图片矩阵

图片缩放

图片缩放常见算法有:

  1. 最近领域插值法
  2. 双线性插值法
  3. 双三次插值法

OpenCV官方提供的resize()的函数(默认为双线性插值法):

1
resize(src, dst, dsize, fx, fy, interpolation)
  1. src要缩放的图片
  2. dst输出的图像
  3. dsize输出图像的大小
    • C++Size
    • Python:元组,前面为width,后面为height
  4. fx:沿水平轴的比例因子
    • 当它等于 0 时,计算为 dsize.width / src.cols
  5. fy:沿垂直轴的比例因子
    • 当它等于 0 时,计算为 dsize.height / src.rows
  6. interpolation:缩放用的方法
    1. INTER_NEAREST:最近邻域插值法
    2. INTER_LINEAR:双线性插值法
    3. INTER_CUBIC:双三次插值法

C++

1
2
Mat dst;
resize(src, dst, Size(100, 100));

Python

1
dst = cv.resize(src, (100, 100), interpolation=cv.INTER_NEAREST)

图像操作

仿射变换

矩阵的缩放、旋转、位移都可以使用矩阵变换来实现

缩放矩阵:
$$
\begin{bmatrix}
k_x & 0 & 0 \
0 & k_y & 0
\end{bmatrix}
$$
旋转矩阵:
$$
\begin{bmatrix}
cos\theta & -sin\theta & 0 \
sin\theta & cos\theta & 0
\end{bmatrix}
$$
位移:
$$
\begin{bmatrix}
1 & 0 & t_x \
0 & 1 & t_y
\end{bmatrix}
$$
仿射变换的函数:

1
warpAffine(src, dst, M, dsize)
  1. src:输入的图像
  2. dst:输出的图像
  3. M:(2 x 3)的转换矩阵
  4. dsize:输出图像的尺寸

获取旋转矩阵的方法

1
getRotationMatrix2D(center, angle, scale)
  1. center:旋转中心
    1. c++:类型为Point2f
    2. Python:类型为元组,两个元素,分别是x,y
  2. angle:旋转角度,单位为度,正值表示逆时针旋转。
  3. scale:缩放比例

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 平移矩阵
Mat translation_matrix = (Mat_<float>(2, 3) << 1, 0, 1, 0, 1, 1);

// 缩放矩阵
Mat scale_matrix = (Mat_<float>(2, 3) << 2, 0, 0, 0, 2, 0);

// 旋转矩阵
Mat rotated_matrix = getRotationMatrix2D(
Point2f(src.size().width / 2, src.size().height / 2), 45, 0.5);

// 获取对应的图形
Mat dst;
// 平移
// warpAffine(src, dst, translation_matrix, src.size());
// 缩放
// warpAffine(src, dst, scale_matrix, src.size());
// 旋转
// warpAffine(src, dst, rotated_matrix, src.size());

// imshow("dst", dst);

Python

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
# 获取图像的高和宽
height, width = src.shape[0:2]

# 平移矩阵
translation_matrix = np.float32([[1, 0, 1],
[0, 1, 1]])
# 缩放矩阵
scale_matrix = np.float32([[2, 0, 0],
[0, 2, 0]])

# 旋转矩阵
rotated_matrix = cv.getRotationMatrix2D((width/2, height/2), 45, 0.5)

# 平移
# dst = cv.warpAffine(src, translation_matrix, src.shape[1::-1])
# 缩放
# dst = cv.warpAffine(src, scale_matrix, src.shape[1::-1])
# 旋转
# dst = cv.warpAffine(src, rotated_matrix, src.shape[1::-1])
# 解释一下src.shape[1::-1]
# 在src.shape中,第一个值代表height,第二个代表width
# 在 warpAffine 参数中 dsize, 第一个表示width, 第二个表示height
# 综上,将其反过来即可

# cv.imshow("dst", dst)

透视变换

透视变换(Perspective Transformation)是指利用透视中心、像点、目标点三点共线的条件,按透视旋转定律使承影面(透视面)绕迹线(透视轴)旋转某一角度,破坏原有的投影光线束,仍能保持承影面上投影几何图形不变的变换。

ros_topic

透视变换的函数:

1
warp(src, dst, M, dsize)
  1. src:输入的图像
  2. dst:输出的图像
  3. M:(3 x 3)转换矩阵
  4. dsize:输出图像的尺寸

获取透视变换需要的矩阵

1
getPerspectiveTransform(src, dst)
  1. src:原图中四边形顶点的坐标
    1. C++:类型为vector<Point2f>
    2. Python:类型为numpy.ndarraydtypenumpy.float32
  2. dst:目标图像中的对应四个点的坐标,类型同上。

C++

1
2
3
4
5
6
7
8
9
// 这里仅仅说明用法,参数根据实际情况,src_point和dst_point应该不一样才对
vector<Point2f> src_point{Point2i(0, 0), Point2i(0, 1), Point2i(1, 0),
Point2i(1, 1)};
vector<Point2f> dst_point{Point2i(0, 0), Point2i(0, 1), Point2i(1, 0),
Point2i(1, 1)};
Mat matrix = getPerspectiveTransform(src_point, dst_point);
Mat dst;
warpPerspective(src, dst, matrix, src.size());
imshow("dst", dst);

Python

1
2
3
4
5
6
7
# 同上
src_point = np.float32(((0, 0), (0, 1), (1, 0), (1, 1)))
dst_point = np.float32(((0, 0), (0, 1), (1, 0), (1, 1)))
print(type(src_point))
matrix = cv.getPerspectiveTransform(src_point, dst_point)
dst = cv.warpPerspective(src, matrix, src.shape[1::-1])
cv.imshow("dst", dst)

图像金字塔(上、下采样)

上采样

1
pyrUp(src, dst)

下采样

1
pyrDown(src, dst)
  1. src:原图
  2. dst:采样结果图

C++

1
2
3
4
5
6
7
8
Mat up_dst, down_dst;
// 上采样
pyrUp(src, up_dst);
// 下采样
pyrDown(src, down_dst);

imshow("up_dst", up_dst);
imshow("down_dst", down_dst);

Python

1
2
3
4
5
6
7
# 上采样
up_dst = cv.pyrUp(src)
# 下采样
down_dst = cv.pyrDown(src)

cv.imshow("up_dst", up_dst)
cv.imshow("down_dst", down_dst)

图像融合

1
addWeighted(src1, alpha, src2, beta, gamma, dst)
  1. src1:图1
  2. alpha:图1系数,double
  3. src2:图2
  4. beta:图2系数,double
  5. gamma:添加的标量
  6. dst:输出图像

C++

1
2
3
Mat dst;
addWeighted(src, 0.5, src, 0.5, 100, dst);
imshow("dst", dst);

Python

1
2
dst = cv.addWeighted(src, 0.5, src, 0.5, 100)
cv.imshow("dst", dst)

绘制图形

绘制线段

1
line(src, pt1, pt2, color, thickness, lineType)
  1. src:图片
  2. pt1:起点
    1. C++Point类型
    2. Python:元组类型
  3. pt2:终止点,类型同上
  4. color:颜色
    1. C++Scalar类型,GBR
    2. Python:颜色(GBR)元组
  5. thickness:线宽,int,默认值为1
  6. lineType:线类型
    1. LINE_AA:抗锯齿线
    2. LINE_8:8连线,默认值

C++

1
line(src, Point(100, 100), Point(200, 200), Scalar(0, 0, 255));

Python

1
cv.line(src, (100, 100), (200, 200), (0, 0, 255))

绘制矩形

1
rectangle(src, pt1, pt2, color, thickness, lineType)

参数同上,但是pt1pt2一定是矩形的对角点。

注意:如果thickness为负数,则会使用color填充整个矩阵

C++

1
rectangle(src, Point(100, 100), Point(200, 200), Scalar(0, 0, 255));

Python

1
cv.rectangle(src, (100, 100), (200, 200), (0, 0, 255))

绘制圆

1
circle(src, center, radius, color, thickness, lineType)
  1. src:图片
  2. center:圆的中点
    1. C++Point类型
    2. Python:元组类型,(x, y)
  3. radius:半径,int
  4. color:颜色,同line
  5. thickness:线宽,同line
  6. lineType:线类型,同line

C++

1
circle(src, Point(100, 100), 100, Scalar(0, 0, 255))

Python

1
cv.circle(src, (100, 100), 100, (0, 0, 255))

绘制多边形

该方法用于绘制多条多边形曲线

1
polylines(src, pts, isClosed, color, thickness, lineType)

与上相同的参数不做解释(srccolorthicknesslineType

  1. pts:顶点集合,二维数组,分别表示多边形数量,多边形的顶点位置
  2. isClosed:是否闭合。如果闭合会从最后一个顶点到第一个顶点绘制一条直线。

C++

1
2
3
4
vector<vector<Point>> pts{
{Point(100, 100), Point(200, 200), Point(300, 400), Point(200, 100)}};
polylines(src, pts, false, Scalar(0, 0, 255));
imshow("src", src);

Python

1
2
3
pts = np.array([[(100, 100), (200, 200), (300, 400), (200, 100)]])
cv.polylines(src, pts, False, (0, 0, 255))
cv.imshow("src", src)

绘制文字

1
putText(src, text, org, fontFace, fontScale, Color, lineType)

与上相同的参数不做介绍。

  1. text:文字文本,string类型
  2. org:文字左下角的坐标位置
    1. C++Point类型
    2. Python:元组类型,(x, y)
  3. fontFace:字体名称。具体见HersheyFonts注意:OpenCV默认不支持中文
  4. fontScale:字体的缩放大小。

C++

1
putText(src, "I'm a text", Point(100, 100), FONT_HERSHEY_PLAIN, 1, Scalar(0, 0, 255));

Python

1
cv.putText(src, "I'm a text", (100, 100), cv.FONT_HERSHEY_PLAIN, 1, (0, 0, 255)

转换颜色空间

1
cvtColor(src, dst, code)
  1. code:颜色空间转换代码

该方法我们常常用来将原图转换为灰色图和将GBR转换为HSV

C++

1
2
3
Mat dst;
cvtColor(src, dst, COLOR_BGR2GRAY);
imshow("dst", dst);

Python

1
2
dst = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
cv.imshow("dst", dst)

直方图

统计直方图

1
calcHist(images, channels, mask,hist,histSize, ranges)
  1. images:需要处理的图片数组
  2. channels:计算对应图片的哪个通道
  3. mask:蒙版
  4. hist:输出的直方图数组
  5. histSize:输出的直方图的大小
  6. ranges:范围

C++

1
2
3
Mat hist;
calcHist(vector<Mat>{src}, vector<int>{0}, Mat(), hist, vector<int>{256},
vector<float>{0, 256});

注意:这里的vector后面使用的是大括号,注意vector大括号初始化和小括号初始化的差别。

Python

1
hist = cv.calcHist([src], [0], None, [256], [0, 256])

或者使用numpy的方式统计

1
2
# 统计第1个通道,其他类似
hist = np.bincount(src[:,0].ravel(), minlength=0)

绘制直方图

C++中绘制直方图比较麻烦,我们使用polylines取画出多边形,从而形成直方图。抽成自定义函数如下

1
2
3
4
5
6
7
8
9
10
11
12
13
void drawHist(Mat &hist, const Scalar &color = Scalar(0, 0, 255)) {
int bin = 4, width = 256 * bin, height = 800;
vector<Point> points;
Mat dst(height, width, CV_8UC3);
// 标准化
Mat out;
normalize(hist, out, 0, height - 100, NORM_MINMAX);
for (int i; i < hist.size().height; i++) {
points.push_back(Point(i * bin, 790 - out.at<float>(i)));
}
polylines(dst, vector<vector<Point>>{points}, false, color, 1, LINE_AA);
imshow("hist", dst);
}

在Python中使用Matplotlib即可,方便快捷高效。

1
2
plt.plot(hist)
plt.show()

HSV模型

HSV(Hue, Saturation, Value)是根据颜色的直观特性由A. R. Smith在1978年创建的一种颜色空间, 也称六角锥体模型(Hexcone Model)。

这个模型中颜色的参数分别是:色调(H),饱和度(S),明度(V)

  1. 当 S = 1,V=1时,H所代表的任何颜色被称为纯色
  2. 当 S = 0,即饱和度为0,颜色最浅,最浅被描述为灰色,灰色的亮度由V决定,此时H无意义
  3. 当 V = 0 时,颜色最暗,最暗被描述为黑色,因此此时H和S均无意义。

注意:在OpenCV中,H、S、V的取值范围是[0, 180]、[0, 255]、[0, 255],而不是[0, 360]、[0, 1]、[0, 1]

下面列出部分的HSV空间颜色值:

ros_topic

图像二值化

手动设置阈值

1
threshold(src, dst, thresh, maxval, type)
  1. src:单通道灰度图
  2. thresh:阈值
  3. maxval:最大值(一般取255)
  4. type:类型

当前公式解析有问题,公式来源

参数名 参数解释
THRESH_BINARY $dst\left( x,y\right) =\begin{cases}maxval & if\ src(x, y)\ >\ thresh \ 0\end{cases}$
THRESH_BINARY_INV $dst\left( x,y\right) =\begin{cases}0 & if\ src(x, y)\ >\ thresh \ maxval\end{cases}$
THRESH_TRUNC $dst\left( x,y\right) =\begin{cases}threshold & if\ src(x, y)\ >\ thresh \ src(x,y)\end{cases}$
THRESH_TOZERO $dst\left( x,y\right) =\begin{cases}src(x,y) & if\ src(x, y)\ >\ thresh \ 0\end{cases}$
THRESH_TOZERO_INV $dst\left( x,y\right) =\begin{cases}0 & if\ src(x, y)\ >\ thresh \ src(x,y) \end{cases}$

OpenCV官网使用了一张图片来描述这5个参数不同的含义,如下:

ros_topic

除了上面的5个参数,还有两个自动算法标志:

参数名 参数解释
THRESH_OTSU 大津算法
THRESH_TRIANGLE 三角算法(常用在图中出现大量的近视颜色的情况下)

C++

1
2
3
4
5
6
Mat dst;
// 手动填写阈值
threshold(src, dst, 100, 255, THRESH_BINARY);

// 大津算法,手动输入的阈值无效,三角算法类似,正确的阈值需要通过返回值获取。
threshold(src, dst, 100, 255, THRESH_BINARY | THRESH_OTSU);

Python

1
2
3
4
5
# retval 实际阈值
retval, dst = cv.threshold(src, 100, 255, cv.THRESH_BINARY)

# 大津算法
retval, dst = cv.threshold(src, 100, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)

自适应阈值

1
adaptiveThreshold(src, dst, maxValue, adaptiveMethod, thresholdType, C)
  1. adaptiveMethod:自适应阈值算法
    1. ADAPTIVE_THRESH_MEAN_C:附近区域减去恒定的平均C
    2. ADAPTIVE_THRESH_GAUSSIAN_C:领域值减去参数C的高斯加权和
  2. thresholdType:阈值类型,只允许是以下两个
    1. THRESH_BINARY:超过阈值是maxval,低于阈值是0
    2. THRESH_BINARY_INV:超过阈值是0,低于阈值是maxval
  3. blockSize:邻域大小,必须是奇数。
  4. C:参数C

C++

1
adaptiveThreshold(src, dst, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, 3, 5);

Python

1
2
3
4
dst = cv.adaptiveThreshold(src, 255, 
cv.ADAPTIVE_THRESH_GAUSSIAN_C,
cv.THRESH_BINARY,
3, 5)

图片卷积

自定义卷积核

1
filter2D(src, dst, ddepth, kernel)
  1. ddepth:目标图像的深度
  2. kernel:卷积核

官网给出的计算的公式:
$$
\texttt{dst} (x,y) = \sum _{ \stackrel{0\leq x’ < \texttt{kernel.cols},}{0\leq y’ < \texttt{kernel.rows}} } \texttt{kernel} (x’,y’)* \texttt{src} (x+x’- \texttt{anchor.x} ,y+y’- \texttt{anchor.y} )
$$
C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Mat dst;
// 1. 均值滤波
Mat means_kernel = (Mat_<float>(3, 3) << 1, 1, 1, 1, 1, 1, 1, 1, 1) / 9;
// 2. 高斯模糊
Mat gaussian_kernel = (Mat_<float>(3, 3) << 1, 2, 1, 2, 4, 2, 1, 2, 1) / 16;
// 3. Sobel 算子
// 3.1 水平梯度
Mat sobel_h_kernel = (Mat_<float>(3, 3) << -1, 0, 1, -2, 0, 2, -1, 0, 1);
// 3.2 垂直梯度
Mat sobel_v_kernel = (Mat_<float>(3, 3) << -1, -2, -1, 0, 0, 0, 1, 2, 1);
// 4. 拉普拉斯算子
// 4.1 普通型
Mat laplacian_normal_kernel =
(Mat_<float>(3, 3) << 0, 1, 0, 1, -4, 1, 0, 1, 0);
// 4.2 增强型
Mat laplacian_strong_kernel =
(Mat_<float>(3, 3) << 1, 1, 1, 1, -4, 1, 1, 1, 1);
// 5. 锐化滤波
Mat sharpen_kernel = (Mat_<float>(3, 3) << -1, -1, -1, -1, 9, -1, -1, -1, -1);

// 以均值滤波为例,其他都是一样的使用方式
filter2D(src, dst, -1, means_kernel);

Python

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
27
28
29
30
31
32
33
# 1. 均值滤波
means_kernel = np.float32(((1, 1, 1),
(1, 1, 1),
(1, 1, 1))) / 9
# 2. 高斯模糊
gaussian_kernel = np.float32(((1, 2, 1),
(2, 4, 2),
(1, 2, 1))) / 16
# 3. Sobel 算子
# 3.1 水平梯度
sobel_h_kernel = np.float32(((-1, 0, 1),
(-2, 0, 2),
(-1, 0, 1)))
# 3.2 垂直梯度
sobel_v_kernel = np.float32(((-1, -2, -1),
(0, 0, 0),
(1, 2, 1)))
# 4. 拉普拉斯算子
# 4.1 普通型
laplacian_normal_kernel = np.float32(((0, 1, 0),
(1, -4, 1),
(0, 1, 0)))
# 4.2 增强型
laplacian_strong_kernel = np.float32(((1, 1, 1),
(1, -4, 1),
(1, 1, 1)))
# 5. 锐化滤波
sharpen_kernel = np.float32(((-1, -1, -1),
(-1, 9, -1),
(-1, -1, -1)))

# 以均值滤波为例,其他都是一样的使用方式
dst = cv.filter2D(src, -1, means_kernel)

自定义卷积核比较灵活,只要定好卷积核,就可以使用该方法进行卷积。

均值滤波

1
blur(src, dst, ksize)
  1. ksize:卷积核尺寸
    1. C++Size类型
    2. Python:元组类型

C++

高斯模糊

1
GaussianBlur(src, dst, ksize, sigmaX, sigmaY)
  1. sigmaX:x轴上的高斯标准差
  2. sigmaY:y轴上的高斯标准差,如果为0,设置为等于sigmaY

**C++**:

1
2
Mat dst;
GaussianBlur(src, dst, Size(3, 3), 1, 1);

Python

1
dst = cv.GaussianBlur(src, (3, 3), 1, 1)

中值滤波

1
medianBlur(src, dst, ksize)

注意此处的 ksize 为int类型

C++

1
medianBlur(src, dst, 3);

Python

Sobel算子

1
Sobel(src, dst, ddepth, dx, dy, ksize=3)
  1. dx:沿x轴的阶数
  2. dy:沿y轴的阶数

C++

Python

1
2
3
4
5
# 沿 x 轴计算一阶sobel
dst = cv.Sobel(src, -1, 1, 0)

# 沿 y 轴计算一阶sobel
dst = cv.Sobel(src, -1, 0, 1)

Scharr滤波器

1
Scharr(src, dst, ddepth, dx, dy)

C++

1
2
3
4
5
// 沿 x 轴计算一阶 Scharr
Scharr(src, dst, -1, 1, 0);

// 沿 y 轴计算一阶 Scharr
Scharr(src, dst, -1, 0, 1);

Python

1
2
3
4
5
# 沿 x 轴计算一阶 Scharr
dst = cv.Scharr(src, -1, 1, 0)

# 沿 y 轴计算一阶 Scharr
dst = cv.Scharr(src, -1, 0, 1)

拉普拉斯算子

1
Laplacian(src, dst, ddepth, ksize=1)

C++

1
Laplacian(src, dst, -1);

Python

1
dst = cv.Laplacian(src, -1)

canny边缘检测算法

1
Canny(src, edges, threshold1, threshold2)
  1. edges:边缘图,单通道8位。
  2. threshold1:第一个阈值
  3. threshold2:第二个阈值

C++

1
Canny(src, dst, 50, 100);

Python

1
dst = cv.Canny(src, 50, 100)

双边滤波

1
bilateralFilter(src, dst, d, sigmaColor, sigmaSpace)
  1. d:滤波期间使用的每个像素邻域的直径。
  2. sigmaColor:在色彩空间中过滤的标准差
  3. sigmaSpace:在坐标空间中过滤的标准差

C++

1
bilateralFilter(src, dst, 3, 1, 1);

Python

1
dst = cv.bilateralFilter(src, 3, 1, 1)

霍夫变换

霍夫圆

使用霍夫变换在灰度图中查找圆

1
2
HoughCircles(src, circles, method, dp, minDist, 
param1=100, param2=100, minRadius=0, maxRadius=0)
  1. circles:找到的圆的输出向量
  2. method:检测方法
    1. 当前唯一实现的方法是HOUGH_GRADIENT
  3. dp:分辨率,累加器分辨率和图像分辨率的反比。
  4. minDist:检测到圆心的最小距离
  5. param1:传递给Canny边缘检测器的两个阈值中的更高的那个,更低的是它的一半
  6. param2:它是检测圆心的累加器阈值,越小,假圆可能越多。
  7. minRadius:最小圆半径
  8. maxRadius:最大圆半径

C++

1
2
3
Mat circles;
HoughCircles(src, circles, HOUGH_GRADIENT, 1, 100, 160, 50, 0, 1000);
// circles 中是检测到的圆的圆心(x, y)和半径

Python

1
circles = cv.HoughCircles(src, HOUGH_GRADIENT, 1, 100, 160, 50, 0, 1000)

霍夫直线变换–找直线

使用标准霍夫变换或者标准多尺度霍夫变换查找直线

1
2
HoughLines(src, lines, rho, theta, threshold, 
srn=0, stn=0, min_theta=0, max_theta=CV_PI)
  1. lines:检测出来的直线(里面的参数是rho和theta)
  2. rho:距离分辨率(以像素为单位)
  3. theta:角度分辨率(以弧度为单位)
  4. threshold:累加器阈值
  5. srn:它是距离分辨率rho的除数
  6. stn:它是角度分辨率theta的除数,如果两个都等于0,则使用标准霍夫变换。否则使用多尺度霍夫变换
  7. min_theta:最小角度:介于0max_theta之间
  8. max_theta:最大角度:介于min_thetaCV_PI之间

注意:输入图必须是二值图

C++

1
2
3
4
5
6
7
// 将 src 转化为二值图
Mat binary;
adaptiveThreshold(src, binary, 255, ADAPTIVE_THRESH_MEAN_C,
THRESH_BINARY_INV, 5, 1);

Mat lines;
HoughLines(binary, lines, 1, CV_PI / 180, 100);

Python

1
2
3
4
5
# 将 src 转化为二值图
binary = cv.adaptiveThreshold(
src, 255, cv.ADAPTIVE_THRESH_MEAN_C, cv.THRESH_BINARY_INV, 5, 1)

lines = cv.HoughLines(binary, 1, np.pi / 180, 100)

注意 lines 的参数分别对应的是一条直线的 rho 和 theta

霍夫直线变换–找线段

使用概率霍夫变换在二进制图像中查找线段

1
HoughLinesP(src, lines, rho, theta, theshold, minLineLength=0, maxLineGap=0)
  1. minLineLength:线段的最小长度。
  2. maxLineGap:连接该线上的点之间的最大允许间隙。

注意:输入图必须是二值图

C++

1
2
3
4
5
6
7
8
9
10
11
12
Mat binary, gray;
cvtColor(src, gray, COLOR_BGR2GRAY);
adaptiveThreshold(gray, binary, 255, ADAPTIVE_THRESH_MEAN_C,
THRESH_BINARY_INV, 5, 1);
imshow("binary", binary);
vector<Vec4i> lines;
HoughLinesP(binary, lines, 1, CV_PI / 180, 200, 150, 30);
for (auto line : lines) {
cv::line(src, Point(line[0], line[1]), Point(line[2], line[3]),
Scalar(0, 0, 255), 2);
}
imshow("src", src);

Python

1
2
3
4
5
6
7
8
9
10
gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
binary = cv.adaptiveThreshold(
gray, 255, cv.ADAPTIVE_THRESH_MEAN_C, cv.THRESH_BINARY_INV, 5, 1)
lines = cv.HoughLinesP(binary, 1, np.pi / 180, 200, 150, 30)

for line in lines:
x1, y1, x2, y2 = line[0]
cv.line(src, (x1, y1), (x2, y2), (0, 0, 255), 2)
cv.imshow("binary", binary)
cv.imshow("src", src)

注意:实际使用中,C++的HoughLineP 和Python的HoughLineP最终的结果有差异,原因未知。

查找轮廓和绘制轮廓

查找轮廓

1
findContours(image, contours, hierachy, mode, method)

参数描述:

  1. image:二值图
  2. contours:查找到的所有轮廓
  3. hierachy:层级关系(目前很少用到)
  4. mode轮廓的检索模式
    1. RETR_EXTERNAL:仅检索外部轮廓。
    2. RETR_LIST:不建立索引关系的情况下,检索所有轮廓。
    3. RETR_CCOMP:检索所有轮廓,分为两级层次结构。
    4. RETR_TREE:检索所有轮廓,重建嵌套的完整结构。
  5. method轮廓近似方法
    1. CHAIN_APPROX_NONE:绝对存储所有轮廓点
    2. CHAIN_APPROX_SIMPLE:压缩水平,垂直和对角线段,仅保留其端点。

绘制轮廓

1
drawContours(image, contours, contourIdx, color, thickness, lineType)
  1. contours:上面方法找到的所有轮廓点
  2. countourIdx:要绘制的索引,-1代表所有

C++

1
2
3
4
5
6
7
Mat gray, binary;
cvtColor(src, gray, COLOR_BGR2GRAY);
adaptiveThreshold(gray, binary, 255, ADAPTIVE_THRESH_GAUSSIAN_C,
THRESH_BINARY_INV, 255, 1);
vector<vector<Point>> contours;
findContours(binary, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
drawContours(src, contours, -1, Scalar(0, 0, 255), 1);

Python

1
2
3
4
5
6
7
8
9
10
gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)

binary = cv.adaptiveThreshold(
gray, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY_INV, 255, 1)

_, contours, hierarchy = cv.findContours(
binary, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
cv.drawContours(src, contours, -1, (0, 0, 255), 1)
cv.imshow("binary", binary)
cv.imshow("src", src)

注意findContours会返回三个值,分别是,图像,轮廓,层级。

膨胀和腐蚀

形态学变化是基于图像形状的一些简单操作。操作对象一般是二值图像,需要两个输入,一个是我们的原图,另一个是3x3的结构元素(内核),决定了膨胀操作的本质。常见的操作是图像的膨胀和腐蚀。以及他们的进阶操作注入Opening、Closing、Gradient等等。

获取结构元素

1
getStructuringElement(shape, ksize)
  1. shape结构元素的形状
    1. MORPH_RECT:矩形结构元素
    2. MORPH_CROSS:十字形结构元素
    3. MORPH_ELLIPSE:椭圆形结构元素

用法在下面

膨胀

用大值填充小值

1
dilate(src, dst, kernel)
  1. kernel:结构元素

C++

1
2
3
4
5
Mat dilate_later;
Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 5));
dilate(src, dilate_later, kernel);
imshow("src", src);
imshow("binary", dilate_later);

Python

1
2
3
4
kernel = cv.getStructuringElement(cv.MORPH_RECT, (5, 5))
dilate_later = cv.dilate(src, kernel)
cv.imshow("src", src)
cv.imshow("binary", dilate_later)

腐蚀

用小值填充大值

1
erode(src, dst, kernel)

C++

1
2
3
4
5
Mat erode_later;
Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 5));
erode(src, erode_later, kernel);
imshow("src", src);
imshow("binary", erode_later);

Python

1
2
3
4
kernel = cv.getStructuringElement(cv.MORPH_RECT, (5, 5))
erode_later = cv.erode(src, kernel)
cv.imshow("src", src)
cv.imshow("binary", erode_later)

高级形态转化

1
morphologyEx(src, dst, op, kernel)
  1. op形态转化方式
    1. MORPH_ERODE:腐蚀
    2. MORPH_DILATE:膨胀
    3. MORPH_OPEN:开操作,先腐蚀后膨胀
    4. MORPH_CLOSE:闭操作,先膨胀后腐蚀

C++

1
2
3
4
5
6
Mat src_later;
Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 5));
// 开操作,其他操作将 op 的值进行更换即可
morphologyEx(src, src_later, MORPH_OPEN, kernel);
imshow("src", src);
imshow("binary", src_later);

Python

1
2
3
4
5
kernel = getStructuringElement(cv.MORPH_RECT, Size(5, 5))
# 开操作,其他操作将 op 的值进行更换即可
src_later = cv.morphologyEx(src, cv.MORPH_OPEN, kernel)
cv.imshow("src", src)
cv.imshow("binary", src_later)

泛洪填充(漫水填充)

1
floodFill(image, mask,seedPoint, newVal, rect, loDiff=Scalar(), upDiff=Scalar(), flags)
  1. seedPoint:起始点
  2. newVal:新值
  3. rect:最小边界矩形,一般使用默认
  4. loDiff:最大较低色差
  5. upDiff:最大较高色差
  6. flags:操作标志
    1. FLOODFILL_FIXED_RANGE

$$
\texttt{src} (x’,y’)- \texttt{loDiff} \leq \texttt{src} (x,y) \leq \texttt{src} (x’,y’)+ \texttt{upDiff}
$$

C++

1
2
3
// {5, 5} 是一种简写方式,会自动生成 Point,{0, 0, 255}同理,会自动生成 Scalar
floodFill(src, {5, 5}, {0, 0, 255});
imshow("src", src);

Python

1
2
cv.floodFill(src, (5, 5), (0, 0, 255));
cv.imshow("src", src);

图像分水岭

1
watershed(image, markers)
  1. markders:标记,类型必须是CV_32S

C++

1
2
3
4
5
6
7
8
9
10
Mat markers(src.size(), CV_32S);
circle(markers, {src.size().width / 2, src.size().height / 2}, 2, {255},
-1);
circle(markers, {10, 10}, 2, {1}, -1);
watershed(src, markers);
std::cout << markers << std::endl;
Mat result;
markers.convertTo(result, CV_8UC3);
imshow("src", src);
imshow("result", result);

Python

1
2
3
4
5
6
7
8
height, width = src.shape[:2]
markers = np.zeros(src.shape[:2], np.int32)
cv.circle(markers, (width // 2, height//2), 2, (255), -1)
cv.circle(markers, (10, 10), 2, (1), -1)
cv.watershed(src, markers)
markers = markers.astype(np.uint8)
cv.imshow("src", src)
cv.imshow("binary", markers)

注意:标定点一般需要我们实际取获取。这里仅仅是使用固定点举例子罢了。

距离变换

1
distanceTransform(src, dst, distanceType, maskSize, dstType=CV_32F)
  1. src:8位单通道二进制图
  2. dst:输出具有计算出的距离图像。它是大小与src相同的8位或32位浮点单通道图像。
  3. distanceType距离类型,一般采用欧式距离就好了
    1. DIST_L1:distance = |x1-x2| + |y1-y2|
    2. DIST_L2:简单的欧式距离
    3. DIST_C:distance = max(|x1-x2|,|y1-y2|)
    4. DIST_L12:L1-L2 metric: distance = 2(sqrt(1+x*x/2) - 1))
    5. DIST_FAIR:distance = c^2(|x|/c-log(1+|x|/c)), c = 1.3998
    6. DIST_WELSCH:distance = c^2/2(1-exp(-(x/c)^2)), c = 2.9846
    7. DIST_HUBER:distance = |x|<c ? x^2/2 : c(|x|-c/2), c=1.345
  4. maskSize:距离变换蒙版大小,一般用3或5
  5. dstType:输出类型,一般采用默认值

C++

1
2
3
4
5
6
7
8
9
Mat gray, binary, dst;
cvtColor(src, gray, COLOR_BGR2GRAY);
adaptiveThreshold(gray, binary, 255, ADAPTIVE_THRESH_GAUSSIAN_C,
THRESH_BINARY_INV, 255, 1);
distanceTransform(binary, dst, DIST_L2, 3);
// 将数据归一化到0-1之间,显示效果比较好
normalize(dst, dst, 0, 1, NORM_MINMAX);
imshow("dst", dst);
imshow("binary", binary);

Python

1
2
3
4
5
6
7
8
gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
binary = cv.adaptiveThreshold(
gray, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY_INV, 255, 1)
dst = cv.distanceTransform(binary, cv.DIST_L2, 3)
# 将数据归一化到0-1之间,显示效果比较好
cv.normalize(dst, dst, 0, 1, cv.NORM_MINMAX)
cv.imshow("binary", binary)
cv.imshow("dst", dst)

评论