opencv-056-二值图像分析(直线拟合与极值点寻找)

知识点

对轮廓进行分析,除了可以对轮廓进行椭圆或者圆的拟合之外,还可以对轮廓点集进行直线拟合,直线拟合的算法有很多,最常见的就是最小二乘法,对于多约束线性方程,最小二乘可以找好直线方程的两个参数、实现直线拟合,OpenCV中直线拟合正是基于最小二乘法实现的。

OpenCV实现直线拟合的API如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void cv::fitLine(
InputArray points,
OutputArray line,
int distType,
double param,
double reps,
double aeps
)
points表示待拟合的输入点集合
line在二维拟合时候输出的是vec4f类型的数据,在三维拟合的时候输出是vec6f的vector
distType表示在拟合时候使用距离计算公式是哪一种,OpenCV支持如下六种方式:
DIST_L1 = 1
DIST_L2 = 2
DIST_L12 = 4
DIST_FAIR = 5
DIST_WELSCH = 6
DIST_HUBER = 7
param对模型拟合距离计算公式需要参数C,5~7 distType需要参数C
reps与aeps是指对拟合结果的精度要求,一般取0.01

代码(c++,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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

/*
* 二值图像分析(直线拟合与极值点寻找)
*/
int main() {
Mat src = imread("../images/twolines.png");
if (src.empty()) {
cout << "could not load image.." << endl;
}
imshow("input", src);

// 去噪声与二值化
Mat binary;
Canny(src, binary, 80, 160, 3, false);
imshow("binary", binary);

Mat k = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
dilate(binary, binary, k);

// 轮廓发现于绘制
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(binary, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point());
for (size_t t = 0; t < contours.size(); ++t) {
// 最大外接轮廓
Rect rect = boundingRect(contours[t]);
int m = max(rect.width, rect.height);
if (m < 30) continue;

// 直线拟合
Vec4f oneline;
fitLine(contours[t], oneline, DIST_L1, 0, 0.01, 0.01);
float dx = oneline[0];
float dy = oneline[1];
float x0 = oneline[2];
float y0 = oneline[3];

// 直线参数斜率k 和 截距b
float k = dy / dx;
float b = y0 - k * x0;

// 寻找轮廓极值点
int minx = 0, miny = 10000;
int maxx = 0, maxy = 0;
for (int i = 0; i < contours[t].size(); ++i) {
Point pt = contours[t][i];
if (miny > pt.y) {
miny = pt.y;
}
if (maxy < pt.y) {
maxy = pt.y;
}
maxx = (maxy - b) / k;
minx = (miny - b) / k;
line(src, Point(maxx, maxy), Point(minx, miny), Scalar(0,0,255), 2);
}
}

imshow("contours", src);

waitKey(0);
return 0;
}
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import cv2 as cv
import numpy as np


def canny_demo(image):
t = 80
canny_output = cv.Canny(image, t, t * 2)
cv.imwrite("D:/canny_output.png", canny_output)
return canny_output


src = cv.imread("D:/images/twolines.png")
cv.namedWindow("input", cv.WINDOW_AUTOSIZE)
cv.imshow("input", src)

binary = canny_demo(src)
k = np.ones((3, 3), dtype=np.uint8)
binary = cv.morphologyEx(binary, cv.MORPH_DILATE, k)
cv.imshow("binary", binary)

# 轮廓发现
out, contours, hierarchy = cv.findContours(binary, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)

# 直线拟合与极值点寻找
for c in range(len(contours)):
x, y, w, h = cv.boundingRect(contours[c])
m = max(w, h)
if m < 30:
continue
vx, vy, x0, y0 = cv.fitLine(contours[c], cv.DIST_L1, 0, 0.01, 0.01)
k = vy/vx
b = y0 - k*x0
maxx = 0
maxy = 0
miny = 100000
minx = 0
for pt in contours[c]:
px, py = pt[0]
if maxy < py:
maxy = py
if miny > py:
miny = py
maxx = (maxy - b) / k
minx = (miny - b) / k
cv.line(src, (np.int32(maxx), np.int32(maxy)),
(np.int32(minx), np.int32(miny)), (0, 0, 255), 2, 8, 0)


# 显示
cv.imshow("contours_analysis", src)
cv.imwrite("D:/contours_analysis.png", src)
cv.waitKey(0)
cv.destroyAllWindows()

结果

代码地址

github