0%

OpenCV(九)-轮廓

一、图像轮廓

具有相同==颜色==或==强度==的==连续点==的曲线

image-20241108164945322

  • 图像轮廓的作用
    • 可以用于图形分析
    • 物体的识别与检测
  • 注意点
    • 为了检测的准确性,需要先对图像进行二值化或Canny操作
    • 画轮廓时会修改输入的图像

轮廓查找的API

1
2
3
4
5
6
7
8
9
10
(function) def findContours(
image: MatLike,
mode: int,
method: int,
contours: Sequence[MatLike] | None = ...,
hierarchy: MatLike | None = ...,
offset: Point = ...
) -> tuple[Sequence[MatLike], MatLike]

# 两个返回值 contours和hierarchy
1
2
3
4
5
# mode
RETR_EXTERNAL = 0,表示只检测外轮廓
RETR_LIST=1,检测的轮廓不建立等级关系
RETR_CCOMP=2,每层最多两级
RETR_TREE=3,按树形存储轮廓
1
2
3
# method
CHAIN_APPROX_NONE,保存所有轮廓上的点
CHAIN_APPROX_SIMPLE,只保存角点

image-20241108165051241

image-20241108165106635

image-20241108165115197

image-20241108165132778

image-20241108165144100

二、查找轮廓

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import cv2
import numpy as np

# 读文件
img = cv2.imread('./contours1.jpeg')
print(img.shape)

# 转变为单通道
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 二值化
ret, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY)

# 轮廓查找
contours, hierarchy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

print(contours)
1
2
3
4
5
6
7
(array([[[  0,   0]],

[[ 0, 435]],

[[345, 435]],

[[345, 0]]], dtype=int32),)
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
# list
(array([[[ 36, 130]],

[[ 37, 129]],

[[310, 129]],

[[311, 130]],

[[311, 400]],

[[310, 401]],

[[ 37, 401]],

[[ 36, 400]]], dtype=int32), array([[[ 36, 35]],

[[ 37, 34]],

[[308, 34]],

[[309, 35]],

[[309, 39]],

[[308, 40]],

[[ 37, 40]],

[[ 36, 39]]], dtype=int32), array([[[ 0, 0]],

[[ 0, 435]],

[[345, 435]],

[[345, 0]]], dtype=int32))
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
# ccomp
(array([[[ 0, 0]],

[[ 0, 435]],

[[345, 435]],

[[345, 0]]], dtype=int32), array([[[ 36, 130]],

[[ 37, 129]],

[[310, 129]],

[[311, 130]],

[[311, 400]],

[[310, 401]],

[[ 37, 401]],

[[ 36, 400]]], dtype=int32), array([[[ 36, 35]],

[[ 37, 34]],

[[308, 34]],

[[309, 35]],

[[309, 39]],

[[308, 40]],

[[ 37, 40]],

[[ 36, 39]]], dtype=int32))
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
# tree
(array([[[ 0, 0]],

[[ 0, 435]],

[[345, 435]],

[[345, 0]]], dtype=int32), array([[[ 36, 130]],

[[ 37, 129]],

[[310, 129]],

[[311, 130]],

[[311, 400]],

[[310, 401]],

[[ 37, 401]],

[[ 36, 400]]], dtype=int32), array([[[ 36, 35]],

[[ 37, 34]],

[[308, 34]],

[[309, 35]],

[[309, 39]],

[[308, 40]],

[[ 37, 40]],

[[ 36, 39]]], dtype=int32))

三、绘制轮廓

1
2
3
4
5
6
7
8
9
10
11
12
(function)
def drawContours(
image: MatLike,
contours: Sequence[MatLike],
contourIdx: int, # -1表示绘制所有轮廓
color: Scalar, # 颜色(0,0,255)
thickness: int = ..., # 线宽,-1是全部填充
lineType: int = ...,
hierarchy: MatLike | None = ...,
maxLevel: int = ...,
offset: Point = ...
) -> MatLike: ...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import cv2
import numpy as np

# 读文件
img = cv2.imread('./contours1.jpeg')
print(img.shape)

# 转变为单通道
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 二值化
ret, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY)

# 轮廓查找
contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

# 绘制轮廓
cv2.drawContours(img, contours, -1, (0, 0, 255), 1)

print(contours)

cv2.imshow('img', img)
cv2.waitKey(0)

image-20241113153838976

四、轮廓的面积与周长

1
2
3
4
5
6
7
8
9
10
11
# 面积
(function) def contourArea(
contour: MatLike, # 轮廓
oriented: bool = ...
) -> float

# 周长
(function) def arcLength(
curve: MatLike, # 轮廓
closed: bool # 是否是闭合的轮廓
) -> float
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
import cv2
import numpy as np

# 读文件
img = cv2.imread('./contours1.jpeg')
print(img.shape)

# 转变为单通道
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 二值化
ret, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY)

# 轮廓查找
contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

# 绘制轮廓
cv2.drawContours(img, contours, 0, (0, 0, 255), 1)

# 计算面积
area = cv2.contourArea(contours[0])
print("area=%d"%(area))

# 计算周长
len = cv2.arcLength(contours[0], True)
print("lenght=%d"%(len))

# print(contours)

cv2.imshow('img', img)
cv2.waitKey(0)
1
2
area=150075
lenght=1560

五、多边形逼近与凸包

image-20241113154729831

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(function) def approxPolyDP(
curve: MatLike, # 轮廓
epsilon: float, # 精度
closed: bool, # 是否闭合
approxCurve: MatLike | None = ...
) -> MatLike

(function)
def convexHull(
points: MatLike, # 轮廓
hull: MatLike | None = ...,
clockwise: bool = ..., # 顺时针绘制
returnPoints: bool = ...
) -> MatLike: ...
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
import cv2
import numpy as np

def drawShape(src, points):
i = 0
while i < len(points):
if (i == len(points) - 1):
x,y = points[i][0]
x1,y1 = points[0][0]
cv2.line(src, (x,y), (x1,y1), (0, 0, 255), 3)
else:
x,y = points[i][0]
x1,y1 = points[i+1][0]
cv2.line(src, (x,y), (x1,y1), (0, 0, 255), 3)
i = i + 1

# 读文件
img = cv2.imread('./hand.png')
print(img.shape)

# 转变为单通道
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 二值化
ret, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY)

# 轮廓查找
contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

# 绘制轮廓
cv2.drawContours(img, contours, 0, (0, 0, 255), 1)

e = 20
approx = cv2.approxPolyDP(contours[0], e, True)

drawShape(img, approx)


hull = cv2.convexHull(contours[0])
drawShape(img, hull)

cv2.imshow('img', img)
cv2.waitKey(0)

image-20241113155929896

image-20241113160349632

六、外接矩形

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
import cv2
import numpy as np

# 读文件
img = cv2.imread('./hello.jpeg')
print(img.shape)

# 转变为单通道
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 二值化
ret, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY)

# 轮廓查找
contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

# 绘制轮廓
cv2.drawContours(img, contours, 0, (0, 0, 255), 1)

# 最小矩形
r = cv2.minAreaRect(contours[1]) # 得到的点是浮点型
box = cv2.boxPoints(r) # 得到的点是带角度的,将角度去除
box = np.int0(box) # 转换为int
cv2.drawContours(img, [box], 0, (0, 0, 255), 2)

# 最大矩形
x,y,w,h = cv2.boundingRect(contours[1])
cv2.rectangle(img, (x,y), (x+w, y+h), (255, 0, 0), 2)

cv2.imshow('img', img)
cv2.waitKey(0)

image-20241113161229355

七、项目总览(车辆统计)

image-20241113161744677

image-20241113161755405

image-20241113161800379

image-20241113161810280

八、视频加载(车辆统计)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import cv2
import numpy as np

cap = cv2.VideoCapture('video.mp4')

while True:
ret, frame = cap.read()

if(ret == True):
cv2.imshow('video', frame)

key = cv2.waitKey(1)
if (key == 27): # 27就是esc
break

cap.release()
cv2.destroyAllWindows()

九、去背景(车辆统计)

image-20241113164238392

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
import cv2
import numpy as np

cap = cv2.VideoCapture('video.mp4')

bgsubmog = cv2.bgsegm.createBackgroundSubtractorMOG()

while True:
ret, frame = cap.read()

if(ret == True):

# 灰度
cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 去噪(高斯)
blur = cv2.GaussianBlur(frame, (3,3), 5)
# 去背景
mask = bgsubmog.apply(blur)
cv2.imshow('video', mask)

key = cv2.waitKey(1)
if (key == 27): # 27就是esc
break

cap.release()
cv2.destroyAllWindows()

image-20241113165933266

十、形态学处理(车辆统计)

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
import cv2
import numpy as np

cap = cv2.VideoCapture('video.mp4')

bgsubmog = cv2.bgsegm.createBackgroundSubtractorMOG()

# 形态学kernel
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))

while True:
ret, frame = cap.read()

if(ret == True):

# 灰度
cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 去噪(高斯)
blur = cv2.GaussianBlur(frame, (3,3), 5)
# 去背景
mask = bgsubmog.apply(blur)

# 腐蚀,去掉图中小斑块
erode = cv2.erode(mask, kernel)

# 膨胀,还原放大
dilate = cv2.dilate(erode, kernel, iterations=3)

# 闭操作,去掉物体内部的小块
close = cv2.morphologyEx(dilate, cv2.MORPH_CLOSE, kernel)
close = cv2.morphologyEx(dilate, cv2.MORPH_CLOSE, kernel)

cnts, h = cv2.findContours(close, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

for (i, c) in enumerate(cnts):
(x,y,w,h) = cv2.boundingRect(c)
cv2.rectangle(frame, (x,y), (x+w,y+h), (0,0,255), 2)
cv2.imshow('video', frame)

key = cv2.waitKey(1)
if (key == 27): # 27就是esc
break

cap.release()
cv2.destroyAllWindows()

image-20241113171250130

十一、逻辑处理(车辆统计)

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import cv2
import numpy as np

min_w = 90
min_h = 90

# 检测线的高度
line_high = 600

#线的偏移
offset = 7

#统计车的数量
carno =0

#存放有效车辆的数组
cars = []

def center(x, y, w, h):
x1 = int(w/2)
y1 = int(h/2)
cx = x + x1
cy = y + y1

return cx, cy

cap = cv2.VideoCapture('video.mp4')

bgsubmog = cv2.bgsegm.createBackgroundSubtractorMOG()

# 形态学kernel
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))

while True:
ret, frame = cap.read()

if(ret == True):

# 灰度
cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 去噪(高斯)
blur = cv2.GaussianBlur(frame, (3,3), 5)
# 去背景
mask = bgsubmog.apply(blur)

# 腐蚀,去掉图中小斑块
erode = cv2.erode(mask, kernel)

# 膨胀,还原放大
dilate = cv2.dilate(erode, kernel, iterations=3)

# 闭操作,去掉物体内部的小块
close = cv2.morphologyEx(dilate, cv2.MORPH_CLOSE, kernel)
close = cv2.morphologyEx(dilate, cv2.MORPH_CLOSE, kernel)

cnts, h = cv2.findContours(close, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

cv2.line(frame, (10, line_high), (1200, line_high), (255, 255, 0), 3)
for (i, c) in enumerate(cnts):
(x,y,w,h) = cv2.boundingRect(c)

# 对车辆的宽高进行判断
# 以验证是否是有效的车辆
isValid = (w >= min_w ) and (h >= min_h)
if (not isValid):
continue

# 到这里都是有效的车
cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 0,255), 2)
cpoint = center(x, y, w, h)
cars.append(cpoint)
cv2.circle(frame, (cpoint), 5, (0, 0, 255), -1)

for (x,y) in cars:
if (y > line_high - offset) and (y < line_high + offset):
carno += 1
cars.remove((x,y))
print(carno)

cv2.imshow('video', frame)

key = cv2.waitKey(1)
if (key == 27): # 27就是esc
break

cap.release()
cv2.destroyAllWindows()

image-20241113172805642

十二、显示信息(车辆统计)

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import cv2
import numpy as np

min_w = 90
min_h = 90

#检测线的高度
line_high = 550

#线的偏移
offset = 7

#统计车的数量
carno =0

#存放有效车辆的数组
cars = []

def center(x, y, w, h):
x1 = int(w/2)
y1 = int(h/2)
cx = x + x1
cy = y + y1

return cx, cy

cap = cv2.VideoCapture('video.mp4')

bgsubmog = cv2.bgsegm.createBackgroundSubtractorMOG()

#形态学kernel
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))

while True:
ret, frame = cap.read()
if(ret == True):

#灰度
cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
#去噪(高斯)
blur = cv2.GaussianBlur(frame, (3,3), 5)
#去背影
mask = bgsubmog.apply(blur)

#腐蚀, 去掉图中小斑块
erode = cv2.erode(mask, kernel)

#膨胀, 还原放大
dilate = cv2.dilate(erode, kernel, iterations = 3)

#闭操作,去掉物体内部的小块
close = cv2.morphologyEx(dilate, cv2.MORPH_CLOSE, kernel)
close = cv2.morphologyEx(close, cv2.MORPH_CLOSE, kernel)

cnts, h = cv2.findContours(close, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

#画一条检测线
cv2.line(frame, (10, line_high), (1200, line_high), (255, 255, 0), 3)

for (i, c) in enumerate(cnts):
(x,y,w,h) = cv2.boundingRect(c)

#对车辆的宽高进行判断
#以验证是否是有效的车辆
isValid = ( w >= min_w ) and ( h >= min_h)
if( not isValid):
continue

#到这里都是有效的车
cv2.rectangle(frame, (x, y), (x+w, y+h), (0,0,255), 2)
cpoint = center(x, y, w, h)
cars.append(cpoint)
cv2.circle(frame, (cpoint), 5, (0,0,255), -1)

for (x, y) in cars:
if( (y > line_high - offset) and (y < line_high + offset ) ):
carno +=1
cars.remove((x , y ))
print(carno)

cv2.putText(frame, "Cars Count:" + str(carno), (500, 60), cv2.FONT_HERSHEY_SIMPLEX, 2, (255,0,0), 5)
cv2.imshow('video', frame)
#cv2.imshow('erode', close)

key = cv2.waitKey(1)
if(key == 27):
break

cap.release()
cv2.destroyAllWindows()

image-20241113173259189

-------------本文结束感谢您的阅读-------------