├── .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 --------------------------------------------------------------------------------