修改整體架構

This commit is contained in:
JEFF 2025-03-10 11:30:13 +08:00
parent cea99ab198
commit 7eeba44dfe
7 changed files with 279 additions and 81 deletions

2
.idea/.name generated
View File

@ -1 +1 @@
test.py
detection.py

54
Detection_window.py Normal file
View File

@ -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", "檢測影像"))

91
Detection_window.ui Normal file
View File

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1308</width>
<height>865</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QPushButton" name="bt_detection">
<property name="minimumSize">
<size>
<width>100</width>
<height>50</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>50</height>
</size>
</property>
<property name="text">
<string>檢測</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="view_origin">
<property name="minimumSize">
<size>
<width>1000</width>
<height>1000</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>1000</width>
<height>1000</height>
</size>
</property>
<property name="text">
<string>原圖影像</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="view_predict">
<property name="minimumSize">
<size>
<width>1000</width>
<height>1000</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>1000</width>
<height>1000</height>
</size>
</property>
<property name="text">
<string>檢測影像</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1308</width>
<height>22</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -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,24 +101,35 @@ class CameraApp(QtWidgets.QMainWindow, Ui_MainWindow):
if not (self.camera and self.camera.IsOpen()):
return
try:
new_exposure_time = self.get_exposure_time()
# 避免重複設定相同的曝光時間,降低 CPU 負載
# 確保曝光時間在相機允許的範圍內
min_exp = self.camera.ExposureTime.GetMin()
max_exp = self.camera.ExposureTime.GetMax()
if new_exposure_time < min_exp or new_exposure_time > max_exp:
self.statusbar.showMessage(f"曝光時間超出範圍 ({min_exp} ~ {max_exp}),請輸入有效值")
return
# 避免重複設定相同的曝光時間
if new_exposure_time == self.last_exposure_time:
return
self.last_exposure_time = new_exposure_time # 更新最後的曝光時間
self.camera.ExposureTime.SetValue(float(new_exposure_time))
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):
"""單張擷取"""
if not (self.camera and self.camera.IsOpen()):
@ -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_())

49
camera/camera_process.py Normal file
View File

@ -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

52
detection.py Normal file
View File

@ -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_())

58
main.ui
View File

@ -1,58 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1308</width>
<height>865</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<widget class="QLabel" name="label">
<property name="geometry">
<rect>
<x>70</x>
<y>110</y>
<width>471</width>
<height>461</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QPushButton" name="pushButton">
<property name="geometry">
<rect>
<x>70</x>
<y>40</y>
<width>91</width>
<height>61</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1308</width>
<height>22</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>