228 lines
9.0 KiB
Python
228 lines
9.0 KiB
Python
|
import sys
|
|||
|
from PyQt5 import QtWidgets, QtGui, QtCore
|
|||
|
from PyQt5.QtCore import QTimer
|
|||
|
from PyQt5.QtWidgets import QFileDialog
|
|||
|
from pypylon import pylon
|
|||
|
import cv2
|
|||
|
import numpy as np
|
|||
|
import os
|
|||
|
from Main import Ui_MainWindow
|
|||
|
|
|||
|
class CameraApp(QtWidgets.QMainWindow, Ui_MainWindow):
|
|||
|
def __init__(self):
|
|||
|
super(CameraApp, self).__init__()
|
|||
|
self.setupUi(self)
|
|||
|
|
|||
|
# 連接按鈕事件
|
|||
|
self.bt_camera_connect.clicked.connect(self.connect_camera)
|
|||
|
self.bt_OneShot.clicked.connect(self.one_shot_capture)
|
|||
|
self.bt_KeetShot.clicked.connect(self.keep_shot_capture)
|
|||
|
self.bt_open_image.clicked.connect(self.open_image)
|
|||
|
self.bt_save_image.clicked.connect(self.save_image) # 連接儲存影像按鈕事件
|
|||
|
|
|||
|
|
|||
|
# 初始化變數
|
|||
|
self.camera = None
|
|||
|
self.timer = QTimer()
|
|||
|
self.timer.timeout.connect(self.keep_shot_process)
|
|||
|
|
|||
|
# 設定 QSlider 範圍和事件
|
|||
|
self.threshold_value.setMinimum(0)
|
|||
|
self.threshold_value.setMaximum(255)
|
|||
|
self.threshold_value.setValue(127) # 預設閥值
|
|||
|
self.threshold_value.valueChanged.connect(self.update_threshold)
|
|||
|
|
|||
|
self.threshold.setText(str(self.threshold_value.value()))
|
|||
|
self.current_image = None
|
|||
|
|
|||
|
def get_bgr_image(self):
|
|||
|
"""取得 BGR 格式的影像"""
|
|||
|
if self.current_image is None:
|
|||
|
return None
|
|||
|
|
|||
|
# 若影像為單通道或 Bayer 格式,進行轉換
|
|||
|
if len(self.current_image.shape) == 2 or self.current_image.shape[2] == 1:
|
|||
|
return cv2.cvtColor(self.current_image, cv2.COLOR_BayerBG2BGR)
|
|||
|
else:
|
|||
|
return self.current_image
|
|||
|
|
|||
|
def connect_camera(self):
|
|||
|
try:
|
|||
|
self.camera = pylon.InstantCamera(pylon.TlFactory.GetInstance().CreateFirstDevice())
|
|||
|
self.camera.Open()
|
|||
|
self.statusbar.showMessage("相機連線成功")
|
|||
|
except Exception as e:
|
|||
|
self.statusbar.showMessage(f"相機連線失敗: {e}")
|
|||
|
|
|||
|
def get_exposure_time(self):
|
|||
|
"""從 QTextEdit 取得曝光時間,若為空則使用預設值"""
|
|||
|
exposure_time_text = self.Ex_time.toPlainText().strip()
|
|||
|
|
|||
|
# 當 QTextEdit 值為空時使用預設值
|
|||
|
if not exposure_time_text:
|
|||
|
return 5000 # 預設曝光時間(微秒)
|
|||
|
|
|||
|
# 驗證是否為數值
|
|||
|
if exposure_time_text.isdigit():
|
|||
|
return int(exposure_time_text)
|
|||
|
|
|||
|
self.statusbar.showMessage("請輸入有效的曝光時間(整數),使用預設值 5000 微秒")
|
|||
|
return 5000
|
|||
|
|
|||
|
def one_shot_capture(self):
|
|||
|
if not (self.camera and self.camera.IsOpen()):
|
|||
|
self.statusbar.showMessage("請先連接相機")
|
|||
|
return
|
|||
|
|
|||
|
try:
|
|||
|
exposure_time = self.get_exposure_time()
|
|||
|
self.camera.ExposureTime.SetValue(float(exposure_time))
|
|||
|
self.statusbar.showMessage(f"曝光時間設定為 {exposure_time} 微秒")
|
|||
|
|
|||
|
if self.camera.IsGrabbing():
|
|||
|
self.camera.StopGrabbing()
|
|||
|
|
|||
|
self.camera.StartGrabbing(1)
|
|||
|
grab_result = self.camera.RetrieveResult(5000, pylon.TimeoutHandling_Return)
|
|||
|
|
|||
|
if grab_result.GrabSucceeded():
|
|||
|
self.current_image = grab_result.Array
|
|||
|
self.display_original_image()
|
|||
|
self.apply_threshold_and_display()
|
|||
|
self.statusbar.showMessage("單張擷取成功")
|
|||
|
else:
|
|||
|
self.statusbar.showMessage("擷取失敗")
|
|||
|
|
|||
|
grab_result.Release()
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
self.statusbar.showMessage(f"擷取失敗: {e}")
|
|||
|
if self.camera.IsGrabbing():
|
|||
|
self.camera.StopGrabbing()
|
|||
|
|
|||
|
def display_original_image(self):
|
|||
|
"""顯示原始影像到 label 上"""
|
|||
|
if self.current_image is not None:
|
|||
|
# 判斷是否需要轉換 Bayer 格式
|
|||
|
if len(self.current_image.shape) == 2 or self.current_image.shape[2] == 1:
|
|||
|
# 影像來自相機(Bayer 格式)
|
|||
|
image_bgr = cv2.cvtColor(self.current_image, cv2.COLOR_BayerBG2BGR)
|
|||
|
else:
|
|||
|
# 影像已經是 BGR 格式
|
|||
|
image_bgr = self.current_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.label.size(), QtCore.Qt.KeepAspectRatio)
|
|||
|
self.label.setPixmap(pixmap)
|
|||
|
|
|||
|
def apply_threshold_and_display(self):
|
|||
|
"""套用二值化處理並顯示影像到 label_2 上"""
|
|||
|
if self.current_image is not None:
|
|||
|
# 判斷是否需要轉換 Bayer 格式
|
|||
|
if len(self.current_image.shape) == 2 or self.current_image.shape[2] == 1:
|
|||
|
# 影像來自相機(Bayer 格式)
|
|||
|
image_bgr = cv2.cvtColor(self.current_image, cv2.COLOR_BayerBG2BGR)
|
|||
|
else:
|
|||
|
# 影像已經是 BGR 格式
|
|||
|
image_bgr = self.current_image
|
|||
|
|
|||
|
# 轉換為灰階
|
|||
|
image_gray = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2GRAY)
|
|||
|
|
|||
|
# 套用二值化處理
|
|||
|
threshold_value = self.threshold_value.value()
|
|||
|
_, thresholded_image = cv2.threshold(image_gray, threshold_value, 255, cv2.THRESH_BINARY)
|
|||
|
|
|||
|
# 顯示影像
|
|||
|
height, width = thresholded_image.shape
|
|||
|
bytes_per_line = width
|
|||
|
qimage = QtGui.QImage(thresholded_image.data, width, height, bytes_per_line, QtGui.QImage.Format_Grayscale8)
|
|||
|
pixmap = QtGui.QPixmap.fromImage(qimage).scaled(self.label_2.size(), QtCore.Qt.KeepAspectRatio)
|
|||
|
self.label_2.setPixmap(pixmap)
|
|||
|
|
|||
|
def update_threshold(self):
|
|||
|
"""當 QSlider 值改變時更新影像"""
|
|||
|
self.apply_threshold_and_display()
|
|||
|
# 更新 QTextEdit 顯示的值
|
|||
|
self.threshold.setText(str(self.threshold_value.value()))
|
|||
|
|
|||
|
def keep_shot_capture(self):
|
|||
|
if self.camera and self.camera.IsOpen():
|
|||
|
if not self.timer.isActive():
|
|||
|
self.camera.StartGrabbing(pylon.GrabStrategy_LatestImageOnly)
|
|||
|
self.timer.start(30)
|
|||
|
self.statusbar.showMessage("開始連續取像")
|
|||
|
else:
|
|||
|
self.timer.stop()
|
|||
|
self.camera.StopGrabbing()
|
|||
|
self.statusbar.showMessage("停止連續取像")
|
|||
|
else:
|
|||
|
self.statusbar.showMessage("請先連接相機")
|
|||
|
|
|||
|
def keep_shot_process(self):
|
|||
|
if self.camera and self.camera.IsGrabbing():
|
|||
|
grab_result = self.camera.RetrieveResult(5000, pylon.TimeoutHandling_ThrowException)
|
|||
|
if grab_result.GrabSucceeded():
|
|||
|
self.current_image = grab_result.Array
|
|||
|
self.display_original_image()
|
|||
|
self.apply_threshold_and_display()
|
|||
|
grab_result.Release()
|
|||
|
|
|||
|
def open_image(self):
|
|||
|
options = QFileDialog.Options()
|
|||
|
file_path, _ = QFileDialog.getOpenFileName(self, "打開影像檔", "", "Images (*.png *.xpm *.jpg *.bmp *.tiff)", options=options)
|
|||
|
|
|||
|
if file_path:
|
|||
|
if not os.path.exists(file_path):
|
|||
|
self.statusbar.showMessage("檔案不存在或路徑錯誤")
|
|||
|
return
|
|||
|
|
|||
|
image_data = np.fromfile(file_path, dtype=np.uint8)
|
|||
|
image = cv2.imdecode(image_data, cv2.IMREAD_COLOR)
|
|||
|
|
|||
|
if image is not None:
|
|||
|
self.current_image = image
|
|||
|
self.display_original_image()
|
|||
|
self.apply_threshold_and_display()
|
|||
|
self.statusbar.showMessage("影像載入成功")
|
|||
|
else:
|
|||
|
self.statusbar.showMessage("無法讀取影像")
|
|||
|
def save_image(self):
|
|||
|
"""將二值化影像儲存到檔案"""
|
|||
|
if self.current_image is None:
|
|||
|
self.statusbar.showMessage("沒有影像可儲存")
|
|||
|
return
|
|||
|
|
|||
|
# 使用 QFileDialog 取得儲存檔案路徑和名稱
|
|||
|
file_path, _ = QFileDialog.getSaveFileName(self, "儲存二值化影像", "", "Images (*.png *.jpg *.bmp *.tiff)")
|
|||
|
|
|||
|
if file_path:
|
|||
|
try:
|
|||
|
# 取得 BGR 影像並生成二值化影像
|
|||
|
image_bgr = self.get_bgr_image()
|
|||
|
if image_bgr is None:
|
|||
|
self.statusbar.showMessage("無法取得影像")
|
|||
|
return
|
|||
|
|
|||
|
image_gray = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2GRAY)
|
|||
|
threshold_value = self.threshold_value.value()
|
|||
|
_, thresholded_image = cv2.threshold(image_gray, threshold_value, 255, cv2.THRESH_BINARY)
|
|||
|
|
|||
|
# 儲存影像
|
|||
|
cv2.imwrite(file_path, thresholded_image)
|
|||
|
self.statusbar.showMessage(f"二值化影像成功儲存到 {file_path}")
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
self.statusbar.showMessage(f"影像儲存失敗: {e}")
|
|||
|
|
|||
|
|
|||
|
|
|||
|
if __name__ == "__main__":
|
|||
|
app = QtWidgets.QApplication(sys.argv)
|
|||
|
window = CameraApp()
|
|||
|
window.show()
|
|||
|
sys.exit(app.exec_())
|