├── .gitattributes
├── README.md
├── Run.py
├── best.kv
├── config
└── ui_config.json
├── kv_dlg
├── dlg_menu.kv
└── dlg_setting.kv
├── logo
└── 1.jpg
└── mylib
├── func.py
├── main.py
├── misc
├── demo.gif
└── settings.png
├── settings.py
└── videos
├── 1.mp4
└── 2.mp4
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Multiple Camera Streams GUI
2 | A simple user interface to stitch and display multiple camera streams with OpenCV and Kivy.
3 |
4 | > Supports configuring several videos and ip cameras.
5 |
6 |
7 |

8 |
9 |
10 | ---
11 |
12 | ## Inference
13 |
14 | - Install main libraries:
15 | ```
16 | pip install opencv-python
17 | pip install kivy
18 | ```
19 | - To setup cameras via config, enter your video path or ip camera url in 'config>ui_config.json'.
20 |
21 | > You can place your videos in mylib>videos or enter the url if its an ip camera. Example:
22 |
23 | ```
24 | "camera_url": [
25 | "mylib/videos/1.mp4",
26 | "http://192.158.0.115:8050/video",
27 | ```
28 | - To setup cameras via ui, click settings>enter details>apply after running the code:
29 |
30 |
31 |

32 |
33 |
34 | > You could also setup a database if you ever want to.
35 |
36 | - To run: ``` python run.py ```
37 |
38 |
39 |
40 | ---
41 |
42 | saimj7/ 28-02-2021 © Sai_Mj.
43 |
--------------------------------------------------------------------------------
/Run.py:
--------------------------------------------------------------------------------
1 | from kivy.app import App
2 | from kivy.config import Config
3 | from kivy.lang import Builder
4 | from kivy.properties import ListProperty, StringProperty
5 | from kivy.clock import Clock
6 | from kivy.graphics.texture import Texture
7 | from kivy.core.window import Window
8 | import numpy as np
9 | import time, cv2
10 | import _thread as thread
11 | from mylib import func
12 | from mylib.settings import *
13 | from mylib.main import WorkCamera
14 |
15 |
16 |
17 | class BestApp(App):
18 |
19 | screen_names = ListProperty([])
20 | screens = {} # Dict of all screens
21 |
22 | title = StringProperty()
23 | camera_list = ListProperty()
24 | txt_db_host = StringProperty()
25 | txt_db_user = StringProperty()
26 | txt_db_password = StringProperty()
27 | txt_db_port = StringProperty()
28 | txt_db_database = StringProperty()
29 | txt_db_table = StringProperty()
30 |
31 | def __init__(self, **kwargs):
32 | self.setting = func.read_json(CONFIG_FILE)
33 |
34 | self.camera_list = self.setting['camera_url']
35 | self.view_mode = -1
36 | self.image_no_camera = cv2.imread('logo/1.jpg')
37 | self.image_no_camera_small = cv2.resize(self.image_no_camera, (PIECE_WIDTH, PIECE_HEIGHT))
38 |
39 | self.txt_db_host = self.setting['db_host']
40 | self.txt_db_port = self.setting['db_port']
41 | self.txt_db_user = self.setting['db_user']
42 | self.txt_db_password = self.setting['db_password']
43 | self.txt_db_database = self.setting['db_database']
44 | self.txt_db_table = self.setting['db_table']
45 |
46 | self.class_main = WorkCamera(self.camera_list)
47 |
48 | for i in range(len(self.class_main.cap_list)):
49 | thread.start_new_thread(self.class_main.read_frame, (i, RESIZE_FACTOR))
50 |
51 | self.title = 'Camera View'
52 | Window.size = (1200, 800)
53 | Window.left = 100
54 | Window.top = 50
55 |
56 | self.event_take_video = None
57 | self.fps = 30
58 | self.on_resume()
59 | super(BestApp, self).__init__(**kwargs)
60 |
61 | # --------------------------- Main Menu Event -----------------------------
62 | def go_setting(self):
63 | self.title = 'Setting'
64 | self.go_screen('dlg_setting', 'left')
65 |
66 | def on_exit(self):
67 | exit(0)
68 |
69 | # ---------------------- Camera Setting dialog Event ------------------------
70 | def on_sel_view_mode(self, mode):
71 | self.view_mode = mode
72 |
73 | def on_cam_set(self, cam1, cam2, cam3, cam4, cam5, cam6, cam7, cam8, host, port, user, password, database, table):
74 | cam_list = [cam1, cam2, cam3, cam4, cam5, cam6, cam7, cam8]
75 | f_change_camera = False
76 | for i in range(len(cam_list)):
77 | if self.camera_list[i] != cam_list[i]:
78 | f_change_camera = True
79 | self.class_main.camera_enable[i] = False
80 | time.sleep(0.5)
81 | self.class_main.camera_list[i] = cam_list[i]
82 | if self.class_main.cap_list[i] is not None:
83 | self.class_main.cap_list[i].release()
84 | self.class_main.cap_list[i] = cv2.VideoCapture(cam_list[i])
85 | self.class_main.tracker_list[i].format()
86 | self.class_main.camera_enable[i] = True
87 |
88 | if f_change_camera:
89 | self.camera_list = cam_list
90 |
91 | f_change_db = False
92 | if self.txt_db_host != host or self.txt_db_table != table or self.txt_db_database != database or \
93 | self.txt_db_user != user or self.txt_db_password != password or self.txt_db_port != port:
94 | f_change_db = True
95 |
96 | if f_change_db:
97 | self.txt_db_host = host
98 | self.txt_db_port = port
99 | self.txt_db_user = user
100 | self.txt_db_password = password
101 | self.txt_db_database = database
102 | self.txt_db_table = table
103 |
104 | self.class_main.sql_table = table
105 |
106 | # save config file
107 | if f_change_camera or f_change_db:
108 | print("Updated config file!")
109 | self.setting['camera_url'] = cam_list
110 | self.setting["db_host"] = host
111 | self.setting["db_user"] = user
112 | self.setting["db_port"] = port
113 | self.setting["db_password"] = password
114 | self.setting["db_database"] = database
115 | self.setting["db_table"] = table
116 | func.write_json(CONFIG_FILE, self.setting)
117 |
118 | self.title = 'Camera View'
119 | self.go_screen('dlg_menu', 'right')
120 |
121 | self.on_resume()
122 |
123 | def on_reset(self):
124 | for i in range(len(self.class_main.tracker_list)):
125 | self.class_main.tracker_list[i].format()
126 |
127 | print("Reset Counting data!")
128 |
129 | def on_return(self):
130 | self.title = 'Camera View'
131 | self.go_screen('dlg_menu', 'right')
132 |
133 | # ------------------------ Image Processing -------------------------------
134 | def on_stop(self):
135 | if self.event_take_video is not None and self.event_take_video.is_triggered:
136 | self.event_take_video.cancel()
137 |
138 | def on_resume(self):
139 | if self.event_take_video is None:
140 | self.event_take_video = Clock.schedule_interval(self.get_frame, 1.0 / self.fps)
141 | elif not self.event_take_video.is_triggered:
142 | self.event_take_video()
143 |
144 | def get_frame(self, *args):
145 |
146 | def get_piece_img(ind):
147 | if ind >= len(self.class_main.ret_image) or self.class_main.ret_image[ind] is None:
148 | return self.image_no_camera_small
149 | else:
150 | return cv2.resize(self.class_main.ret_image[ind], (PIECE_WIDTH, PIECE_HEIGHT))
151 |
152 | if self.view_mode == -1: # all camera
153 | # merge images
154 | img1 = np.concatenate((get_piece_img(0), get_piece_img(1)), axis=1)
155 | img2 = np.concatenate((img1, get_piece_img(2)), axis=1)
156 | img3 = np.concatenate((get_piece_img(3), get_piece_img(4)), axis=1)
157 | img4 = np.concatenate((img3, get_piece_img(5)), axis=1)
158 | img5 = np.concatenate((get_piece_img(6), get_piece_img(7)), axis=1)
159 | img6 = np.concatenate((img5, get_piece_img(8)), axis=1)
160 | img7 = np.concatenate((img2, img4), axis=0)
161 | frame = np.concatenate((img7, img6), axis=0)
162 | # draw grid
163 | cv2.line(frame, (PIECE_WIDTH, 0), (PIECE_WIDTH, PIECE_HEIGHT * 3), (0, 0, 0), 1)
164 | cv2.line(frame, (PIECE_WIDTH * 2, 0), (PIECE_WIDTH * 2, PIECE_HEIGHT * 3), (0, 0, 0), 1)
165 | cv2.line(frame, (0, PIECE_HEIGHT), (PIECE_WIDTH * 3, PIECE_HEIGHT), (0, 0, 0), 1)
166 | cv2.line(frame, (0, PIECE_HEIGHT * 2), (PIECE_WIDTH * 3, PIECE_HEIGHT * 2), (0, 0, 0), 1)
167 | else:
168 | if self.class_main.ret_image[self.view_mode] is None:
169 | frame = self.image_no_camera
170 | else:
171 | frame = self.class_main.ret_image[self.view_mode]
172 |
173 | self.frame_to_buf(frame=frame)
174 |
175 | def frame_to_buf(self, frame):
176 | fh, fw = frame.shape[:2]
177 | buf1 = cv2.flip(frame, 0)
178 | buf = buf1.tostring()
179 | self.root.ids.img_video.texture = Texture.create(size=(fw, fh))
180 | self.root.ids.img_video.texture.blit_buffer(buf, colorfmt='bgr', bufferfmt='ubyte')
181 |
182 | def build(self):
183 | self.load_screen()
184 | self.go_screen('dlg_menu', 'right')
185 |
186 | def go_screen(self, dest_screen, direction):
187 | sm = self.root.ids.sm
188 | sm.switch_to(self.screens[dest_screen], direction=direction)
189 |
190 | def load_screen(self):
191 | self.screen_names = ['dlg_menu', 'dlg_setting']
192 | for i in range(len(self.screen_names)):
193 | screen = Builder.load_file('kv_dlg/' + self.screen_names[i] + '.kv')
194 | self.screens[self.screen_names[i]] = screen
195 | return True
196 |
197 |
198 | if __name__ == '__main__':
199 | Config.set('graphics', 'resizable', 0)
200 | BestApp().run()
201 |
--------------------------------------------------------------------------------
/best.kv:
--------------------------------------------------------------------------------
1 | #:kivy 1.0.9
2 |
3 | FloatLayout:
4 |
5 | BoxLayout:
6 | orientation: 'vertical'
7 | padding: 30
8 |
9 | Image:
10 | id: img_video
11 |
12 | Label:
13 | size_hint_y: None
14 | height: 80
15 |
16 | ScreenManager:
17 | id: sm
18 | on_current_screen:
19 | idx = app.screen_names.index(args[1].name)
20 |
--------------------------------------------------------------------------------
/config/ui_config.json:
--------------------------------------------------------------------------------
1 | {
2 | "camera_url": [
3 | "mylib/videos/1.mp4",
4 | "mylib/videos/2.mp4",
5 | "",
6 | "",
7 | "",
8 | "",
9 | "",
10 | ""
11 | ],
12 | "db_host": "",
13 | "db_user": "",
14 | "db_port": "",
15 | "db_password": "",
16 | "db_database": "",
17 | "db_table": ""
18 | }
--------------------------------------------------------------------------------
/kv_dlg/dlg_menu.kv:
--------------------------------------------------------------------------------
1 | Screen:
2 | name: 'dlg_menu'
3 |
4 | BoxLayout:
5 | padding: (50, 15)
6 | spacing: 30
7 | orientation: 'horizontal'
8 |
9 | size_hint_y: None
10 | height: 100
11 |
12 | ToggleButton:
13 | size_hint_x: 0.14
14 | text: 'All Cameras'
15 | font_size: 22
16 | group: 'view_mode'
17 | state: 'down'
18 | on_release: app.on_sel_view_mode(-1)
19 |
20 | GridLayout:
21 | cols: 4
22 | rows: 2
23 | size_hint_x: 0.45
24 |
25 | ToggleButton:
26 | text: 'Camera1'
27 | font_size: 22
28 | group: 'view_mode'
29 | on_release: app.on_sel_view_mode(0)
30 |
31 | ToggleButton:
32 | text: 'Camera2'
33 | font_size: 22
34 | group: 'view_mode'
35 | on_release: app.on_sel_view_mode(1)
36 |
37 | ToggleButton:
38 | text: 'Camera3'
39 | font_size: 22
40 | group: 'view_mode'
41 | on_release: app.on_sel_view_mode(2)
42 |
43 | ToggleButton:
44 | text: 'Camera4'
45 | font_size: 22
46 | group: 'view_mode'
47 | on_release: app.on_sel_view_mode(3)
48 |
49 | ToggleButton:
50 | text: 'Camera5'
51 | font_size: 22
52 | group: 'view_mode'
53 | on_release: app.on_sel_view_mode(4)
54 |
55 | ToggleButton:
56 | text: 'Camera6'
57 | font_size: 22
58 | group: 'view_mode'
59 | on_release: app.on_sel_view_mode(5)
60 |
61 | ToggleButton:
62 | text: 'Camera7'
63 | font_size: 22
64 | group: 'view_mode'
65 | on_release: app.on_sel_view_mode(6)
66 |
67 | ToggleButton:
68 | text: 'Camera8'
69 | font_size: 22
70 | group: 'view_mode'
71 | on_release: app.on_sel_view_mode(7)
72 |
73 | Button:
74 | text: 'Reset'
75 | font_size: 22
76 | size_hint_x: 0.1
77 | on_release: app.on_reset()
78 |
79 | Button:
80 | text: 'Settings'
81 | font_size: 22
82 | size_hint_x: 0.1
83 | on_release: app.go_setting()
84 |
85 | Button:
86 | size_hint_x: 0.1
87 | text: 'Exit'
88 | font_size: 22
89 | on_release: app.on_exit()
90 |
--------------------------------------------------------------------------------
/kv_dlg/dlg_setting.kv:
--------------------------------------------------------------------------------
1 | Screen:
2 | name: 'dlg_setting'
3 |
4 | canvas:
5 | Rectangle:
6 | pos: 0, 0
7 | size: root.width, root.height
8 |
9 | BoxLayout:
10 | orientation: 'vertical'
11 | padding: (100, 40)
12 | spacing: 30
13 |
14 | BoxLayout:
15 | size_hint_y: 0.1
16 |
17 | Label
18 | text: 'Camera Settings'
19 | color: (0, 0, 0.5, 1)
20 | font_size: 32
21 |
22 | BoxLayout:
23 | orientation: 'vertical'
24 | size_hint_y: 0.7
25 | spacing: 10
26 |
27 | BoxLayout:
28 | spacing: 20
29 | orientation: 'horizontal'
30 | size_hint_y: None
31 | height: 35
32 |
33 | Label
34 | text: 'Camera1'
35 | color: (0, 0, 0.5, 1)
36 | font_size: 24
37 | size_hint_x: None
38 |
39 | TextInput
40 | id: txt_cam1
41 | text: app.camera_list[0]
42 | font_size: 18
43 | multiline: False
44 |
45 | BoxLayout:
46 | spacing: 20
47 | orientation: 'horizontal'
48 | size_hint_y: None
49 | height: 35
50 |
51 | Label
52 | text: 'Camera2'
53 | color: (0, 0, 0.5, 1)
54 | font_size: 24
55 | size_hint_x: None
56 |
57 | TextInput
58 | id: txt_cam2
59 | text: app.camera_list[1]
60 | font_size: 18
61 | multiline: False
62 |
63 | BoxLayout:
64 | spacing: 20
65 | orientation: 'horizontal'
66 | size_hint_y: None
67 | height: 35
68 |
69 | Label
70 | text: 'Camera3'
71 | color: (0, 0, 0.5, 1)
72 | font_size: 24
73 | size_hint_x: None
74 |
75 | TextInput
76 | id: txt_cam3
77 | text: app.camera_list[2]
78 | font_size: 18
79 | multiline: False
80 |
81 | BoxLayout:
82 | spacing: 20
83 | orientation: 'horizontal'
84 | size_hint_y: None
85 | height: 35
86 |
87 | Label
88 | text: 'Camera4'
89 | color: (0, 0, 0.5, 1)
90 | font_size: 24
91 | size_hint_x: None
92 |
93 | TextInput
94 | id: txt_cam4
95 | text: app.camera_list[3]
96 | font_size: 18
97 | multiline: False
98 |
99 | BoxLayout:
100 | spacing: 20
101 | orientation: 'horizontal'
102 | size_hint_y: None
103 | height: 35
104 |
105 | Label
106 | text: 'Camera5'
107 | color: (0, 0, 0.5, 1)
108 | font_size: 24
109 | size_hint_x: None
110 |
111 | TextInput
112 | id: txt_cam5
113 | text: app.camera_list[4]
114 | font_size: 18
115 | multiline: False
116 |
117 | BoxLayout:
118 | spacing: 20
119 | orientation: 'horizontal'
120 | size_hint_y: None
121 | height: 35
122 |
123 | Label
124 | text: 'Camera6'
125 | color: (0, 0, 0.5, 1)
126 | font_size: 24
127 | size_hint_x: None
128 |
129 | TextInput
130 | id: txt_cam6
131 | text: app.camera_list[5]
132 | font_size: 18
133 | multiline: False
134 |
135 | BoxLayout:
136 | spacing: 20
137 | orientation: 'horizontal'
138 | size_hint_y: None
139 | height: 35
140 |
141 | Label
142 | text: 'Camera7'
143 | color: (0, 0, 0.5, 1)
144 | font_size: 24
145 | size_hint_x: None
146 |
147 | TextInput
148 | id: txt_cam7
149 | text: app.camera_list[6]
150 | font_size: 18
151 | multiline: False
152 |
153 | BoxLayout:
154 | spacing: 20
155 | orientation: 'horizontal'
156 | size_hint_y: None
157 | height: 35
158 |
159 | Label
160 | text: 'Camera8'
161 | color: (0, 0, 0.5, 1)
162 | font_size: 24
163 | size_hint_x: None
164 |
165 | TextInput
166 | id: txt_cam8
167 | text: app.camera_list[7]
168 | font_size: 18
169 | multiline: False
170 |
171 | BoxLayout:
172 | size_hint_y: 0.1
173 |
174 | Label
175 | text: 'Database Settings'
176 | color: (0, 0, 0.5, 1)
177 | font_size: 32
178 |
179 | GridLayout:
180 | orientation: 'vertical'
181 | cols: 3
182 | spacing: (30, 15)
183 | size_hint_y: 0.2
184 |
185 | BoxLayout:
186 | spacing: 10
187 | orientation: 'horizontal'
188 | size_hint_y: None
189 | height: 35
190 |
191 | Label
192 | text: 'Host'
193 | color: (0, 0, 0.5, 1)
194 | font_size: 24
195 | size_hint_x: None
196 |
197 | TextInput
198 | id: txt_db_host
199 | text: app.txt_db_host
200 | font_size: 18
201 | multiline: False
202 |
203 | BoxLayout:
204 | spacing: 10
205 | orientation: 'horizontal'
206 | size_hint_y: None
207 | height: 35
208 |
209 | Label
210 | text: 'Username'
211 | color: (0, 0, 0.5, 1)
212 | font_size: 24
213 | size_hint_x: None
214 |
215 | TextInput
216 | id: txt_db_user
217 | text: app.txt_db_user
218 | font_size: 18
219 | multiline: False
220 |
221 | BoxLayout:
222 | spacing: 10
223 | orientation: 'horizontal'
224 | size_hint_y: None
225 | height: 35
226 |
227 | Label
228 | text: 'Database'
229 | color: (0, 0, 0.5, 1)
230 | font_size: 24
231 | size_hint_x: None
232 |
233 | TextInput
234 | id: txt_db_database
235 | text: app.txt_db_database
236 | font_size: 18
237 | multiline: False
238 |
239 | BoxLayout:
240 | spacing: 10
241 | orientation: 'horizontal'
242 | size_hint_y: None
243 | height: 35
244 |
245 | Label
246 | text: 'Port'
247 | color: (0, 0, 0.5, 1)
248 | font_size: 24
249 | size_hint_x: None
250 |
251 | TextInput
252 | id: txt_db_port
253 | text: app.txt_db_port
254 | font_size: 18
255 | multiline: False
256 |
257 | BoxLayout:
258 | spacing: 10
259 | orientation: 'horizontal'
260 | size_hint_y: None
261 | height: 35
262 |
263 | Label
264 | text: 'Password'
265 | color: (0, 0, 0.5, 1)
266 | font_size: 24
267 | size_hint_x: None
268 |
269 | TextInput
270 | id: txt_db_password
271 | text: app.txt_db_password
272 | font_size: 18
273 | multiline: False
274 |
275 | BoxLayout:
276 | spacing: 10
277 | orientation: 'horizontal'
278 | size_hint_y: None
279 | height: 35
280 |
281 | Label
282 | text: 'Table'
283 | color: (0, 0, 0.5, 1)
284 | font_size: 24
285 | size_hint_x: None
286 |
287 | TextInput
288 | id: txt_db_table
289 | text: app.txt_db_table
290 | font_size: 18
291 | multiline: False
292 |
293 | GridLayout:
294 | cols: 2
295 | spacing: 250
296 | size_hint_y: None
297 | height: 60
298 | padding: (200, 0)
299 | Button:
300 | text: "Apply"
301 | font_size: 26
302 | on_release: app.on_cam_set(txt_cam1.text, txt_cam2.text, txt_cam3.text, txt_cam4.text, txt_cam5.text, txt_cam6.text, txt_cam7.text, txt_cam8.text, txt_db_host.text, txt_db_port.text, txt_db_user.text, txt_db_password.text, txt_db_database.text, txt_db_table.text)
303 | Button:
304 | text: "Back"
305 | font_size: 26
306 | on_release: app.on_return()
307 |
--------------------------------------------------------------------------------
/logo/1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saimj7/Multiple-Camera-Streams-UI/2512da0a757de04a99c56dca39a01122bc01a412/logo/1.jpg
--------------------------------------------------------------------------------
/mylib/func.py:
--------------------------------------------------------------------------------
1 |
2 | import numpy as np
3 | import os, base64, shutil, csv, json
4 |
5 |
6 | def rm_file(file_name):
7 | if os.path.isfile(file_name):
8 | os.remove(file_name)
9 |
10 |
11 | def rm_tree(folder_name):
12 | shutil.rmtree(folder_name, True)
13 | os.mkdir(folder_name)
14 |
15 |
16 | def img_encode_b64(img_name):
17 | img_file = open(img_name, 'rb')
18 | img_b64_data = base64.b64encode(img_file.read()).decode('UTF-8')
19 | return img_b64_data
20 |
21 |
22 | def img_decode_b64(img_date, img_name):
23 | img_out = open(img_name, 'wb')
24 | img_out.write(img_date.decode('base64'))
25 | img_out.close()
26 |
27 |
28 | def write_text(filename, text):
29 | file1 = open(filename, 'w')
30 | file1.write(text)
31 | file1.close()
32 |
33 |
34 | def append_text(filename, text):
35 | file1 = open(filename, 'a')
36 | file1.write(text)
37 | file1.close()
38 |
39 |
40 | def read_text(filename):
41 | file1 = open(filename, 'r')
42 | text = file1.read()
43 | file1.close()
44 |
45 | return text
46 |
47 |
48 | def get_file_list(root_dir):
49 | path_list = []
50 | file_list = []
51 | join_list = []
52 | for path, _, files in os.walk(root_dir):
53 | for name in files:
54 | path_list.append(path)
55 | file_list.append(name)
56 | join_list.append(os.path.join(path, name))
57 |
58 | return path_list, file_list, join_list
59 |
60 |
61 | def load_csv(filename):
62 | """
63 | load the csv data and return it.
64 | """
65 | if not os.path.isfile(filename):
66 | return []
67 |
68 | file_csv = open(filename, 'r')
69 | reader = csv.reader(file_csv)
70 | data_csv = []
71 | for row_data in reader:
72 | data_csv.append(row_data)
73 |
74 | file_csv.close()
75 | return data_csv
76 |
77 |
78 | def save_csv(filename, data):
79 | """
80 | save the "data" to filename as csv format.
81 | """
82 | file_out = open(filename, 'wb')
83 | writer = csv.writer(file_out)
84 | writer.writerows(data)
85 | file_out.close()
86 |
87 |
88 | def append_csv(filename, data):
89 | file_out = open(filename, 'a')
90 | writer = csv.writer(file_out)
91 | writer.writerows(data)
92 | file_out.close()
93 |
94 |
95 | def get_distance_rect(rect1, rect2):
96 | cx1 = int((rect1[2] + rect1[0]) / 2)
97 | cy1 = int((rect1[3] + rect1[1]) / 2)
98 | cx2 = int((rect2[2] + rect2[0]) / 2)
99 | cy2 = int((rect2[3] + rect2[1]) / 2)
100 | d = (cx2 - cx1) ** 2 + (cy2 - cy1) ** 2
101 |
102 | return np.sqrt(d)
103 |
104 |
105 | def read_json(filename):
106 | with open(filename) as json_file:
107 | data = json.load(json_file)
108 |
109 | return data
110 |
111 |
112 | def write_json(filename, data):
113 | with open(filename, 'w') as outfile:
114 | json.dump(data, outfile, indent=4)
115 |
116 |
117 | def calc_overlap_area(rect1, rect2):
118 | dx = min(rect1[2], rect2[2]) - max(rect1[0], rect2[0])
119 | dy = min(rect1[3], rect2[3]) - max(rect1[1], rect2[1])
120 | if (dx >= 0) and (dy >= 0):
121 | return dx * dy
122 | else:
123 | return 0
124 |
125 |
126 | def check_overlap_rect(rect1, rect2):
127 | # calculate the area of each rects and their overlap
128 | area1 = (rect1[2] - rect1[0]) * (rect1[3] - rect1[1])
129 | area2 = (rect2[2] - rect2[0]) * (rect2[3] - rect2[1])
130 | area_overlap = calc_overlap_area(rect1, rect2)
131 |
132 | # decide 2 rects are 80% overlap or not
133 | if area_overlap / area1 > 0.6 or area_overlap / area2 > 0.6:
134 | return True
135 | else:
136 | return False
137 |
--------------------------------------------------------------------------------
/mylib/main.py:
--------------------------------------------------------------------------------
1 |
2 | import _thread as thread
3 | import mylib.func, time, cv2, sys, os
4 | from mylib.settings import *
5 | from datetime import datetime
6 |
7 |
8 | class WorkCamera:
9 |
10 | def __init__(self, camera_list):
11 | self.camera_list = camera_list
12 | self.cap_list = []
13 | self.frame_list = []
14 | self.update_frame = []
15 | self.ret_image = []
16 | self.detect_rects_list = []
17 | self.detect_scores_list = []
18 | self.frame_ind_list = []
19 | # self.tracker_list = []
20 | self.count_list = []
21 | self.camera_list = camera_list
22 | self.camera_enable = []
23 | self.sql_table = ''
24 |
25 | for i in range(len(camera_list)):
26 | if camera_list[i] == '' or camera_list[i] is None:
27 | self.cap_list.append(None)
28 | else:
29 | self.cap_list.append(cv2.VideoCapture(camera_list[i]))
30 |
31 | # self.tracker_list.append(Tracker())
32 | self.frame_list.append(None)
33 | self.update_frame.append(False)
34 | self.ret_image.append(None)
35 | self.frame_ind_list.append(0)
36 | self.count_list.append(0)
37 | self.camera_enable.append(True)
38 |
39 | # create counting data save folder
40 | self.cur_path = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__)))
41 | self.save_folder = os.path.join(self.cur_path, SAVE_PATH)
42 | if not os.path.isdir(self.save_folder):
43 | os.mkdir(self.save_folder)
44 |
45 | def read_frame(self, camera_ind, scale_factor=1.0):
46 | while True:
47 | if self.cap_list[camera_ind] is None:
48 | self.frame_list[camera_ind] = None
49 |
50 | elif self.camera_enable[camera_ind]:
51 | ret, frame = self.cap_list[camera_ind].read()
52 |
53 | if ret:
54 | if scale_factor != 1.0:
55 | frame = cv2.resize(frame, None, fx=scale_factor, fy=scale_factor)
56 | self.frame_list[camera_ind] = frame
57 | self.ret_image[camera_ind] = frame
58 | self.update_frame[camera_ind] = True
59 | else:
60 | cam_url = self.camera_list[camera_ind]
61 | print("Fail to read camera!", cam_url)
62 | self.cap_list[camera_ind].release()
63 | time.sleep(0.5)
64 | self.cap_list[camera_ind] = cv2.VideoCapture(cam_url)
65 |
66 | time.sleep(0.02)
67 |
68 | def check_valid_detection(self, rect_list, score_list, cam_ind):
69 | check_rect_list = []
70 | check_score_list = []
71 |
72 | for i in range(len(score_list)):
73 | if score_list[i] < DETECTION_THRESHOLD:
74 | continue
75 |
76 | # check ROI
77 | frame_height, frame_width = self.frame_list[cam_ind].shape[:2]
78 | roi_x1 = int(CAMERA_ROI[cam_ind][0] * frame_width)
79 | roi_x2 = int(CAMERA_ROI[cam_ind][2] * frame_width)
80 | roi_y1 = int(CAMERA_ROI[cam_ind][1] * frame_height)
81 | roi_y2 = int(CAMERA_ROI[cam_ind][3] * frame_height)
82 | if rect_list[i][0] < roi_x1 or rect_list[i][2] > roi_x2:
83 | continue
84 | elif rect_list[i][1] < roi_y1 or rect_list[i][3] > roi_y2:
85 | continue
86 |
87 | # check overlap with other rects
88 | f_overlap = False
89 | for j in range(len(check_rect_list)):
90 | if func.check_overlap_rect(check_rect_list[j], rect_list[i]):
91 | if check_score_list[j] < score_list[i]:
92 | check_score_list[j] = score_list[i]
93 | check_rect_list[j] = rect_list[i]
94 | f_overlap = True
95 | break
96 |
97 | if f_overlap:
98 | continue
99 |
100 | # check width/height rate
101 | w = rect_list[i][2] - rect_list[i][0]
102 | h = rect_list[i][3] - rect_list[i][1]
103 | if max(w, h) / min(w, h) > 2:
104 | continue
105 |
106 | # register data
107 | check_rect_list.append(rect_list[i])
108 | check_score_list.append(score_list[i])
109 |
110 | # print(class_list[i], rect_list[i])
111 |
112 | return check_rect_list, check_score_list
113 |
114 | def draw_image(self, img, count, rects, scores, cam_ind):
115 | # draw ROI region
116 | frame_height, frame_width = img.shape[:2]
117 | roi_x1 = int(CAMERA_ROI[cam_ind][0] * frame_width)
118 | roi_x2 = int(CAMERA_ROI[cam_ind][2] * frame_width)
119 | roi_y1 = int(CAMERA_ROI[cam_ind][1] * frame_height)
120 | roi_y2 = int(CAMERA_ROI[cam_ind][3] * frame_height)
121 | cv2.rectangle(img, (roi_x1, roi_y1), (roi_x2, roi_y2), (100, 100, 100), 1)
122 |
123 | # draw objects with rectangle
124 | for i in range(len(rects)):
125 | rect = rects[i]
126 | cv2.rectangle(img, (rect[0], rect[1]), (rect[2], rect[3]), (255, 0, 0), 2)
127 | return img
128 |
129 | def run_thread(self):
130 | # self.class_db.connect(host='78.188.225.187', database='camera', user='root', password='Aa3846sasa*-')
131 | self.sql_table = 'camera'
132 |
133 | for i in range(len(self.cap_list)):
134 | thread.start_new_thread(self.read_frame, (i, RESIZE_FACTOR))
135 |
136 | while True:
137 | for i in range(len(self.cap_list)):
138 | if self.frame_list[i] is not None:
139 | img_org = self.draw_image(self.frame_list[i].copy(),
140 | count=0,
141 | rects=self.detect_rects_list[i],
142 | scores=self.detect_scores_list[i],
143 | cam_ind=i)
144 | cv2.imshow('org' + str(i), img_org)
145 |
146 | key = cv2.waitKey(10)
147 | if key == ord('q'):
148 | break
149 | elif key == ord('s'):
150 | img_gray = cv2.cvtColor(self.frame_list[0], cv2.COLOR_BGR2GRAY)
151 | img_color = cv2.cvtColor(img_gray, cv2.COLOR_GRAY2BGR)
152 | # img_color = img_color[20:-70, 180:-180]
153 | cv2.imwrite(str(time.time()) + '.jpg', img_color)
154 |
155 | def run(self):
156 | frame_ind = 0
157 | while True:
158 | frame_ind += 1
159 | ret, frame = self.cap_list[0].read()
160 | if not ret:
161 | break
162 |
163 | # resize image
164 | if RESIZE_FACTOR != 1.0:
165 | frame = cv2.resize(frame, None, fx=RESIZE_FACTOR, fy=RESIZE_FACTOR)
166 |
167 | # detect
168 | valid_rects = []
169 | valid_scores = []
170 | img_ret = self.draw_image(frame,
171 | 0, valid_rects, valid_scores, 0)
172 |
173 | cv2.imshow('ret', img_ret)
174 | if cv2.waitKey(10) == ord('q'):
175 | break
176 |
177 |
178 | if __name__ == '__main__':
179 | if len(sys.argv) > 1:
180 | cam_list = [sys.argv[1]]
181 | else:
182 | cam_list = CAMERA_URL
183 |
184 | class_work = WorkCamera(cam_list)
185 |
186 | if RUN_MODE_THREAD:
187 | class_work.run_thread()
188 | else:
189 | class_work.run()
190 |
--------------------------------------------------------------------------------
/mylib/misc/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saimj7/Multiple-Camera-Streams-UI/2512da0a757de04a99c56dca39a01122bc01a412/mylib/misc/demo.gif
--------------------------------------------------------------------------------
/mylib/misc/settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saimj7/Multiple-Camera-Streams-UI/2512da0a757de04a99c56dca39a01122bc01a412/mylib/misc/settings.png
--------------------------------------------------------------------------------
/mylib/settings.py:
--------------------------------------------------------------------------------
1 | # config file
2 | CONFIG_FILE = 'config/ui_config.json'
3 |
4 | # roi
5 | CAMERA_ROI = [
6 | [0.14, 0.07, 0.875, 0.89],
7 | [0.01, 0.05, 0.96, 0.95],
8 | [0.01, 0.05, 0.96, 0.95],
9 | [0.01, 0.05, 0.96, 0.95],
10 | [0.01, 0.05, 0.96, 0.95],
11 | [0.01, 0.05, 0.96, 0.95],
12 | [0.01, 0.05, 0.96, 0.95],
13 | [0.01, 0.05, 0.96, 0.95],
14 | ]
15 |
16 | # engine
17 | RUN_MODE_THREAD = True
18 | RESIZE_FACTOR = 1.0
19 | DISPLAY_DETECT_FRAME_ONLY = False
20 | COUNTING_INTEGRATE = True
21 | SEND_SQL = False
22 | SAVE_PATH = 'videos'
23 |
24 | # detector
25 | DETECT_ENABLE = False
26 | DETECTION_THRESHOLD = 0.1
27 |
28 | # tracker
29 | TRACKER_THRESHOLD_DISTANCE = 90 # 90
30 | TRACKER_BUFFER_LENGTH = 100
31 | TRACKER_KEEP_LENGTH = 30
32 |
33 | # UI
34 | PIECE_WIDTH = 510
35 | PIECE_HEIGHT = 300
36 |
--------------------------------------------------------------------------------
/mylib/videos/1.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saimj7/Multiple-Camera-Streams-UI/2512da0a757de04a99c56dca39a01122bc01a412/mylib/videos/1.mp4
--------------------------------------------------------------------------------
/mylib/videos/2.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saimj7/Multiple-Camera-Streams-UI/2512da0a757de04a99c56dca39a01122bc01a412/mylib/videos/2.mp4
--------------------------------------------------------------------------------