├── README.md └── Streaming_IP_Camera_Using_PyQt_OpenCV.py /README.md: -------------------------------------------------------------------------------- 1 | # Streaming-IP-Cameras-Using-PyQt-and-OpenCV 2 | This project shows how to create a security camera system using OpenCV to stream IP cameras 3 | and PyQt to create the graphical interface (GUI). 4 | 5 | OpenCV (Open Source Computer Vision Library) is an open source computer vision and machine learning software library. 6 | In this project OpenCV is used to read the frames from an IP camera. 7 | 8 | PyQt connects the Qt C++ cross-platform framework with the Python language, it is a GUI module. 9 | This module takes care of the graphical interface and layout each camera in a grid layout. 10 | 11 | # The following video shows the appication streaming four cameras. 12 | https://user-images.githubusercontent.com/5813359/158277130-f4377128-fa7e-455d-a18b-fdf332429b4b.mp4 13 | -------------------------------------------------------------------------------- /Streaming_IP_Camera_Using_PyQt_OpenCV.py: -------------------------------------------------------------------------------- 1 | # import the require packages. 2 | import cv2 3 | from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, \ 4 | QLabel, QGridLayout, QScrollArea, QSizePolicy 5 | from PyQt5.QtGui import QPixmap, QIcon, QImage, QPalette 6 | from PyQt5.QtCore import QThread, pyqtSignal, Qt, QEvent, QObject 7 | from PyQt5 import QtCore 8 | import sys 9 | 10 | 11 | class CaptureIpCameraFramesWorker(QThread): 12 | # Signal emitted when a new image or a new frame is ready. 13 | ImageUpdated = pyqtSignal(QImage) 14 | 15 | def __init__(self, url) -> None: 16 | super(CaptureIpCameraFramesWorker, self).__init__() 17 | # Declare and initialize instance variables. 18 | self.url = url 19 | self.__thread_active = True 20 | self.fps = 0 21 | self.__thread_pause = False 22 | 23 | def run(self) -> None: 24 | # Capture video from a network stream. 25 | cap = cv2.VideoCapture(self.url, cv2.CAP_FFMPEG) 26 | # Get default video FPS. 27 | self.fps = cap.get(cv2.CAP_PROP_FPS) 28 | print(self.fps) 29 | # If video capturing has been initialized already.q 30 | if cap.isOpened(): 31 | # While the thread is active. 32 | while self.__thread_active: 33 | # 34 | if not self.__thread_pause: 35 | # Grabs, decodes and returns the next video frame. 36 | ret, frame = cap.read() 37 | # If frame is read correctly. 38 | if ret: 39 | # Get the frame height, width and channels. 40 | height, width, channels = frame.shape 41 | # Calculate the number of bytes per line. 42 | bytes_per_line = width * channels 43 | # Convert image from BGR (cv2 default color format) to RGB (Qt default color format). 44 | cv_rgb_image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) 45 | # Convert the image to Qt format. 46 | qt_rgb_image = QImage(cv_rgb_image.data, width, height, bytes_per_line, QImage.Format_RGB888) 47 | # Scale the image. 48 | # NOTE: consider removing the flag Qt.KeepAspectRatio as it will crash Python on older Windows machines 49 | # If this is the case, call instead: qt_rgb_image.scaled(1280, 720) 50 | qt_rgb_image_scaled = qt_rgb_image.scaled(1280, 720, Qt.KeepAspectRatio) # 720p 51 | # qt_rgb_image_scaled = qt_rgb_image.scaled(1920, 1080, Qt.KeepAspectRatio) 52 | # Emit this signal to notify that a new image or frame is available. 53 | self.ImageUpdated.emit(qt_rgb_image_scaled) 54 | else: 55 | break 56 | # When everything done, release the video capture object. 57 | cap.release() 58 | # Tells the thread's event loop to exit with return code 0 (success). 59 | self.quit() 60 | 61 | def stop(self) -> None: 62 | self.__thread_active = False 63 | 64 | def pause(self) -> None: 65 | self.__thread_pause = True 66 | 67 | def unpause(self) -> None: 68 | self.__thread_pause = False 69 | 70 | 71 | class MainWindow(QMainWindow): 72 | 73 | def __init__(self) -> None: 74 | super(MainWindow, self).__init__() 75 | 76 | # rtsp://:@:/cam/realmonitor?channel=1&subtype=0 77 | self.url_1 = "rtsp://:@:/cam/realmonitor?channel=1&subtype=0" 78 | self.url_2 = "rtsp://:@:/cam/realmonitor?channel=1&subtype=0" 79 | self.url_3 = "rtsp://:@:/cam/realmonitor?channel=1&subtype=0" 80 | 81 | # Dictionary to keep the state of a camera. The camera state will be: Normal or Maximized. 82 | self.list_of_cameras_state = {} 83 | 84 | # Create an instance of a QLabel class to show camera 1. 85 | self.camera_1 = QLabel() 86 | self.camera_1.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) 87 | self.camera_1.setScaledContents(True) 88 | self.camera_1.installEventFilter(self) 89 | self.camera_1.setObjectName("Camera_1") 90 | self.list_of_cameras_state["Camera_1"] = "Normal" 91 | 92 | # Create an instance of a QScrollArea class to scroll camera 1 image. 93 | self.QScrollArea_1 = QScrollArea() 94 | self.QScrollArea_1.setBackgroundRole(QPalette.Dark) 95 | self.QScrollArea_1.setWidgetResizable(True) 96 | self.QScrollArea_1.setWidget(self.camera_1) 97 | 98 | # Create an instance of a QLabel class to show camera 2. 99 | self.camera_2 = QLabel() 100 | self.camera_2.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) 101 | self.camera_2.setScaledContents(True) 102 | self.camera_2.installEventFilter(self) 103 | self.camera_2.setObjectName("Camera_2") 104 | self.list_of_cameras_state["Camera_2"] = "Normal" 105 | 106 | # Create an instance of a QScrollArea class to scroll camera 2 image. 107 | self.QScrollArea_2 = QScrollArea() 108 | self.QScrollArea_2.setBackgroundRole(QPalette.Dark) 109 | self.QScrollArea_2.setWidgetResizable(True) 110 | self.QScrollArea_2.setWidget(self.camera_2) 111 | 112 | # Create an instance of a QLabel class to show camera 3. 113 | self.camera_3 = QLabel() 114 | self.camera_3.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) 115 | self.camera_3.setScaledContents(True) 116 | self.camera_3.installEventFilter(self) 117 | self.camera_3.setObjectName("Camera_3") 118 | self.list_of_cameras_state["Camera_3"] = "Normal" 119 | 120 | # Create an instance of a QScrollArea class to scroll camera 3 image. 121 | self.QScrollArea_3 = QScrollArea() 122 | self.QScrollArea_3.setBackgroundRole(QPalette.Dark) 123 | self.QScrollArea_3.setWidgetResizable(True) 124 | self.QScrollArea_3.setWidget(self.camera_3) 125 | 126 | # Create an instance of a QLabel class to show camera 4. 127 | self.camera_4 = QLabel() 128 | self.camera_4.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) 129 | self.camera_4.setScaledContents(True) 130 | self.camera_4.installEventFilter(self) 131 | self.camera_4.setObjectName("Camera_4") 132 | self.list_of_cameras_state["Camera_4"] = "Normal" 133 | 134 | # Create an instance of a QScrollArea class to scroll camera 4 image. 135 | self.QScrollArea_4 = QScrollArea() 136 | self.QScrollArea_4.setBackgroundRole(QPalette.Dark) 137 | self.QScrollArea_4.setWidgetResizable(True) 138 | self.QScrollArea_4.setWidget(self.camera_4) 139 | 140 | # Set the UI elements for this Widget class. 141 | self.__SetupUI() 142 | 143 | # Create an instance of CaptureIpCameraFramesWorker. 144 | self.CaptureIpCameraFramesWorker_1 = CaptureIpCameraFramesWorker(self.url_1) 145 | self.CaptureIpCameraFramesWorker_1.ImageUpdated.connect(lambda image: self.ShowCamera1(image)) 146 | 147 | # Create an instance of CaptureIpCameraFramesWorker. 148 | self.CaptureIpCameraFramesWorker_2 = CaptureIpCameraFramesWorker(self.url_2) 149 | self.CaptureIpCameraFramesWorker_2.ImageUpdated.connect(lambda image: self.ShowCamera2(image)) 150 | 151 | # Create an instance of CaptureIpCameraFramesWorker. 152 | self.CaptureIpCameraFramesWorker_3 = CaptureIpCameraFramesWorker(self.url_3) 153 | self.CaptureIpCameraFramesWorker_3.ImageUpdated.connect(lambda image: self.ShowCamera3(image)) 154 | 155 | # Create an instance of CaptureIpCameraFramesWorker. 156 | self.CaptureIpCameraFramesWorker_4 = CaptureIpCameraFramesWorker(self.url_1) 157 | self.CaptureIpCameraFramesWorker_4.ImageUpdated.connect(lambda image: self.ShowCamera4(image)) 158 | 159 | # Start the thread getIpCameraFrameWorker_1. 160 | self.CaptureIpCameraFramesWorker_1.start() 161 | 162 | # Start the thread getIpCameraFrameWorker_2. 163 | self.CaptureIpCameraFramesWorker_2.start() 164 | 165 | # Start the thread getIpCameraFrameWorker_3. 166 | self.CaptureIpCameraFramesWorker_3.start() 167 | 168 | # Start the thread getIpCameraFrameWorker_4. 169 | self.CaptureIpCameraFramesWorker_4.start() 170 | 171 | def __SetupUI(self) -> None: 172 | # Create an instance of a QGridLayout layout. 173 | grid_layout = QGridLayout() 174 | grid_layout.setContentsMargins(0, 0, 0, 0) 175 | grid_layout.addWidget(self.QScrollArea_1, 0, 0) 176 | grid_layout.addWidget(self.QScrollArea_2, 0, 1) 177 | grid_layout.addWidget(self.QScrollArea_3, 1, 0) 178 | grid_layout.addWidget(self.QScrollArea_4, 1, 1) 179 | 180 | # Create a widget instance. 181 | self.widget = QWidget(self) 182 | self.widget.setLayout(grid_layout) 183 | 184 | # Set the central widget. 185 | self.setCentralWidget(self.widget) 186 | self.setMinimumSize(800, 600) 187 | self.showMaximized() 188 | self.setStyleSheet("QMainWindow {background: 'black';}") 189 | self.setWindowIcon(QIcon(QPixmap("camera_2.png"))) 190 | # Set window title. 191 | self.setWindowTitle("IP Camera System") 192 | 193 | @QtCore.pyqtSlot() 194 | def ShowCamera1(self, frame: QImage) -> None: 195 | self.camera_1.setPixmap(QPixmap.fromImage(frame)) 196 | 197 | @QtCore.pyqtSlot() 198 | def ShowCamera2(self, frame: QImage) -> None: 199 | self.camera_2.setPixmap(QPixmap.fromImage(frame)) 200 | 201 | @QtCore.pyqtSlot() 202 | def ShowCamera3(self, frame: QImage) -> None: 203 | self.camera_3.setPixmap(QPixmap.fromImage(frame)) 204 | 205 | @QtCore.pyqtSlot() 206 | def ShowCamera4(self, frame: QImage) -> None: 207 | self.camera_4.setPixmap(QPixmap.fromImage(frame)) 208 | 209 | # Override method for class MainWindow. 210 | def eventFilter(self, source: QObject, event: QEvent) -> bool: 211 | """ 212 | Method to capture the events for objects with an event filter installed. 213 | :param source: The object for whom an event took place. 214 | :param event: The event that took place. 215 | :return: True if event is handled. 216 | """ 217 | # 218 | if event.type() == QtCore.QEvent.MouseButtonDblClick: 219 | if source.objectName() == 'Camera_1': 220 | # 221 | if self.list_of_cameras_state["Camera_1"] == "Normal": 222 | self.QScrollArea_2.hide() 223 | self.QScrollArea_3.hide() 224 | self.QScrollArea_4.hide() 225 | self.list_of_cameras_state["Camera_1"] = "Maximized" 226 | else: 227 | self.QScrollArea_2.show() 228 | self.QScrollArea_3.show() 229 | self.QScrollArea_4.show() 230 | self.list_of_cameras_state["Camera_1"] = "Normal" 231 | elif source.objectName() == 'Camera_2': 232 | # 233 | if self.list_of_cameras_state["Camera_2"] == "Normal": 234 | self.QScrollArea_1.hide() 235 | self.QScrollArea_3.hide() 236 | self.QScrollArea_4.hide() 237 | self.list_of_cameras_state["Camera_2"] = "Maximized" 238 | else: 239 | self.QScrollArea_1.show() 240 | self.QScrollArea_3.show() 241 | self.QScrollArea_4.show() 242 | self.list_of_cameras_state["Camera_2"] = "Normal" 243 | elif source.objectName() == 'Camera_3': 244 | # 245 | if self.list_of_cameras_state["Camera_3"] == "Normal": 246 | self.QScrollArea_1.hide() 247 | self.QScrollArea_2.hide() 248 | self.QScrollArea_4.hide() 249 | self.list_of_cameras_state["Camera_3"] = "Maximized" 250 | else: 251 | self.QScrollArea_1.show() 252 | self.QScrollArea_2.show() 253 | self.QScrollArea_4.show() 254 | self.list_of_cameras_state["Camera_3"] = "Normal" 255 | elif source.objectName() == 'Camera_4': 256 | # 257 | if self.list_of_cameras_state["Camera_4"] == "Normal": 258 | self.QScrollArea_1.hide() 259 | self.QScrollArea_2.hide() 260 | self.QScrollArea_3.hide() 261 | self.list_of_cameras_state["Camera_4"] = "Maximized" 262 | else: 263 | self.QScrollArea_1.show() 264 | self.QScrollArea_2.show() 265 | self.QScrollArea_3.show() 266 | self.list_of_cameras_state["Camera_4"] = "Normal" 267 | else: 268 | return super(MainWindow, self).eventFilter(source, event) 269 | return True 270 | else: 271 | return super(MainWindow, self).eventFilter(source, event) 272 | 273 | # Overwrite method closeEvent from class QMainWindow. 274 | def closeEvent(self, event) -> None: 275 | # If thread getIpCameraFrameWorker_1 is running, then exit it. 276 | if self.CaptureIpCameraFramesWorker_1.isRunning(): 277 | self.CaptureIpCameraFramesWorker_1.quit() 278 | # If thread getIpCameraFrameWorker_2 is running, then exit it. 279 | if self.CaptureIpCameraFramesWorker_2.isRunning(): 280 | self.CaptureIpCameraFramesWorker_2.quit() 281 | # If thread getIpCameraFrameWorker_3 is running, then exit it. 282 | if self.CaptureIpCameraFramesWorker_3.isRunning(): 283 | self.CaptureIpCameraFramesWorker_3.quit() 284 | # Accept the event 285 | event.accept() 286 | 287 | 288 | def main() -> None: 289 | # Create a QApplication object. It manages the GUI application's control flow and main settings. 290 | # It handles widget specific initialization, finalization. 291 | # For any GUI application using Qt, there is precisely one QApplication object 292 | app = QApplication(sys.argv) 293 | # Create an instance of the class MainWindow. 294 | window = MainWindow() 295 | # Show the window. 296 | window.show() 297 | # Start Qt event loop. 298 | sys.exit(app.exec_()) 299 | 300 | 301 | if __name__ == '__main__': 302 | main() 303 | --------------------------------------------------------------------------------