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