├── 2018
└── get_light_type.py
├── .gitattributes
├── __pycache__
├── kmeans.cpython-37.pyc
├── get_object.cpython-37.pyc
├── judge_color.cpython-37.pyc
└── judge_diretcton.cpython-37.pyc
├── .idea
├── vcs.xml
├── misc.xml
├── modules.xml
├── detet_traffic_light.iml
└── workspace.xml
├── README.md
├── main_all_in.py
├── .gitignore
├── kmeans.py
├── judge_color.py
├── judge_diretcton.py
├── main_test_ZED_with_yolov3.py
├── get_object.py
└── yzn_judge_light.py
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/__pycache__/kmeans.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yangzhaonan18/detect_traffic_light/HEAD/__pycache__/kmeans.cpython-37.pyc
--------------------------------------------------------------------------------
/__pycache__/get_object.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yangzhaonan18/detect_traffic_light/HEAD/__pycache__/get_object.cpython-37.pyc
--------------------------------------------------------------------------------
/__pycache__/judge_color.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yangzhaonan18/detect_traffic_light/HEAD/__pycache__/judge_color.cpython-37.pyc
--------------------------------------------------------------------------------
/__pycache__/judge_diretcton.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yangzhaonan18/detect_traffic_light/HEAD/__pycache__/judge_diretcton.cpython-37.pyc
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 红绿灯检测
2 |
3 | ## 1.环境
4 | - py37
5 | - opencv 3.4.2
6 | - win10 + pycharm
7 |
8 | ## 2.程序作用:判断红绿灯的颜色和方向。
9 |
10 | ### 具体说明如下:
11 |
12 | 0. 为了方便复制程序,已经将所有的程序放入这个py文件中了,程序测试正常。修改前请备份,谢谢。
13 | 1. 输入是的一个只包含红绿灯的图片(注意,不是摄像头读取的整个图),输出是红绿灯的颜色+方向+置信度。
14 | 2. 颜色用字符表示:红色是"R", 黄色是"Y", 绿色是"G", 未知或不能判断是"X"。
15 | 3. 方向用字符表示:圆饼灯是"C",左转箭头是"L",直行箭头是"D",右转箭头是"R",未知或不能判断是"X"。
16 | 4. 置信度用[0,1]之间的两位小数表示,数值越大,正确率越高。如0.86
17 |
18 | ## 3. 函数说明
19 |
20 | ## 4. demo
21 |
22 | https://www.bilibili.com/video/av53928579/
23 |
--------------------------------------------------------------------------------
/.idea/detet_traffic_light.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/main_all_in.py:
--------------------------------------------------------------------------------
1 | # -*- coding=utf-8 -*-
2 | # py37
3 | # opencv 3.4.1
4 | # win10 + pycharm
5 |
6 | import time
7 |
8 | import cv2
9 |
10 | from get_object import get_object
11 | from judge_diretcton import judge_direction
12 | from judge_color import judge_color
13 |
14 |
15 | # judge_light 是一个总函数,同时调用了三个函数:get_object,judge_direction,udge_color
16 | def judge_light(trafficLight):
17 | obj_bgr, cnt_max, cx, cy = get_object(trafficLight) # 通过颜色来获取红绿灯区域。(目前只考虑了一个红绿灯的情况)
18 | if cnt_max is None:
19 | return "X", "X", 0
20 | direction, direction_conf = judge_direction(obj_bgr, cnt_max) # 判断方向
21 | # print("\nResult : \ndirection, ratio = ", direction, direction_conf)
22 | color, color_conf = judge_color(trafficLight, obj_bgr, cx, cy) # 判断颜色
23 |
24 | # print("color, color_conf = ", color, color_conf)
25 | conf = round(direction_conf * color_conf, 2) # 将反向和颜色置信度的成绩作为最后的置信度。
26 |
27 | return color, direction, conf # 单个字符, 单个字符, 小于1的小数
28 |
29 |
30 | if __name__ == "__main__":
31 | trafficLight = cv2.imread('C:\\Users\\qcdz-003\\Pictures\\light\\001.jpg', cv2.IMREAD_COLOR)
32 | # cv2.imshow("trafficLight", trafficLight)
33 | start = time.time()
34 | color, direction, conf = judge_light(trafficLight) # 最终只使用这一行代码。
35 | end = time.time()
36 | print("judge_light() use time = ", end - start) # 0.00297
37 | print("color, direction, conf = ", color, direction, conf)
38 | print("the end !")
39 | cv2.waitKey()
40 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | .idea/
4 | *.py[cod]
5 | *$py.class
6 |
7 | # C extensions
8 | *.so
9 |
10 | # Distribution / packaging
11 | .Python
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | wheels/
24 | pip-wheel-metadata/
25 | share/python-wheels/
26 | *.egg-info/
27 | .installed.cfg
28 | *.egg
29 | MANIFEST
30 |
31 | # PyInstaller
32 | # Usually these files are written by a python script from a template
33 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
34 | *.manifest
35 | *.spec
36 |
37 | # Installer logs
38 | pip-log.txt
39 | pip-delete-this-directory.txt
40 |
41 | # Unit test / coverage reports
42 | htmlcov/
43 | .tox/
44 | .nox/
45 | .coverage
46 | .coverage.*
47 | .cache
48 | nosetests.xml
49 | coverage.xml
50 | *.cover
51 | .hypothesis/
52 | .pytest_cache/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | target/
76 |
77 | # Jupyter Notebook
78 | .ipynb_checkpoints
79 |
80 | # IPython
81 | profile_default/
82 | ipython_config.py
83 |
84 | # pyenv
85 | .python-version
86 |
87 | # pipenv
88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
91 | # install all needed dependencies.
92 | #Pipfile.lock
93 |
94 | # celery beat schedule file
95 | celerybeat-schedule
96 |
97 | # SageMath parsed files
98 | *.sage.py
99 |
100 | # Environments
101 | .env
102 | .venv
103 | env/
104 | venv/
105 | ENV/
106 | env.bak/
107 | venv.bak/
108 |
109 | # Spyder project settings
110 | .spyderproject
111 | .spyproject
112 |
113 | # Rope project settings
114 | .ropeproject
115 |
116 | # mkdocs documentation
117 | /site
118 |
119 | # mypy
120 | .mypy_cache/
121 | .dmypy.json
122 | dmypy.json
123 |
124 | # Pyre type checker
125 | .pyre/
126 |
--------------------------------------------------------------------------------
/kmeans.py:
--------------------------------------------------------------------------------
1 | # -*- coding=utf-8 -*-
2 | # K-means 图像分割 连续图像分割 (充分利用红绿灯的特点)
3 |
4 | import cv2
5 | import matplotlib.pyplot as plt
6 | import numpy as np
7 |
8 |
9 | def seg_kmeans_color(img, k=2):
10 |
11 | # 变换一下图像通道bgr->rgb,否则很别扭啊
12 | h, w, d = img.shape # (595, 1148, 3) # 0.47 聚类的时间和图像的面积大小成正比,即与图像的点数量成正比。
13 | b, g, r = cv2.split(img)
14 | img = cv2.merge([r, g, b])
15 |
16 | # 3个通道展平
17 | img_flat = img.reshape((img.shape[0] * img.shape[1], 3)) # 一个通道一列,共三列。每行都是一个点。
18 | img_flat = np.float32(img_flat)
19 | # print("len of img_flat = ", len(img_flat))
20 | # 迭代参数
21 | criteria = (cv2.TERM_CRITERIA_EPS + cv2.TermCriteria_MAX_ITER, 20, 0.5)
22 | flags = cv2.KMEANS_RANDOM_CENTERS
23 |
24 | # 聚类
25 |
26 | compactness, labels, centers = cv2.kmeans(img_flat, k, None, criteria, 10, flags)
27 | # print(" len of labels = ", len(labels))
28 | # print(labels) # 每个点的标签
29 | # print(centers) # 两类的中心,注意是点的值,不是点的坐标
30 | labels = np.squeeze(labels)
31 | img_output = labels.reshape((img.shape[0], img.shape[1]))
32 | L1 = max(int(img.shape[1] / 4), 2) # 当图像只有四个像素时, L1等于1 会出现-1:的异常异常,所以需要改为2,
33 |
34 | # 取每个四个角上的点
35 | boundary00 = np.squeeze(img_output[0:L1, 0: L1]. reshape((1, -1)))
36 | boundary01 = np.squeeze(img_output[0:L1, - L1:]. reshape((1, -1)))
37 | boundary10 = np.squeeze(img_output[- L1:, 0:L1]. reshape((1, -1)))
38 | boundary11 = np.squeeze(img_output[- L1:, - L1-1]. reshape((1, -1)))
39 |
40 | # 取中间一个正方形内的点
41 | inter = img_output[L1: -L1, L1: -L1]
42 | boundary_avg = np.average(np.concatenate((boundary00, boundary01, boundary10, boundary11), axis=0))
43 | inter_avg = np.average(inter)
44 | print("boundary_avg", boundary_avg)
45 | print("inter_avg", inter_avg)
46 |
47 | if k == 2 and boundary_avg > inter_avg: # 如果聚类使得边缘类是1,即亮的话,需要纠正颜色(所有的标签)。
48 | img_output = abs(img_output - 1) # 将边缘类(背景)的标签值设置为0,中间类(中间类)的值设置为1.
49 |
50 | img_output = np.array(img_output, dtype=np.uint8) # jiang
51 |
52 | return img_output
53 |
54 |
55 | if __name__ == '__main__':
56 |
57 | img = cv2.imread('C:\\Users\\qcdz-003\\Pictures\\light\\007.jpg', cv2.IMREAD_COLOR)
58 |
59 | img_output = seg_kmeans_color(img, k=2) # 2 channel picture
60 |
61 | # print(img_output.shape) # (244, 200)
62 | # 显示结果
63 |
64 | plt.subplot(121), plt.imshow(img), plt.title('input')
65 | plt.subplot(122), plt.imshow(img_output, 'gray'), plt.title('kmeans')
66 | plt.show()
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/judge_color.py:
--------------------------------------------------------------------------------
1 | from get_object import get_object
2 | import cv2
3 | import numpy as np
4 |
5 |
6 | def judge_light_color(obj_bgr):
7 | # 将图片转成HSV的
8 | obj_hsv = cv2.cvtColor(obj_bgr, cv2.COLOR_BGR2HSV)
9 | # 从H通道中提取出颜色的平均值
10 | H = obj_hsv[:, :, 0]
11 | H = np.squeeze(np.reshape(H, (1, -1)))
12 |
13 | # print(H) # 这里要注意红色H值得范围很特别,特别小和特别大都是红色,直接取平均值会出现判断错误的情况。
14 | H_value = np.array([])
15 | for i in H:
16 | if i > 0 and i < 124:
17 | H_value = np.append(H_value, i)
18 | elif i > 155: # 将大于155的红色的H值,都看成是1,方便处理
19 | H_value = np.append(H_value, 1.0)
20 | H_avg = np.average(H_value)
21 |
22 | # 根据这个值来判断是什么颜色, RYG
23 | if H_avg <= 18 or H_avg >= 156:
24 | color = "R"
25 | elif H_avg >= 18 and H_avg <= 34:
26 | color = "Y"
27 | elif H_avg >= 35 and H_avg <= 100:
28 | color = "G"
29 | else:
30 | color = "X"
31 |
32 | return color
33 |
34 |
35 | def judge_light_position(trafficLight, cx, cy):
36 | h, w, d = trafficLight.shape
37 | # print(trafficLight.shape)
38 | position = "0"
39 | if max(w / h, h / w) > 2: # 根据位置来判断颜色
40 | if h > w: # 竖着三个灯
41 | if cy < h / 3: # 灯的中心在上方
42 | position = "1"
43 | elif cy < 2 * h / 3: # 灯的中心在中间
44 | position = "2"
45 | else: # 灯的中心在下方
46 | position = "3"
47 |
48 | else: # 横着三个灯
49 | if cx < w / 3: # 灯的中心在左方
50 | position = "1"
51 | elif cx < 2 * w / 3: # 灯的中心在中间
52 | position = "2"
53 | else: # 灯的中心在右方
54 | position = "3"
55 | return position
56 |
57 |
58 | def judge_color(trafficLight, obj_bgr, cx, cy):
59 | position = judge_light_position(trafficLight, cx, cy) # 判断位置
60 | color = judge_light_color(obj_bgr) # 判断颜色
61 | if (position == "1" and color == "R") or (position == "2" and color == "Y") or (position == "3" and color == "G"):
62 | color_conf = 1.0
63 | elif position == "2" and color == "R":
64 | color = "Y" # 如何在中间就纠正为黄色
65 | color_conf = 0.9
66 | elif position == "1" and color == "Y":
67 | color = "R" # 如何在中间就纠正为黄色
68 | color_conf = 0.9
69 | else:
70 | color_conf = 0.8
71 | return color, color_conf
72 |
73 |
74 | if __name__ == '__main__':
75 | trafficLight = cv2.imread('C:\\Users\\qcdz-003\\Pictures\\light\\023.jpg', cv2.IMREAD_COLOR)
76 | obj_bgr, cnt_max, cx, cy = get_object(trafficLight)
77 |
78 | color, color_conf = judge_color(trafficLight, obj_bgr, cx, cy) # 一行代码
79 |
80 | print("color, color_conf = ", color, color_conf)
81 | print("The end")
82 |
--------------------------------------------------------------------------------
/judge_diretcton.py:
--------------------------------------------------------------------------------
1 | # -*- conding=utf-8 -*-
2 | # py37
3 | # 判断红绿灯的形状和方向
4 |
5 | from get_object import get_object
6 | import cv2
7 | import numpy as np
8 |
9 |
10 | def cal_circle_xy(frame, x, y, radius):
11 | x1 = x - radius if x - radius > 0 else 0
12 | x2 = x + radius if x + radius < frame.shape[1] else frame.shape[1] # cv里面横坐标是x 是shape[1]
13 | y1 = y - radius if y - radius > 0 else 0
14 | y2 = y + radius if y + radius < frame.shape[0] else frame.shape[0] # cv里面纵坐标是y 是shape[0]
15 | return int(x1), int(x2), int(y1), int(y2)
16 |
17 |
18 | def cal_point(SomeBinary, x, y, radius): # 返回最大方向的编号int
19 |
20 | x = int(x)
21 | y = int(y)
22 | x1, x2, y1, y2 = cal_circle_xy(SomeBinary, x, y, radius)
23 | S00 = SomeBinary[y1:y, x1:x] # 计算面积时,使用二值图,左上
24 | S01 = SomeBinary[y1:y, x:x2] # 右上
25 | S10 = SomeBinary[y:y2, x1:x] # 左下
26 | S11 = SomeBinary[y:y2, x:x2] # 右下
27 |
28 | SS00 = np.sum(S00)
29 | SS01 = np.sum(S01)
30 | SS10 = np.sum(S10)
31 | SS11 = np.sum(S11)
32 |
33 | value = [SS00, SS01, SS10, SS11]
34 | value.sort(reverse=True) # 将面积大的放在前面
35 |
36 | print("\nSS00, SS01 , SS10, SS11 = ", SS00, SS01, SS10, SS11)
37 | if SS00 in value[0:2] and SS10 in value[0:2]:
38 | return "R" # right
39 | elif SS01 in value[0:2] and SS11 in value[0:2]: # 箭头右侧需要补齐的东西多
40 | return "L" # left
41 | elif SS10 in value[0:2] and SS11 in value[0:2]:
42 | return "D" # direct
43 | else:
44 | return "X" # circle
45 |
46 |
47 | def judge_direction(obj_bgr, cnt_max):
48 |
49 | cnt = np.array(cnt_max)
50 | ((x, y), radius) = cv2.minEnclosingCircle(cnt) # 确定面积最大的轮廓的外接圆 返回圆心坐标和半径
51 | if radius < 3:
52 | print("\nminEnclosingCircle radius = ", radius, "< 5 , so NO direction !! ", )
53 | return "X", 0.0
54 |
55 | area = cv2.contourArea(cnt) # 轮廓面积
56 | hull = cv2.convexHull(cnt) # 计算出凸包形状(计算边界点)
57 | hull_area = cv2.contourArea(hull) # 计算凸包面积
58 | solidity = round(float(area) / hull_area, 2) # 自己的面积 / 凸包面积 凸度
59 | circularity = round(hull_area / (np.pi * pow(radius, 2)), 2) # 自己的面积/外接圆的面积
60 |
61 | print("solidity = ", solidity)
62 | print("circularity = ", circularity)
63 |
64 | direction = "X" # X 表示是未知方向,或者不能正确判断
65 | ratio = 0
66 | threshold01 = 0.9 # solidity 远大越可能是圆
67 | threshold03 = 0.5 # solidity * circularity 越小越可能是箭头
68 |
69 | if solidity > threshold01 and circularity > 0.5: # 当solidity度很大,且圆形度不是很小时 (考虑椭圆的情况),直接判断为圆
70 | direction = "C" # 判断 为圆形灯
71 | ratio = solidity # 将solidity度作为 圆灯的概率返回
72 | elif solidity > threshold01 and circularity <= 0.5: # 凸度很大,但圆形度很小时,说明不是圆
73 | direction = "X"
74 | ratio = 0.0
75 | else: # 当solidity度处于中间值时,判断为箭头,并计算概率
76 | # (规定圆形度与凸度成绩小于0.7时,概率为1。大于0.7时,概率小于1)
77 | cnts_ColorThings = cv2.drawContours(obj_bgr.copy(), [cnt], -1, (255, 255, 255), -1)
78 | hull_ColorThings = cv2.drawContours(obj_bgr.copy(), [hull], -1, (255, 255, 255), -1)
79 | BinThings = ~cnts_ColorThings & hull_ColorThings & ~obj_bgr # 找到凸包与原图之间的差
80 |
81 | # cv2.imshow("obj_bgr", obj_bgr)
82 | # cv2.imshow("cnts_ColorThings = ", cnts_ColorThings)
83 | # cv2.imshow("BinThings = ", BinThings)
84 | direction = cal_point(BinThings, int(x), int(y), radius) # 判断方向(圆心和半径)根据凸包与原图之间的差,计算箭头的方向
85 | x = solidity * circularity
86 | print("solidity * circularity = ", x)
87 | if x < threshold03: # 乘积小于这个值,判断为箭头,概率为1。
88 | ratio = 1
89 | else:
90 | ratio = (x - 1) / (threshold03 - 1)
91 | # 当圆形度特别小时,判断为处于异常,输出X 0
92 | return direction, ratio
93 |
94 |
95 | if __name__ == '__main__':
96 | trafficLight = cv2.imread('C:\\Users\\qcdz-003\\Pictures\\light\\027.jpg', cv2.IMREAD_COLOR)
97 | obj_bgr, cnt_max = get_object(trafficLight)
98 | direction = judge_direction(obj_bgr, cnt_max)
99 |
100 | # cv2.imshow("trafficLight", trafficLight)
101 | # cv2.imshow("obj_bgr", obj_bgr)
102 |
103 | # print("direction", direction)
104 | cv2.waitKey()
105 | print("the end")
106 |
--------------------------------------------------------------------------------
/main_test_ZED_with_yolov3.py:
--------------------------------------------------------------------------------
1 | # -*- coding=utf-8 -*-
2 | # py37
3 |
4 | from detect import *
5 | from get_light_type import get_light_type
6 | import cv2
7 |
8 | from yzn_judge_light import judge_light
9 |
10 |
11 | if __name__ == "__main__":
12 | parser = argparse.ArgumentParser()
13 | # parser.add_argument("--image_folder", type=str, default="data/samples", help="path to dataset")
14 | parser.add_argument("--model_def", type=str, default="config/yolov3.cfg", help="path to model definition file")
15 | parser.add_argument("--weights_path", type=str, default="weights/yolov3.weights", help="path to weights file")
16 | parser.add_argument("--class_path", type=str, default="data/coco.names", help="path to class label file")
17 | parser.add_argument("--conf_thres", type=float, default=0.8, help="object confidence threshold")
18 | parser.add_argument("--nms_thres", type=float, default=0.4, help="iou thresshold for non-maximum suppression")
19 | # parser.add_argument("--batch_size", type=int, default=1, help="size of the batches")
20 | # parser.add_argument("--n_cpu", type=int, default=0, help="number of cpu threads to use during batch generation")
21 | parser.add_argument("--img_size", type=int, default=416, help="size of each image dimension")
22 | # parser.add_argument("--checkpoint_model", type=str, help="path to checkpoint model")
23 | opt = parser.parse_args()
24 | # print(opt)
25 |
26 | device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
27 |
28 | os.makedirs("output", exist_ok=True)
29 |
30 | # Set up model
31 | model = Darknet(opt.model_def, img_size=opt.img_size).to(device)
32 |
33 | if opt.weights_path.endswith(".weights"):
34 | # Load darknet weights
35 | model.load_darknet_weights(opt.weights_path)
36 | else:
37 | # Load checkpoint weights
38 | model.load_state_dict(torch.load(opt.weights_path))
39 |
40 | model.eval() # Set in evaluation mode # 设置为评估模式
41 |
42 | # dataloader = DataLoader(
43 | # ImageFolder(opt.image_folder, img_size=opt.img_size),
44 | # batch_size=opt.batch_size,
45 | # shuffle=False,
46 | # num_workers=opt.n_cpu,
47 | # )
48 |
49 | classes = load_classes(opt.class_path) # Extracts class labels from file
50 |
51 | Tensor = torch.cuda.FloatTensor if torch.cuda.is_available() else torch.FloatTensor
52 |
53 | imgs = [] # Stores image paths
54 | img_detections = [] # Stores detections for each image index
55 |
56 | # detect object from ZED carmer
57 |
58 | capture = cv2.VideoCapture(0)
59 | batch_i = 0
60 | while(True):
61 | batch_i += 1
62 | # 获取一帧
63 | ret, frame = capture.read()
64 | h, w, d = frame.shape
65 | frame = frame[0:416, 0:416] # 双目摄像头只显示其中的一半
66 | # frame = frame[0:h, 0:int(w / 2)] # 双目摄像头只显示其中的一半
67 |
68 | transform2 = transforms.Compose([transforms.ToTensor(), ])
69 | input_imgs = transform2(frame)
70 | # input_imgs = torch.FloatTensor(frame)
71 | print("\n", "##" * 66)
72 | print("Performing object detection:")
73 | prev_time = time.time()
74 | # for batch_i, (img_paths, input_imgs) in enumerate(dataloader): # 遍历一个 bach_size 中的每一张图片
75 | # Configure input
76 | # input_imgs = Variable(input_imgs.type(Tensor))
77 | input_imgs = Variable(input_imgs.type(Tensor).unsqueeze(0))
78 |
79 | # Get detections
80 | with torch.no_grad(): # ????????????????????
81 | detections = model(input_imgs) # 预测一张图片的检测结果
82 | detections = non_max_suppression(detections, opt.conf_thres, opt.nms_thres) # 非极大抑制(一张图片上的)
83 |
84 | # Log progress
85 | current_time = time.time()
86 | inference_time = datetime.timedelta(seconds=current_time - prev_time)
87 | prev_time = current_time
88 | print("\t+ Batch %d, Inference Time: %s" % (batch_i, inference_time))
89 |
90 | # Draw bounding boxes and labels of detections
91 | frame_copy = frame.copy()
92 | if detections[0] is not None:
93 | # Rescale boxes to original image
94 | # detections = rescale_boxes(detections, opt.img_size, frame.shape[:2])
95 | # unique_labels = detections[:, -1].cpu().unique()
96 | # n_cls_preds = len(unique_labels)
97 | print("Have detect object !!")
98 | for detection in detections:
99 | for x1, y1, x2, y2, conf, cls_conf, cls_pred in detection: # 只输出检测到红绿灯的情况。
100 | if cls_pred == 9:
101 | print("This is a traffic light")
102 | trafficLight = frame[int(y1): int(y2), int(x1):int(x2)]
103 | # trafficLight = frame[int(y1) + 2: int(y2) - 2, int(x1) + 2:int(x2)-2]
104 |
105 | # type = get_light_type(trafficLight_05) # 去年的方法
106 | color, direction, conf = judge_light(trafficLight) # 今年的新方法,获取红绿灯区域。(目前只考虑了一个红绿灯的情况)
107 |
108 | frame_copy = cv2.rectangle(frame_copy, (x1, y1), (x2, y2), (0, 255, 0), 2)
109 | font = cv2.FONT_HERSHEY_SIMPLEX
110 |
111 | # frame_copy = cv2.putText(frame_copy, classes[int(cls_pred)], (x1, y1), font, 1.2, (0, 0, 255), 2)
112 |
113 | frame_copy = cv2.putText(frame_copy, color + direction + "+" + str(conf), (x1, y1), font, 0.6, (0, 0, 255), 2)
114 |
115 | else:
116 | print("No object or cant detect")
117 | cv2.imshow('frame_copy', frame_copy)
118 | if cv2.waitKey(1) == ord('q'):
119 | break
120 |
121 |
--------------------------------------------------------------------------------
/get_object.py:
--------------------------------------------------------------------------------
1 | import cv2
2 | import numpy as np
3 | import time
4 | import matplotlib.pyplot as plt
5 | from kmeans import seg_kmeans_color
6 |
7 |
8 | def find_contours_max(img_bin_color):
9 | # 找最大轮廓的正矩形
10 | gray = cv2.cvtColor(img_bin_color, cv2.COLOR_BGR2GRAY) # 转成灰色图像
11 | ret, BinThings = cv2.threshold(gray, 1, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU) # 灰色图像二值化(变黑白图像)
12 | _, contours, hierarchy = cv2.findContours(BinThings, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
13 |
14 | # for i in range(len(contours) - 1): # 绘制每一个轮廓
15 | # cv2.drawContours(frame_copy, [contours[i+1]], -1, (0, 0, 255), 2)
16 |
17 | # if len(contours) > 0:
18 | # cnt_max = max(contours, key=cv2.contourArea) # 找到面积最大的轮廓
19 | # color_aera = cv2.contourArea(cnt_max)
20 |
21 | if len(contours) > 0:
22 | cnt_max = max(contours, key=cv2.contourArea) # 找到面积最大的轮廓
23 | # cv2.drawContours(img_bgr, [cnt_max], -1, (0, 255, 255), 2)
24 | # # ((x, y), radius) = cv2.minEnclosingCircle(np.array(cnt_max))
25 | # cv2.circle(img_bgr, (int(x), int(y)), int(radius), (0, 0, 255), 2)
26 | # print(((x, y), radius))
27 |
28 | # print("cnt_max", cnt_max)
29 | return cnt_max
30 | return None
31 |
32 |
33 | def crop_max_region(frame_bgr, img_bin_color): # frame_bgr, img_bgr
34 | """
35 | :param frame_bgr: 用于裁剪的原始图片
36 | :param img_bin_color: # 用于腐蚀,寻找颜色区域的
37 | :return: 在原图上裁剪下来的区域。
38 | """
39 |
40 | cnt_max = find_contours_max(img_bin_color)
41 | if cnt_max is not None:
42 | x, y, w, h = cv2.boundingRect(np.array(cnt_max)) # 正外界矩形
43 | # print("x, y, w, h = ", x, y, w, h)
44 | # cv2.rectangle(img_bgr, (x, y), (x + w, y + h), (0, 255, 0), 2)
45 | frame_crop = frame_bgr[y: y + h, x: x + w]
46 |
47 | # cv2.imshow("frame_crop6", frame_crop)
48 | cnt_max = find_contours_max(frame_crop)
49 |
50 | # 将原始图像翻转并判断形状和方向
51 |
52 | return frame_crop, cnt_max, x, y, w, h
53 | return None, None, None, None, None, None
54 |
55 | def get_color_region(frame_bgr, frame_hsv, color="RYG"):
56 | # cv2.imshow("frame_bgr", frame_bgr)
57 | # colorLower = np.array([0, 43, 46], dtype=np.uint8) # 非黑白灰的
58 | # colorUpper = np.array([180, 255, 255], dtype=np.uint8)
59 | try:
60 | redLower01 = np.array([0, 43, 46], dtype=np.uint8) # 部分红 和橙黄绿
61 | redUpper01 = np.array([124, 255, 255], dtype=np.uint8)
62 | red_mask02_and_othersLower = np.array([156, 43, 46], dtype=np.uint8) # 部分红
63 | red_mask02_and_othersUpper = np.array([180, 255, 255], dtype=np.uint8)
64 |
65 | red_mask01 = cv2.inRange(frame_hsv, redLower01, redUpper01)
66 | red_mask02_and_others = cv2.inRange(frame_hsv, red_mask02_and_othersLower, red_mask02_and_othersUpper)
67 | mask = None
68 | if color == "RYG":
69 | mask = red_mask01 + red_mask02_and_others
70 | # print("mask.shape = ", mask.shape) # (26, 23)
71 |
72 | BinColors = cv2.bitwise_and(frame_bgr, frame_bgr, mask=mask)
73 | # cv2.imshow("BinColors", BinColors)
74 | # BinColors = cv2.GaussianBlur(BinColors, (5, 5), 0) # 彩色图时 高斯消除噪音 # 很耗时,不建议使用
75 |
76 | # dst = cv2.erode(dst, None, iterations=2) # 腐蚀操作
77 | kernel = np.ones((5, 5), np.uint8)
78 | img_bin_color = cv2.morphologyEx(BinColors, cv2.MORPH_OPEN, kernel) # 开运算
79 | # kernel = np.ones((10, 10), np.uint8)
80 | img_bin_color = cv2.morphologyEx(img_bin_color, cv2.MORPH_CLOSE, kernel) # 闭运算
81 |
82 | # cv2.imshow("mask ", mask) # 这是一个二值图
83 | # cv2.imshow("crop_frame ", crop_frame) # 这是一个二值图
84 |
85 | # cv2.imshow("BinColors", BinColors) # 原图
86 | # cv2.imshow("crop_frame", crop_frame) # 原图
87 | # cv2.imshow("BinColors", BinColors) # 其中的彩色区域
88 | # cv2.imshow("img_bin_color ", img_bin_color)
89 |
90 | # cv2.waitKey(0)
91 | return crop_max_region(frame_bgr, img_bin_color)
92 | except:
93 | return None, None, None, None, None, None
94 |
95 |
96 | def get_object(frame_bgr):
97 | frame_hsv = cv2.cvtColor(frame_bgr, cv2.COLOR_BGR2HSV)
98 | frame_crop_bgr, _, x1, y1, w1, h1 = get_color_region(frame_bgr, frame_hsv, color="RYG") # 获取彩色区域(裁剪一个矩形)
99 | if frame_crop_bgr is None:
100 | print("no object in get_object")
101 | return None, None, None, None
102 | frame_kmeans = seg_kmeans_color(frame_crop_bgr) # k menan k=2
103 |
104 | # cv2.imshow("frame_crop_bgr", frame_crop_bgr)
105 | # cv2.imshow("frame_kmeans", frame_kmeans)
106 |
107 | frame_new_bgr = cv2.bitwise_and(frame_crop_bgr, frame_crop_bgr, mask=frame_kmeans) # 使用Kmean之后的结果比直接使用颜色阈值分割更可靠。
108 |
109 | kernel = np.ones((3, 3), np.uint8)
110 | frame_new_bgr = cv2.morphologyEx(frame_new_bgr, cv2.MORPH_OPEN, kernel)
111 | frame_max_bgr, cnt_max, x2, y2, w2, h2 = crop_max_region(frame_new_bgr, frame_new_bgr) # Kmeans之后的中间颜色区域提取。
112 |
113 | # light = frame_bgr[y1 + y2: y1 + y2 + h2, x1 + x2: x1 + x2 + w2]
114 | # cv2.imshow("light =", light)
115 |
116 | cx = int(x1 + x2 + w2 / 2) # 灯的中心点坐标
117 | cy = int(y1 + y2 + h2 / 2)
118 |
119 | # # 显示结果
120 | # b, g, r = cv2.split(frame_bgr)
121 | # frame_rgb = cv2.merge([r, g, b])
122 | # frame_crop_rgb = frame_crop_bgr[:, :, (2, 1, 0)]
123 | # frame_new_rgb = frame_new_bgr[:, :, (2, 1, 0)]
124 | # frame_max_rgb = frame_max_bgr[:, :, (2, 1, 0)]
125 | #
126 | # # plt 显示图片RGB的顺序
127 | # # plt.subplot(161), plt.imshow(frame_rgb), plt.title('rgb') # plt是RGB的顺序,CV是BGR的顺序。
128 | # # plt.subplot(162), plt.imshow(frame_crop_rgb), plt.title('crop_rgb') # plt是RGB的顺序,CV是BGR的顺序。
129 | # # plt.subplot(163), plt.imshow(frame_kmeans, 'gray'), plt.title('kmeans')
130 | # # plt.subplot(164), plt.imshow(frame_new_rgb, 'gray'), plt.title('new_rgb')
131 | # # plt.subplot(165), plt.imshow(frame_max_rgb), plt.title('max_rgb')
132 | # # plt.show()
133 |
134 | # cv显示图片 BGR的通道顺序
135 | # cv2.imshow("frame", frame) # 原图
136 | # cv2.imshow("crop_frame", frame_crop) # 原图
137 | # cv2.waitKey(0)
138 | # frame_crop_gray = cv2.cvtColor(frame_crop_bgr, cv2.COLOR_BGR2GRAY)
139 | # cv2.imshow("frame_crop_gray", frame_crop_gray)
140 | # cv2.waitKey()
141 | return frame_max_bgr, cnt_max, cx, cy
142 |
143 |
144 | if __name__ == '__main__':
145 | frame_bgr = cv2.imread('C:\\Users\\qcdz-003\\Pictures\\light\\023.jpg', cv2.IMREAD_COLOR)
146 |
147 | frame_max_bgr, cnt = get_object(frame_bgr)
148 | # cv2.imshow("asdf", frame_max_bgr)
149 | cv2.waitKey()
150 |
--------------------------------------------------------------------------------
/2018/get_light_type.py:
--------------------------------------------------------------------------------
1 | #! -*- coding=utf-8 -*-
2 |
3 |
4 | import cv2
5 | import numpy as np
6 |
7 |
8 | # judge_color
9 |
10 | def find_mask(frame, color):
11 | blackLower01 = np.array([0, 0, 0], dtype=np.uint8) # 黑的阈值 标准H:0:180 S:0:255 V:0:46:220
12 | blackUpper01 = np.array([180, 255, 90], dtype=np.uint8)
13 | blackLower02 = np.array([0, 0, 46], dtype=np.uint8) # 灰的阈值 标准H:0:180 S:0:43 V:0:46:220
14 | blackUpper02 = np.array([180, 43, 45], dtype=np.uint8) # 灰色基本没用
15 |
16 | redLower01 = np.array([0, 80, 80], dtype=np.uint8) # 红色的阈值 标准H:0-10 and 160-179 S:43:255 V:46:255
17 | redUpper01 = np.array([10, 255, 255], dtype=np.uint8)
18 | redLower02 = np.array([156, 80, 80], dtype=np.uint8) # 125 to 156
19 | redUpper02 = np.array([180, 255, 255], dtype=np.uint8)
20 |
21 | greenLower = np.array([40, 80, 80], dtype=np.uint8) # 绿色的阈值 标准H:35:77 S:43:255 V:46:255
22 | greenUpper = np.array([95, 255, 255], dtype=np.uint8) # V 60 调整到了150
23 |
24 | blueLower = np.array([105, 120, 46], dtype=np.uint8) # 蓝H:100:124 紫色H:125:155
25 | blueUpper = np.array([130, 255, 255], dtype=np.uint8)
26 |
27 | yellowLower = np.array([24, 80, 80], dtype=np.uint8) # 黄色的阈值 标准H:26:34 S:43:255 V:46:255
28 | yellowUpper = np.array([36, 255, 255], dtype=np.uint8) # 有的图 黄色变成红色的了
29 | try:
30 | hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
31 | red1_mask = cv2.inRange(hsv, redLower01, redUpper01) # 根据阈值构建掩膜, 红色的两个区域
32 | red2_mask = cv2.inRange(hsv, redLower02, redUpper02)
33 | red_mask = red1_mask + red2_mask
34 |
35 | black01_mask = cv2.inRange(hsv, blackLower01, blackUpper01) # 根据阈值构建掩膜,黑色的区域
36 | black02_mask = cv2.inRange(hsv, blackLower02, blackUpper02) # 根据阈值构建掩膜,黑色的区域
37 | black_mask = black01_mask + black02_mask
38 |
39 | yellow_mask = cv2.inRange(hsv, yellowLower, yellowUpper) # 根据阈值构建掩膜, 黄色的区域
40 | green_mask = cv2.inRange(hsv, greenLower, greenUpper) # 根据阈值构建掩膜, 绿色的区域
41 |
42 | blue_mask = cv2.inRange(hsv, blueLower, blueUpper)
43 | if color == "black":
44 | mask = black_mask
45 | elif color == "yellow":
46 | mask = yellow_mask
47 | elif color == "red":
48 | mask = red_mask
49 | elif color == "green":
50 | mask = green_mask
51 | elif color == "blue":
52 | mask = blue_mask
53 | elif color == "red+blue":
54 | mask = red_mask + blue_mask
55 | elif color == "yellow+green":
56 | mask = yellow_mask + green_mask
57 | elif color == "red+yellow+green":
58 | mask = red_mask + yellow_mask + green_mask
59 | else:
60 | mask = None
61 | return mask
62 |
63 | except:
64 | return None
65 |
66 |
67 | def find_color_aera(Crop_frame, color):
68 |
69 | mask = find_mask(Crop_frame, color)
70 | # mask = cv2.dilate(mask, None, iterations=1) # 膨胀操作,其实先腐蚀再膨胀的效果是开运算,去除噪点
71 | # mask = cv2.erode(mask, None, iterations=num) # 腐蚀操作
72 | BinColors = cv2.bitwise_and(Crop_frame, Crop_frame, mask=mask) # 提取感兴趣的颜色区域 背景黑色+彩色的图像
73 |
74 | dst = cv2.GaussianBlur(BinColors, (3, 3), 0) # 彩色图时 高斯消除噪音
75 | gray = cv2.cvtColor(dst, cv2.COLOR_BGR2GRAY) # 转成灰色图像
76 |
77 | ret, BinThings = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU) # 灰色图像二值化(变黑白图像)
78 |
79 | # cv2.imshow("BinThings", BinThings)
80 | # cv2.waitKey(0)
81 |
82 | _, contours, hierarchy = cv2.findContours(BinThings, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE) # 边界是封闭的 del first BinThings,
83 | if len(contours) > 0:
84 | cnt_max = max(contours, key=cv2.contourArea) # 找到面积最大的轮廓
85 | color_aera = cv2.contourArea(cnt_max)
86 | # cv2.imshow("BinThings", BinColors)
87 | # cv2.waitKey(0)
88 | else:
89 | color_aera = 0
90 | return color_aera
91 |
92 |
93 | def judge_color(Crop_frame):
94 | yellow_area = find_color_aera(Crop_frame, "yellow")
95 | red_area = find_color_aera(Crop_frame, "red")
96 | green_area = find_color_aera(Crop_frame, "green")
97 | print("\nyellow_area, red_area, green_area = ", yellow_area, red_area, green_area)
98 | ratio = 0.05 # 调参
99 | if max(yellow_area, red_area, green_area) > ratio * pow(min(Crop_frame.shape[0], Crop_frame.shape[1]), 2): # 提取出的图像的面积不能太小
100 |
101 | if yellow_area > red_area and yellow_area > green_area:
102 | return "yellow"
103 | if red_area > yellow_area and red_area > green_area:
104 | return "red"
105 | if green_area > yellow_area and green_area > red_area:
106 | return "green"
107 | else:
108 | print("\n max area = ", max(yellow_area, red_area, green_area), " < ", ratio, " * min^2 =", 0.1 * pow(min(Crop_frame.shape[0], Crop_frame.shape[1]), 2))
109 | return "NO"
110 |
111 | # judge_direction
112 |
113 |
114 | def cal_circle_xy(frame, x, y, radius):
115 | x1 = x - radius if x - radius > 0 else 0
116 | x2 = x + radius if x + radius < frame.shape[1] else frame.shape[1] # cv里面横坐标是x 是shape[1]
117 | y1 = y - radius if y - radius > 0 else 0
118 | y2 = y + radius if y + radius < frame.shape[0] else frame.shape[0] # cv里面纵坐标是y 是shape[0]
119 | return int(x1), int(x2), int(y1), int(y2)
120 |
121 |
122 | def cal_point(SomeBinary, x, y, radius): # 返回最大方向的编号int
123 |
124 | x = int(x)
125 | y = int(y)
126 | x1, x2, y1, y2 = cal_circle_xy(SomeBinary, x, y, radius)
127 | S00 = SomeBinary[y1:y, x1:x] # 计算面积时,使用二值图,左上
128 | S01 = SomeBinary[y1:y, x:x2] # 右上
129 | S10 = SomeBinary[y:y2, x1:x] # 左下
130 | S11 = SomeBinary[y:y2, x:x2] # 右下
131 |
132 | SS00 = np.sum(S00)
133 | SS01 = np.sum(S01)
134 | SS10 = np.sum(S10)
135 | SS11 = np.sum(S11)
136 |
137 | value = [SS00, SS01, SS10, SS11]
138 | value.sort(reverse=True) # 将面积大的放在前面
139 |
140 | print("\nSS00, SS01 , SS10, SS11 = ", SS00, SS01, SS10, SS11)
141 | if SS00 in value[0:2] and SS10 in value[0:2]:
142 | return "R" # right
143 | elif SS01 in value[0:2] and SS11 in value[0:2]: # 箭头右侧需要补齐的东西多
144 | return "L" # left
145 | elif SS10 in value[0:2] and SS11 in value[0:2]:
146 | return "D" # direct
147 | else:
148 | return "X" # circle
149 |
150 |
151 | def find_cnt(Crop_frame, mask): # 找轮廓
152 |
153 | mask = cv2.dilate(mask, None, iterations=1)
154 | BinColors = cv2.bitwise_and(Crop_frame, Crop_frame, mask=mask) # 提取感兴趣的颜色区域 背景黑色+彩色的图像
155 | dst = cv2.GaussianBlur(BinColors, (3, 3), 0) # 彩色图时 高斯消除噪音
156 | gray = cv2.cvtColor(dst, cv2.COLOR_BGR2GRAY) # 转成灰色图像
157 | ret, BinThings = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU) # 灰色图像二值化(变黑白图像)
158 | _, contours, hierarchy = cv2.findContours(BinThings, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE) # 边界是封闭的 linux del first one
159 | cv2.imshow("asdf",BinThings )
160 | if len(contours) > 0:
161 | cnt_max = max(contours, key=cv2.contourArea) # 找到面积最大的轮廓
162 | return cnt_max
163 | else:
164 | return None
165 |
166 |
167 | def judge_direction(Crop_frame): # 判断方向
168 | size = 50
169 | Crop_frame = cv2.resize(Crop_frame, (size, int(size * Crop_frame.shape[0] / Crop_frame.shape[1])),
170 | interpolation=cv2.INTER_CUBIC) # 将裁剪下来的图片调整到固定的尺寸
171 | # cv2.imshow("Crop_frame", Crop_frame)
172 | # cv2.waitKey(0)
173 | mask = find_mask(Crop_frame, "red+yellow+green")
174 | mask = cv2.erode(mask, None, iterations=2) # 腐蚀操作
175 | cnt_max = find_cnt(Crop_frame, mask) # 找到最大轮廓
176 |
177 | if cnt_max is None:
178 | print("\nno color contour, so NO direction !!")
179 | return "NO"
180 | solidity = 0.85
181 | direction = "NO"
182 | ilter_num = 1
183 | min_s = 0.8 # 比例最小值,低于这个值,直接判断为箭头信号灯
184 | max_s = 0.94 # 比例最大值,高于这个值,直接判断为圆形信号灯
185 | max_item = 4 # 最大腐蚀次数,比例介于最大值和最小值之间时,通过腐蚀来判断。
186 | while solidity > min_s and solidity < max_s and ilter_num < max_item:
187 | print("ilter_num = ", ilter_num)
188 |
189 | cnts = np.array(cnt_max)
190 | # cnts = cnt_max
191 | ((x, y), radius) = cv2.minEnclosingCircle(cnts) # 确定面积最大的轮廓的外接圆 返回圆心坐标和半径
192 | if radius < 5:
193 | print("\nminEnclosingCircle radius = ", radius, "< 5 , so NO direction !! ", )
194 | return "NO"
195 | x = int(x)
196 | y = int(y)
197 | area = cv2.contourArea(cnts) # 轮廓面积
198 | hull = cv2.convexHull(cnts) # 计算出凸包形状(计算边界点)
199 | hull_area = cv2.contourArea(hull) # 计算凸包面积
200 | solidity = float(area) / hull_area # 轮廓面积 / 凸包面积
201 | print("\nsolidity = ", solidity)
202 | if solidity > max_s:
203 | direction = "C" # circle
204 | break
205 | # elif solidity < min_s:
206 | # direction = "" # others type not light
207 | # # print("direction = D ", solidity)
208 | # break
209 |
210 | cnts_ColorThings = Crop_frame.copy()
211 | hull_ColorThings = Crop_frame.copy()
212 | cnts_ColorThings = cv2.drawContours(cnts_ColorThings, [cnts], -1, (255, 255, 255), -1)
213 | hull_ColorThings = cv2.drawContours(hull_ColorThings, [hull], -1, (255, 255, 255), -1)
214 | BinThings = ~cnts_ColorThings & hull_ColorThings & ~Crop_frame # 找到凸包与原图之间的差
215 |
216 |
217 |
218 | direction = cal_point(BinThings, x, y, radius) # (圆心和半径)根据凸包与原图之间的差,计算箭头的方向
219 | ilter_num += 1
220 | cnt_max = find_cnt(Crop_frame, mask) # 找最大轮廓
221 |
222 | if cv2.contourArea(cnt_max) < size * size / 5: # 腐蚀到,剩余面积很小时,结束。
223 | break
224 | return direction
225 |
226 |
227 | def get_light_type(crop_frame):
228 | color = judge_color(crop_frame) # 颜色
229 | direction = judge_direction(crop_frame) # 方向
230 | print("\n color, direction = ", color, direction)
231 |
232 | if color == "red" and direction == "R":
233 | return "15"
234 | elif color == "green" and direction == "R":
235 | return "16"
236 | elif color == "yellow" and direction == "R":
237 | return "17"
238 | elif color == "red" and direction == "D":
239 | return "18"
240 | elif color == "green" and direction == "D":
241 | return "19"
242 | elif color == "yellow" and direction == "D":
243 | return "20"
244 | elif color == "red" and direction == "L":
245 | return "21"
246 | elif color == "green" and direction == "L":
247 | return "22"
248 | elif color == "yellow" and direction == "L":
249 | return "23"
250 | elif color == "red" and direction == "C":
251 | return "24"
252 | elif color == "green" and direction == "C":
253 | return "25"
254 | elif color == "yellow" and direction == "C":
255 | return "26"
256 | else:
257 | return "NO light or cant judge"
258 |
--------------------------------------------------------------------------------
/.idea/workspace.xml:
--------------------------------------------------------------------------------
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 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 | 1558935661626
189 |
190 |
191 | 1558935661626
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
--------------------------------------------------------------------------------
/yzn_judge_light.py:
--------------------------------------------------------------------------------
1 | # -*- coding=utf-8 -*-
2 | # py37
3 | # opencv 3.4.2
4 | # win10 + pycharm
5 |
6 | # 程序作用:判断红绿灯灯颜色和方向。
7 | # 具体说明如下:
8 | # 0. 为了方便复制程序,已经将所有的程序放入这个py文件中了,程序测试正常。修改前请备份,谢谢。
9 | # 1. 输入是的一个只包含红绿灯的图片(注意,不是摄像头读取的整个图),输出是红绿灯的颜色+方向+置信度。
10 | # 2. 颜色用字符表示:红色是"R", 黄色是"Y", 绿色是"G", 未知或不能判断是"X"。
11 | # 3. 方向用字符表示:圆饼灯是"C",左转箭头是"L",直行箭头是"D",右转箭头是"R",未知或不能判断是"X"。
12 | # 4. 置信度用[0,1]之间的两位小数表示,数值越大,正确率越高。如0.86
13 |
14 |
15 | import cv2
16 | import time
17 | import numpy as np
18 |
19 | import matplotlib.pyplot as plt
20 |
21 | # 将使用Kmeans聚类用于分割,最后生成提取红绿灯使用的mask。
22 | def seg_kmeans_color(img, k=2): # 使用的是opencv中的Kmean算法
23 |
24 | # 变换一下图像通道bgr->rgb,否则很别扭啊
25 | h, w, d = img.shape # (595, 1148, 3) # 0.47 聚类的时间和图像的面积大小成正比,即与图像的点数量成正比。
26 | b, g, r = cv2.split(img)
27 | img = cv2.merge([r, g, b])
28 |
29 | # 3个通道展平
30 | img_flat = img.reshape((img.shape[0] * img.shape[1], 3)) # 一个通道一列,共三列。每行都是一个点。
31 | img_flat = np.float32(img_flat)
32 | # print("len of img_flat = ", len(img_flat))
33 | # 迭代参数
34 | criteria = (cv2.TERM_CRITERIA_EPS + cv2.TermCriteria_MAX_ITER, 20, 0.5)
35 | flags = cv2.KMEANS_RANDOM_CENTERS
36 |
37 | # 聚类
38 |
39 | compactness, labels, centers = cv2.kmeans(img_flat, k, None, criteria, 10, flags)
40 | # print(" len of labels = ", len(labels))
41 | # print(labels) # 每个点的标签
42 | # print(centers) # 两类的中心,注意是点的值,不是点的坐标
43 | labels = np.squeeze(labels)
44 | img_output = labels.reshape((img.shape[0], img.shape[1]))
45 | L1 = max(int(img.shape[1] / 4), 2) # 当图像只有四个像素时, L1等于1 会出现-1:的异常异常,所以需要改为2,
46 |
47 | # 取每个四个角上的点
48 | boundary00 = np.squeeze(img_output[0:L1, 0: L1]. reshape((1, -1)))
49 | boundary01 = np.squeeze(img_output[0:L1, - L1:]. reshape((1, -1)))
50 | boundary10 = np.squeeze(img_output[- L1:, 0:L1]. reshape((1, -1)))
51 | boundary11 = np.squeeze(img_output[- L1:, - L1-1]. reshape((1, -1)))
52 |
53 | # 取中间一个正方形内的点
54 | inter = img_output[L1: -L1, L1: -L1]
55 | boundary_avg = np.average(np.concatenate((boundary00, boundary01, boundary10, boundary11), axis=0))
56 | inter_avg = np.average(inter)
57 | # print("boundary_avg", boundary_avg)
58 | # print("inter_avg", inter_avg)
59 |
60 | if k == 2 and boundary_avg > inter_avg: # 如果聚类使得边缘类是1,即亮的话,需要纠正颜色(所有的标签)。
61 | img_output = abs(img_output - 1) # 将边缘类(背景)的标签值设置为0,中间类(中间类)的值设置为1.
62 |
63 | img_output = np.array(img_output, dtype=np.uint8) # jiang
64 |
65 | return img_output
66 |
67 |
68 | # 下面是实现 judge_color 函数的各个子函数
69 | def judge_light_color(obj_bgr):
70 | # 将图片转成HSV的
71 | obj_hsv = cv2.cvtColor(obj_bgr, cv2.COLOR_BGR2HSV)
72 | # 从H通道中提取出颜色的平均值
73 | H = obj_hsv[:, :, 0]
74 | H = np.squeeze(np.reshape(H, (1, -1)))
75 | print(H) # 这里要注意红色H值得范围很特别,特别小和特别大都是红色,直接取平均值会出现判断错误的情况。
76 | H_value = np.array([])
77 | for i in H:
78 | if i > 0 and i < 124:
79 | H_value = np.append(H_value, i)
80 | elif i > 155: # 将大于155的红色的H值,都看成是1,方便处理
81 | H_value = np.append(H_value, 1.0)
82 | H_avg = np.average(H_value)
83 | # 根据这个值来判断是什么颜色, RYG
84 | if H_avg <= 18:
85 | color = "R"
86 | elif H_avg >= 18 and H_avg <= 34:
87 | color = "Y"
88 | elif H_avg >= 35 and H_avg <= 100:
89 | color = "G"
90 | else:
91 | color = "X"
92 |
93 | return color
94 |
95 |
96 | def judge_light_position(trafficLight, cx, cy):
97 | h, w, d = trafficLight.shape
98 | # print(trafficLight.shape)
99 | position = "0"
100 | if max(w / h, h / w) > 2: # 根据位置来判断颜色
101 | if h > w: # 竖着三个灯
102 | if cy < h / 3: # 灯的中心在上方
103 | position = "1"
104 | elif cy < 2 * h / 3: # 灯的中心在中间
105 | position = "2"
106 | else: # 灯的中心在下方
107 | position = "3"
108 |
109 | else: # 横着三个灯
110 | if cx < w / 3: # 灯的中心在左方
111 | position = "1"
112 | elif cx < 2 * w / 3: # 灯的中心在中间
113 | position = "2"
114 | else: # 灯的中心在右方
115 | position = "3"
116 | return position
117 |
118 |
119 | def judge_color(trafficLight, obj_bgr, cx, cy):
120 | position = judge_light_position(trafficLight, cx, cy) # 判断位置
121 | color = judge_light_color(obj_bgr) # 判断颜色
122 | if (position == "1" and color == "R") or (position == "2" and color == "Y") or (position == "3" and color == "G"):
123 | color_conf = 1.0
124 | elif position == "2" and color == "R":
125 | color = "Y" # 如何在中间就纠正为黄色
126 | color_conf = 0.9
127 | elif position == "1" and color == "Y":
128 | color = "R" # 如何在中间就纠正为黄色
129 | color_conf = 0.9
130 | else:
131 | color_conf = 0.8
132 | return color, color_conf
133 |
134 |
135 | # 下面是实现judge_direction函数的各个子函数
136 | def cal_circle_xy(frame, x, y, radius):
137 | x1 = x - radius if x - radius > 0 else 0
138 | x2 = x + radius if x + radius < frame.shape[1] else frame.shape[1] # cv里面横坐标是x 是shape[1]
139 | y1 = y - radius if y - radius > 0 else 0
140 | y2 = y + radius if y + radius < frame.shape[0] else frame.shape[0] # cv里面纵坐标是y 是shape[0]
141 | return int(x1), int(x2), int(y1), int(y2)
142 |
143 |
144 | def cal_point(SomeBinary, x, y, radius): # 返回最大方向的编号int
145 |
146 | x = int(x)
147 | y = int(y)
148 | x1, x2, y1, y2 = cal_circle_xy(SomeBinary, x, y, radius)
149 | S00 = SomeBinary[y1:y, x1:x] # 计算面积时,使用二值图,左上
150 | S01 = SomeBinary[y1:y, x:x2] # 右上
151 | S10 = SomeBinary[y:y2, x1:x] # 左下
152 | S11 = SomeBinary[y:y2, x:x2] # 右下
153 |
154 | SS00 = np.sum(S00)
155 | SS01 = np.sum(S01)
156 | SS10 = np.sum(S10)
157 | SS11 = np.sum(S11)
158 |
159 | value = [SS00, SS01, SS10, SS11]
160 | value.sort(reverse=True) # 将面积大的放在前面
161 |
162 | print("\nSS00, SS01 , SS10, SS11 = ", SS00, SS01, SS10, SS11)
163 | if SS00 in value[0:2] and SS10 in value[0:2]:
164 | return "R" # right
165 | elif SS01 in value[0:2] and SS11 in value[0:2]: # 箭头右侧需要补齐的东西多
166 | return "L" # left
167 | elif SS10 in value[0:2] and SS11 in value[0:2]:
168 | return "D" # direct
169 | else:
170 | return "X" # circle
171 |
172 |
173 | def judge_direction(obj_bgr, cnt_max):
174 |
175 | cnt = np.array(cnt_max)
176 | ((x, y), radius) = cv2.minEnclosingCircle(cnt) # 确定面积最大的轮廓的外接圆 返回圆心坐标和半径
177 | if radius < 3:
178 | print("\nminEnclosingCircle radius = ", radius, "< 5 , so NO direction !! ", )
179 | return "X", 0.0
180 |
181 | area = cv2.contourArea(cnt) # 轮廓面积
182 | hull = cv2.convexHull(cnt) # 计算出凸包形状(计算边界点)
183 | hull_area = cv2.contourArea(hull) # 计算凸包面积
184 | solidity = round(float(area) / hull_area, 2) # 自己的面积 / 凸包面积 凸度
185 | circularity = round(hull_area / (np.pi * pow(radius, 2)), 2) # 自己的面积/外接圆的面积
186 |
187 | # print("solidity = ", solidity)
188 | # print("circularity = ", circularity)
189 |
190 | direction = "X" # X 表示是未知方向,或者不能正确判断
191 | ratio = 0
192 | threshold01 = 0.9 # solidity 远大越可能是圆
193 | threshold03 = 0.5 # solidity * circularity 越小越可能是箭头
194 |
195 | if solidity > threshold01 and circularity > 0.5: # 当solidity度很大,且圆形度不是很小时 (考虑椭圆的情况),直接判断为圆
196 | direction = "C" # 判断 为圆形灯
197 | ratio = solidity # 将solidity度作为 圆灯的概率返回
198 | elif solidity > threshold01 and circularity <= 0.5: # 凸度很大,但圆形度很小时,说明不是圆
199 | direction = "X"
200 | ratio = 0.0
201 | else: # 当solidity度处于中间值时,判断为箭头,并计算概率
202 | # (规定圆形度与凸度成绩小于0.7时,概率为1。大于0.7时,概率小于1)
203 | cnts_ColorThings = cv2.drawContours(obj_bgr.copy(), [cnt], -1, (255, 255, 255), -1)
204 | hull_ColorThings = cv2.drawContours(obj_bgr.copy(), [hull], -1, (255, 255, 255), -1)
205 | BinThings = ~cnts_ColorThings & hull_ColorThings & ~obj_bgr # 找到凸包与原图之间的差
206 |
207 | # cv2.imshow("obj_bgr", obj_bgr)
208 | # cv2.imshow("cnts_ColorThings = ", cnts_ColorThings)
209 | # cv2.imshow("BinThings = ", BinThings)
210 | direction = cal_point(BinThings, int(x), int(y), radius) # 判断方向(圆心和半径)根据凸包与原图之间的差,计算箭头的方向
211 | x = solidity * circularity
212 | print("solidity * circularity = ", x)
213 | if x < threshold03: # 乘积小于这个值,判断为箭头,概率为1。
214 | ratio = 1
215 | else:
216 | ratio = (x - 1) / (threshold03 - 1)
217 | # 当圆形度特别小时,判断为处于异常,输出X 0
218 | return direction, ratio
219 |
220 |
221 | # 下面是实现 get_object函数的各个子函数。
222 | def find_contours_max(img_bin_color):
223 | # 找最大轮廓的正矩形
224 | gray = cv2.cvtColor(img_bin_color, cv2.COLOR_BGR2GRAY) # 转成灰色图像
225 | ret, BinThings = cv2.threshold(gray, 1, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU) # 灰色图像二值化(变黑白图像)
226 | _, contours, hierarchy = cv2.findContours(BinThings, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
227 |
228 | # for i in range(len(contours) - 1): # 绘制每一个轮廓
229 | # cv2.drawContours(frame_copy, [contours[i+1]], -1, (0, 0, 255), 2)
230 |
231 | # if len(contours) > 0:
232 | # cnt_max = max(contours, key=cv2.contourArea) # 找到面积最大的轮廓
233 | # color_aera = cv2.contourArea(cnt_max)
234 |
235 | if len(contours) > 0:
236 | cnt_max = max(contours, key=cv2.contourArea) # 找到面积最大的轮廓
237 | # cv2.drawContours(img_bin_color, [cnt_max], -1, (0, 255, 255), 2)
238 | # cv2.imshow("img_bin_color", img_bin_color)
239 | # # ((x, y), radius) = cv2.minEnclosingCircle(np.array(cnt_max))
240 | # cv2.circle(img_bgr, (int(x), int(y)), int(radius), (0, 0, 255), 2)
241 | # print(((x, y), radius))
242 |
243 | # print("cnt_max", cnt_max)
244 | return cnt_max
245 | return None
246 |
247 |
248 | def crop_max_region(frame_bgr, img_bin_color): # frame_bgr, img_bgr
249 | """
250 | :param frame_bgr: 用于裁剪的原始图片
251 | :param img_bin_color: # 用于腐蚀,寻找颜色区域的
252 | :return: 在原图上裁剪下来的区域。
253 | """
254 |
255 | cnt_max = find_contours_max(img_bin_color)
256 | if cnt_max is not None:
257 | x, y, w, h = cv2.boundingRect(np.array(cnt_max)) # 正外界矩形
258 | # print("x, y, w, h = ", x, y, w, h)
259 | # cv2.rectangle(img_bgr, (x, y), (x + w, y + h), (0, 255, 0), 2)
260 | frame_crop = frame_bgr[y: y + h, x: x + w]
261 |
262 | # cv2.imshow("frame_crop6", frame_crop)
263 | cnt_max = find_contours_max(frame_crop)
264 |
265 | # 将原始图像翻转并判断形状和方向
266 |
267 | return frame_crop, cnt_max, x, y, w, h
268 | return None, None, None, None, None, None
269 |
270 |
271 | def get_color_region(frame_bgr, frame_hsv, color="RYG"):
272 | # cv2.imshow("frame_bgr", frame_bgr)
273 | # colorLower = np.array([0, 43, 46], dtype=np.uint8) # 非黑白灰的
274 | # colorUpper = np.array([180, 255, 255], dtype=np.uint8)
275 | try:
276 | redLower01 = np.array([0, 43, 46], dtype=np.uint8) # 部分红 和橙黄绿
277 | redUpper01 = np.array([124, 255, 255], dtype=np.uint8)
278 | red_mask02_and_othersLower = np.array([156, 43, 46], dtype=np.uint8) # 部分红
279 | red_mask02_and_othersUpper = np.array([180, 255, 255], dtype=np.uint8)
280 |
281 | red_mask01 = cv2.inRange(frame_hsv, redLower01, redUpper01)
282 | red_mask02_and_others = cv2.inRange(frame_hsv, red_mask02_and_othersLower, red_mask02_and_othersUpper)
283 | mask = None
284 | if color == "RYG":
285 | mask = red_mask01 + red_mask02_and_others
286 | # print("mask.shape = ", mask.shape) # (26, 23)
287 |
288 | BinColors = cv2.bitwise_and(frame_bgr, frame_bgr, mask=mask)
289 | # cv2.imshow("BinColors", BinColors)
290 | # BinColors = cv2.GaussianBlur(BinColors, (5, 5), 0) # 彩色图时 高斯消除噪音 # 很耗时,不建议使用
291 |
292 | # dst = cv2.erode(dst, None, iterations=2) # 腐蚀操作
293 | kernel = np.ones((3, 3), np.uint8)
294 | # BinColors = cv2.morphologyEx(BinColors, cv2.MORPH_OPEN, kernel) # 开运算
295 | # kernel = np.ones((10, 10), np.uint8)
296 | BinColors = cv2.morphologyEx(BinColors, cv2.MORPH_CLOSE, kernel) # 闭运算
297 |
298 | # cv2.imshow("mask ", mask) # 这是一个二值图
299 | # cv2.imshow("crop_frame ", crop_frame) # 这是一个二值图
300 |
301 | # cv2.imshow("BinColors", BinColors) # 原图
302 | # cv2.imshow("crop_frame", crop_frame) # 原图
303 | # cv2.imshow("BinColors", BinColors) # 其中的彩色区域
304 | # cv2.imshow("img_bin_color ", img_bin_color)
305 |
306 | # cv2.waitKey(0)
307 | return crop_max_region(frame_bgr, BinColors)
308 | except:
309 | return None, None, None, None, None, None
310 |
311 |
312 | def get_object(frame_bgr):
313 | frame_hsv = cv2.cvtColor(frame_bgr, cv2.COLOR_BGR2HSV)
314 | frame_crop_bgr, _, x1, y1, w1, h1 = get_color_region(frame_bgr, frame_hsv, color="RYG") # 获取彩色区域(裁剪一个矩形)
315 | if frame_crop_bgr is None:
316 | print("no object in get_object")
317 | return None, None, None, None
318 | frame_kmeans = seg_kmeans_color(frame_crop_bgr) # k menan k=2
319 |
320 | # cv2.imshow("frame_crop_bgr", frame_crop_bgr)
321 | # cv2.imshow("frame_kmeans", frame_kmeans)
322 |
323 | frame_new_bgr = cv2.bitwise_and(frame_crop_bgr, frame_crop_bgr, mask=frame_kmeans) # 使用Kmean之后的结果比直接使用颜色阈值分割更可靠。
324 |
325 | kernel = np.ones((3, 3), np.uint8)
326 | frame_new_bgr = cv2.morphologyEx(frame_new_bgr, cv2.MORPH_OPEN, kernel)
327 | frame_max_bgr, cnt_max, x2, y2, w2, h2 = crop_max_region(frame_new_bgr, frame_new_bgr) # Kmeans之后的中间颜色区域提取。
328 |
329 | # light = frame_bgr[y1 + y2: y1 + y2 + h2, x1 + x2: x1 + x2 + w2]
330 | # cv2.imshow("light =", light)
331 |
332 | cx = int(x1 + x2 + w2 / 2) # 灯的中心点坐标
333 | cy = int(y1 + y2 + h2 / 2)
334 |
335 | # 显示结果
336 | b, g, r = cv2.split(frame_bgr)
337 | frame_rgb = cv2.merge([r, g, b])
338 | frame_crop_rgb = frame_crop_bgr[:, :, (2, 1, 0)]
339 | frame_new_rgb = frame_new_bgr[:, :, (2, 1, 0)]
340 | frame_max_rgb = frame_max_bgr[:, :, (2, 1, 0)]
341 |
342 | # plt 显示图片RGB的顺序
343 | plt.subplot(161), plt.imshow(frame_rgb), plt.title('rgb') # plt是RGB的顺序,CV是BGR的顺序。
344 | plt.subplot(162), plt.imshow(frame_crop_rgb), plt.title('crop_rgb') # plt是RGB的顺序,CV是BGR的顺序。
345 | plt.subplot(163), plt.imshow(frame_kmeans, 'gray'), plt.title('kmeans')
346 | plt.subplot(164), plt.imshow(frame_new_rgb, 'gray'), plt.title('new_rgb')
347 | plt.subplot(165), plt.imshow(frame_max_rgb), plt.title('max_rgb')
348 | plt.show()
349 |
350 | # cv显示图片 BGR的通道顺序
351 | # cv2.imshow("frame", frame) # 原图
352 | # cv2.imshow("crop_frame", frame_crop) # 原图
353 | # cv2.waitKey(0)
354 | # frame_crop_gray = cv2.cvtColor(frame_crop_bgr, cv2.COLOR_BGR2GRAY)
355 | # cv2.imshow("frame_crop_gray", frame_crop_gray)
356 | # cv2.waitKey()
357 | return frame_max_bgr, cnt_max, cx, cy
358 |
359 |
360 | # judge_light 是一个总函数,同时调用了三个函数:get_object,judge_direction,udge_color
361 | def judge_light(trafficLight):
362 | obj_bgr, cnt_max, cx, cy = get_object(trafficLight) # 通过颜色来获取红绿灯区域。(目前只考虑了一个红绿灯的情况)
363 | if cnt_max is None:
364 | return "X", "X", 0
365 | direction, direction_conf = judge_direction(obj_bgr, cnt_max) # 判断方向
366 | # print("\nResult : \ndirection, ratio = ", direction, direction_conf)
367 | color, color_conf = judge_color(trafficLight, obj_bgr, cx, cy) # 判断颜色
368 |
369 | # print("color, color_conf = ", color, color_conf)
370 | conf = round(direction_conf * color_conf, 2) # 将反向和颜色置信度的成绩作为最后的置信度。
371 |
372 | return color, direction, conf # 单个字符, 单个字符, 小于1的小数
373 |
374 |
375 | if __name__ == "__main__":
376 | trafficLight = cv2.imread('C:\\Users\\qcdz-003\\Pictures\\light\\025.jpg', cv2.IMREAD_COLOR)
377 | start = time.time()
378 | color, direction, conf = judge_light(trafficLight) # 最终只使用这一行代码。
379 | end = time.time()
380 | print("\n####################")
381 | print("judge_light() use time = ", end - start) # 0.00297
382 | print("color, direction, conf = ", color, direction, conf)
383 | print("the end !")
384 |
--------------------------------------------------------------------------------