diff --git a/.idea/.name b/.idea/.name index 9465664..d581472 100644 --- a/.idea/.name +++ b/.idea/.name @@ -1 +1 @@ -test.py \ No newline at end of file +detection.py \ No newline at end of file diff --git a/Detection_window.py b/Detection_window.py new file mode 100644 index 0000000..68095f1 --- /dev/null +++ b/Detection_window.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'Detection_window.ui' +# +# Created by: PyQt5 UI code generator 5.15.6 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_MainWindow(object): + def setupUi(self, MainWindow): + MainWindow.setObjectName("MainWindow") + MainWindow.resize(1308, 865) + self.centralwidget = QtWidgets.QWidget(MainWindow) + self.centralwidget.setObjectName("centralwidget") + self.gridLayout = QtWidgets.QGridLayout(self.centralwidget) + self.gridLayout.setObjectName("gridLayout") + self.bt_detection = QtWidgets.QPushButton(self.centralwidget) + self.bt_detection.setMinimumSize(QtCore.QSize(100, 50)) + self.bt_detection.setMaximumSize(QtCore.QSize(100, 50)) + self.bt_detection.setObjectName("bt_detection") + self.gridLayout.addWidget(self.bt_detection, 0, 0, 1, 1) + self.view_origin = QtWidgets.QLabel(self.centralwidget) + self.view_origin.setMinimumSize(QtCore.QSize(1000, 1000)) + self.view_origin.setMaximumSize(QtCore.QSize(1000, 1000)) + self.view_origin.setObjectName("view_origin") + self.gridLayout.addWidget(self.view_origin, 1, 0, 1, 1) + self.view_predict = QtWidgets.QLabel(self.centralwidget) + self.view_predict.setMinimumSize(QtCore.QSize(1000, 1000)) + self.view_predict.setMaximumSize(QtCore.QSize(1000, 1000)) + self.view_predict.setObjectName("view_predict") + self.gridLayout.addWidget(self.view_predict, 1, 1, 1, 1) + MainWindow.setCentralWidget(self.centralwidget) + self.menubar = QtWidgets.QMenuBar(MainWindow) + self.menubar.setGeometry(QtCore.QRect(0, 0, 1308, 22)) + self.menubar.setObjectName("menubar") + MainWindow.setMenuBar(self.menubar) + self.statusbar = QtWidgets.QStatusBar(MainWindow) + self.statusbar.setObjectName("statusbar") + MainWindow.setStatusBar(self.statusbar) + + self.retranslateUi(MainWindow) + QtCore.QMetaObject.connectSlotsByName(MainWindow) + + def retranslateUi(self, MainWindow): + _translate = QtCore.QCoreApplication.translate + MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow")) + self.bt_detection.setText(_translate("MainWindow", "檢測")) + self.view_origin.setText(_translate("MainWindow", "原圖影像")) + self.view_predict.setText(_translate("MainWindow", "檢測影像")) diff --git a/Detection_window.ui b/Detection_window.ui new file mode 100644 index 0000000..3f32aa8 --- /dev/null +++ b/Detection_window.ui @@ -0,0 +1,91 @@ + + + MainWindow + + + + 0 + 0 + 1308 + 865 + + + + MainWindow + + + + + + + + 100 + 50 + + + + + 100 + 50 + + + + 檢測 + + + + + + + + 1000 + 1000 + + + + + 1000 + 1000 + + + + 原圖影像 + + + + + + + + 1000 + 1000 + + + + + 1000 + 1000 + + + + 檢測影像 + + + + + + + + + 0 + 0 + 1308 + 22 + + + + + + + + diff --git a/camera/camera.py b/camera/camera.py index 6f8025c..c919ea8 100644 --- a/camera/camera.py +++ b/camera/camera.py @@ -7,7 +7,6 @@ import cv2 import numpy as np from camera_window import Ui_MainWindow - class CameraThread(QThread): update_image_signal = pyqtSignal(np.ndarray) # 訊號:傳遞影像數據 @@ -45,9 +44,6 @@ class CameraThread(QThread): except Exception as e: print(f"影像擷取錯誤: {e}") - except Exception as e: - print(f"影像擷取錯誤: {e}") - def stop(self): """停止執行緒""" self.running = False @@ -57,9 +53,9 @@ class CameraThread(QThread): self.wait() # 確保執行緒完全停止 -class CameraApp(QtWidgets.QMainWindow, Ui_MainWindow): +class Camera(QtWidgets.QMainWindow, Ui_MainWindow): def __init__(self): - super(CameraApp, self).__init__() + super(Camera, self).__init__() self.setupUi(self) # 連接按鈕事件 @@ -76,6 +72,7 @@ class CameraApp(QtWidgets.QMainWindow, Ui_MainWindow): self.camera = None self.camera_thread = None self.current_image = None + self.latest_image = None # 用於存儲最新影像 self.last_exposure_time = None # 記錄最後的曝光時間,避免重複更新 def connect_camera(self): @@ -83,9 +80,11 @@ class CameraApp(QtWidgets.QMainWindow, Ui_MainWindow): try: self.camera = pylon.InstantCamera(pylon.TlFactory.GetInstance().CreateFirstDevice()) self.camera.Open() - self.statusbar.showMessage("相機連線成功") + print("相機連線成功") + return True except Exception as e: - self.statusbar.showMessage(f"相機連線失敗: {e}") + print(f"相機連線失敗: {e}") + return False def get_exposure_time(self): """取得曝光時間,若為空則使用預設值""" @@ -102,23 +101,34 @@ class CameraApp(QtWidgets.QMainWindow, Ui_MainWindow): if not (self.camera and self.camera.IsOpen()): return - new_exposure_time = self.get_exposure_time() + try: + new_exposure_time = self.get_exposure_time() - # 避免重複設定相同的曝光時間,降低 CPU 負載 - if new_exposure_time == self.last_exposure_time: - return + # 確保曝光時間在相機允許的範圍內 + min_exp = self.camera.ExposureTime.GetMin() + max_exp = self.camera.ExposureTime.GetMax() - self.last_exposure_time = new_exposure_time # 更新最後的曝光時間 + if new_exposure_time < min_exp or new_exposure_time > max_exp: + self.statusbar.showMessage(f"曝光時間超出範圍 ({min_exp} ~ {max_exp}),請輸入有效值") + return - self.camera.ExposureTime.SetValue(float(new_exposure_time)) - self.statusbar.showMessage(f"曝光時間更新為 {new_exposure_time} 微秒") + # 避免重複設定相同的曝光時間 + if new_exposure_time == self.last_exposure_time: + return - # 若相機正在連續擷取,直接應用新的曝光時間 - if self.camera_thread and self.camera_thread.isRunning(): - self.stop_keep_shot() - self.keep_shot_capture() - else: - self.one_shot_capture() + self.last_exposure_time = new_exposure_time + self.camera.ExposureTime.SetValue(float(new_exposure_time)) # 設定曝光時間 + self.statusbar.showMessage(f"曝光時間更新為 {new_exposure_time} 微秒") + + # 重新擷取影像 + if self.camera_thread and self.camera_thread.isRunning(): + self.stop_keep_shot() + self.keep_shot_capture() + else: + self.one_shot_capture() + + except Exception as e: + self.statusbar.showMessage(f"設定曝光時間失敗: {e}") def one_shot_capture(self): """單張擷取""" @@ -189,6 +199,6 @@ class CameraApp(QtWidgets.QMainWindow, Ui_MainWindow): if __name__ == "__main__": app = QtWidgets.QApplication(sys.argv) - window = CameraApp() + window = Camera() window.show() sys.exit(app.exec_()) diff --git a/camera/camera_process.py b/camera/camera_process.py new file mode 100644 index 0000000..73fff3c --- /dev/null +++ b/camera/camera_process.py @@ -0,0 +1,49 @@ +import time +import numpy as np +import cv2 +import multiprocessing +from pypylon import pylon + +class CameraProcess(multiprocessing.Process): + """ 相機擷取獨立進程 """ + def __init__(self, image_queue, exposure_time=20000): + super(CameraProcess, self).__init__() + self.image_queue = image_queue + self.exposure_time = exposure_time + self.running = multiprocessing.Value('b', True) # 控制進程是否運行 + self.camera = None + + def connect_camera(self): + """ 連接相機 """ + try: + self.camera = pylon.InstantCamera(pylon.TlFactory.GetInstance().CreateFirstDevice()) + self.camera.Open() + self.camera.ExposureTime.SetValue(float(self.exposure_time)) + print("相機連線成功") + return True + except Exception as e: + print(f"相機連線失敗: {e}") + return False + + def run(self): + """ 進程啟動後執行影像擷取 """ + if not self.connect_camera(): + return + + self.camera.StartGrabbing(pylon.GrabStrategy_LatestImageOnly) + + while self.running.value and self.camera.IsGrabbing(): + grab_result = self.camera.RetrieveResult(20000, pylon.TimeoutHandling_ThrowException) + if grab_result.GrabSucceeded(): + image = grab_result.Array # 取得影像數據 + if not self.image_queue.full(): + self.image_queue.put(image) # 將影像放入 Queue + grab_result.Release() + time.sleep(0.05) # 控制擷取速度,避免 CPU 過載 + + self.camera.Close() + print("相機關閉") + + def stop(self): + """ 停止相機擷取進程 """ + self.running.value = False diff --git a/detection.py b/detection.py new file mode 100644 index 0000000..652b1d5 --- /dev/null +++ b/detection.py @@ -0,0 +1,52 @@ +import sys +import cv2 +import numpy as np +import multiprocessing +from PyQt5 import QtWidgets, QtGui, QtCore +from PyQt5.QtCore import QTimer +from Detection_window import Ui_MainWindow +from camera.camera_process import CameraProcess + +class DetectionApp(QtWidgets.QMainWindow, Ui_MainWindow): + def __init__(self): + super(DetectionApp, self).__init__() + self.setupUi(self) + + self.image_queue = multiprocessing.Queue(maxsize=1) + + # ✅ 啟動 CameraProcess + self.camera_process = CameraProcess(self.image_queue) + self.camera_process.start() + + # ✅ 設定 QTimer,每 100ms 更新影像 + self.timer = QTimer(self) + self.timer.timeout.connect(self.update_view_origin) + self.timer.start(100) # 每 100ms 更新一次影像 + + def update_view_origin(self): + """ 從 Queue 獲取影像並顯示在 QLabel (view_origin) 上 """ + if not self.image_queue.empty(): + image = self.image_queue.get() # 取得最新影像 + self.display_image(image) + + def display_image(self, image): + """ 顯示影像到 QLabel (view_origin) """ + image_bgr = cv2.cvtColor(image, cv2.COLOR_BayerBG2BGR) if len(image.shape) == 2 else image + height, width, channel = image_bgr.shape + bytes_per_line = 3 * width + qimage = QtGui.QImage(image_bgr.data, width, height, bytes_per_line, QtGui.QImage.Format_BGR888) + pixmap = QtGui.QPixmap.fromImage(qimage).scaled(self.view_origin.size(), QtCore.Qt.KeepAspectRatio) + self.view_origin.setPixmap(pixmap) + + def closeEvent(self, event): + """ 確保程式關閉時正確停止相機擷取進程 """ + self.camera_process.stop() # 停止相機擷取 + self.camera_process.join() # 等待進程結束 + event.accept() + + +if __name__ == "__main__": + app = QtWidgets.QApplication(sys.argv) + window = DetectionApp() + window.show() + sys.exit(app.exec_()) diff --git a/main.ui b/main.ui deleted file mode 100644 index 14843c1..0000000 --- a/main.ui +++ /dev/null @@ -1,58 +0,0 @@ - - - MainWindow - - - - 0 - 0 - 1308 - 865 - - - - MainWindow - - - - - - 70 - 110 - 471 - 461 - - - - TextLabel - - - - - - 70 - 40 - 91 - 61 - - - - PushButton - - - - - - - 0 - 0 - 1308 - 22 - - - - - - - -