import shutil import threading import sys import cv2 import numpy as np from PyQt5.QtGui import * from PyQt5.QtCore import * from PyQt5.QtWidgets import * import os.path as osp class MainWindow(QTabWidget): def __init__(self): # 初始化界面 super().__init__() self.load_custom_font() self.setWindowTitle('华东师范大学校园卡检测系统') self.resize(1200, 800) self.setWindowIcon(QIcon("images/UI/ECNU.png")) # 图片读取进程 self.cab=None self.stopEvent = threading.Event() self.stopEvent.clear() self.output_size = 1000 self.img2predict = "" self.initUI() self.webcam=False self.reset_vid() def load_custom_font(self): # 加载自定义字体文件 font_id = QFontDatabase.addApplicationFont('font/OPPOSans-H-2.ttf') if font_id != -1: self.font_family = QFontDatabase.applicationFontFamilies(font_id)[0] else: self.font_family = '楷体' # 备用字体 def initUI(self): # 图片检测子界面 font_title = QFont(self.font_family, 16) font_main = QFont(self.font_family, 14) # 图片识别界面, 两个按钮,上传图片和显示结果 img_detection_widget = QWidget() img_detection_layout = QVBoxLayout() img_detection_title = QLabel("图片检测功能") img_detection_title.setFont(font_title) mid_img_widget = QWidget() mid_img_layout = QHBoxLayout() self.left_img = QLabel() self.right_img = QLabel() self.left_img.setPixmap(QPixmap("images/UI/touxiang.png")) self.right_img.setPixmap(QPixmap("images/UI/touxiang2.png")) self.left_img.setAlignment(Qt.AlignCenter) self.right_img.setAlignment(Qt.AlignCenter) mid_img_layout.addWidget(self.left_img) mid_img_layout.addStretch(0) mid_img_layout.addWidget(self.right_img) mid_img_widget.setLayout(mid_img_layout) up_img_button = QPushButton("上传图片") det_img_button = QPushButton("开始检测") up_img_button.clicked.connect(self.upload_img) det_img_button.clicked.connect(self.detect_img) up_img_button.setFont(font_main) det_img_button.setFont(font_main) up_img_button.setStyleSheet("QPushButton{color:white}" "QPushButton:hover{background-color: rgb(2,110,180);}" "QPushButton{background-color:rgb(48,124,208)}" "QPushButton{border:2px}" "QPushButton{border-radius:5px}" "QPushButton{padding:5px 5px}" "QPushButton{margin:5px 5px}") det_img_button.setStyleSheet("QPushButton{color:white}" "QPushButton:hover{background-color: rgb(2,110,180);}" "QPushButton{background-color:rgb(48,124,208)}" "QPushButton{border:2px}" "QPushButton{border-radius:5px}" "QPushButton{padding:5px 5px}" "QPushButton{margin:5px 5px}") img_detection_layout.addWidget(img_detection_title, alignment=Qt.AlignCenter) img_detection_layout.addWidget(mid_img_widget, alignment=Qt.AlignCenter) img_detection_layout.addWidget(up_img_button) img_detection_layout.addWidget(det_img_button) img_detection_widget.setLayout(img_detection_layout) # 视频识别界面 vid_detection_widget = QWidget() vid_detection_layout = QVBoxLayout() vid_title = QLabel("视频检测功能") vid_title.setFont(font_title) self.vid_img = QLabel() self.vid_img.setPixmap(QPixmap("images/UI/logo.png")) vid_title.setAlignment(Qt.AlignCenter) self.vid_img.setAlignment(Qt.AlignCenter) self.webcam_detection_btn = QPushButton("摄像头实时监测") self.mp4_detection_btn = QPushButton("视频文件检测") self.vid_stop_btn = QPushButton("停止检测") self.webcam_detection_btn.setFont(font_main) self.mp4_detection_btn.setFont(font_main) self.vid_stop_btn.setFont(font_main) self.webcam_detection_btn.setStyleSheet("QPushButton{color:white}" "QPushButton:hover{background-color: rgb(2,110,180);}" "QPushButton{background-color:rgb(48,124,208)}" "QPushButton{border:2px}" "QPushButton{border-radius:5px}" "QPushButton{padding:5px 5px}" "QPushButton{margin:5px 5px}") self.mp4_detection_btn.setStyleSheet("QPushButton{color:white}" "QPushButton:hover{background-color: rgb(2,110,180);}" "QPushButton{background-color:rgb(48,124,208)}" "QPushButton{border:2px}" "QPushButton{border-radius:5px}" "QPushButton{padding:5px 5px}" "QPushButton{margin:5px 5px}") self.vid_stop_btn.setStyleSheet("QPushButton{color:white}" "QPushButton:hover{background-color: rgb(2,110,180);}" "QPushButton{background-color:rgb(48,124,208)}" "QPushButton{border:2px}" "QPushButton{border-radius:5px}" "QPushButton{padding:5px 5px}" "QPushButton{margin:5px 5px}") self.webcam_detection_btn.clicked.connect(self.open_cam) self.mp4_detection_btn.clicked.connect(self.open_mp4) self.vid_stop_btn.clicked.connect(self.close_vid) # 添加组件到布局上 vid_detection_layout.addWidget(vid_title) vid_detection_layout.addWidget(self.vid_img) vid_detection_layout.addWidget(self.webcam_detection_btn) vid_detection_layout.addWidget(self.mp4_detection_btn) vid_detection_layout.addWidget(self.vid_stop_btn) vid_detection_widget.setLayout(vid_detection_layout) self.left_img.setAlignment(Qt.AlignCenter) self.addTab(img_detection_widget, '图片检测') self.addTab(vid_detection_widget, '视频检测') self.setTabIcon(0, QIcon('images/UI/logo.png')) self.setTabIcon(1, QIcon('images/UI/logo.png')) # 上传图片 def upload_img(self): fileName, fileType = QFileDialog.getOpenFileName(self, 'Choose file', '', '*.jpg *.png *.tif *.jpeg') if fileName: suffix = fileName.split(".")[-1] save_path = osp.join("images/tmp", "original_image." + suffix) shutil.copy(fileName, save_path) # 应该调整一下图片的大小,然后统一放在一起 im0 = cv2.imread(save_path) new_height = 400 * im0.shape[0] / im0.shape[1] im0 = cv2.resize(im0, (400, int(new_height)), interpolation=cv2.INTER_CUBIC) cv2.imwrite("images/tmp/compressed_image.jpg", im0) self.img2predict = "images/tmp/compressed_image.jpg" self.left_img.setPixmap(QPixmap("images/tmp/compressed_image.jpg")) # 上传图片之后右侧的图片重置, self.right_img.setPixmap(QPixmap("images/UI/touxiang2.png")) def find_campus_card(self,bgr_image): # 预处理图像,对图像进行压缩,便于之后处理 image_height, image_width = bgr_image.shape[:2] # 掩膜匹配白色区域 hsv = cv2.cvtColor(bgr_image, cv2.COLOR_BGR2HSV) lower_white = np.array([0, 0, 170]) upper_white = np.array([180, 80, 255]) mask = cv2.inRange(hsv, lower_white, upper_white) # 开运算去除噪声 kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5)) mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel) # 高斯滤波,减少噪声和增强主要结构 blur_mask = cv2.GaussianBlur(mask, (3, 3), 0) gradx = cv2.Sobel(blur_mask, cv2.CV_16SC1, 1, 0) grady = cv2.Sobel(blur_mask, cv2.CV_16SC1, 0, 1) # canny边缘检测 edges = cv2.Canny(gradx, grady, 50, 150) contours, _ = cv2.findContours(edges, cv2.RETR_TREE, cv2.CHAIN_APPROX_TC89_L1) best_contour = None best_rect = None best_ratio_diff = float('inf') desired_ratio = 2.5 min_area = 0.05 * image_width * image_height for contour in contours: epsilon = 0.02 * cv2.arcLength(contour, True) approx = cv2.approxPolyDP(contour, epsilon, True) # 获取最小外接矩形 rect = cv2.minAreaRect(approx) (width, height) = rect[1] if width == 0 or height == 0: continue # 计算长宽比 if width < height: ratio = height / width else: ratio = width / height # 计算矩形面积 area = width * height # 计算与期望比例的差异 ratio_diff = abs(ratio - desired_ratio) # 更新最优轮廓 if ratio_diff < best_ratio_diff and area >= min_area: best_contour = approx best_rect = rect best_ratio_diff = ratio_diff if best_rect is not None: # 获取最小外接矩形的顶点 box = cv2.boxPoints(best_rect) box = np.intp(box) # 在图像上绘制矩形 cv2.drawContours(bgr_image, [box], 0, (0, 0, 255), 2) angle = best_rect[2] if best_rect[1][0] < best_rect[1][1]: angle -= 90 return bgr_image, box, angle else: return bgr_image, None,None def background_remove(self,afterimg, box): # 创建一个与原始图像大小相同的掩膜图像,初始为全白(255) mask = np.ones_like(afterimg) * 0 # 将矩形区域填充为黑色 cv2.drawContours(mask, [box], 0, (255, 255, 255), -1) # 将掩膜图像与原始图像进行按位与操作,保留矩形区域为彩色,其他区域变为黑色 result = cv2.bitwise_and(afterimg, mask) return result def rotate_image(self,image, angle): # 获取图像中心点 center = tuple(np.array(image.shape[1::-1]) / 2) # 计算旋转矩阵 rotation_matrix = cv2.getRotationMatrix2D(center, angle, 1.0) # 进行仿射变换 rotated_image = cv2.warpAffine(image, rotation_matrix, image.shape[1::-1], flags=cv2.INTER_LINEAR) return rotated_image def detect_img(self): source = self.img2predict if source == "": QMessageBox.warning(self, "请上传", "请先上传图片再进行检测") else: bgr_image = cv2.imread(source) processed_image, box, rotation_angle = self.find_campus_card(bgr_image) cv2.imwrite("images/tmp/find_card_image.jpg", processed_image) if box is not None: background_remove_img = self.background_remove(processed_image, box) rotated_image = self.rotate_image(background_remove_img, rotation_angle) cv2.imwrite("images/tmp/result_image.jpg", rotated_image) self.left_img.setPixmap(QPixmap("images/tmp/find_card_image.jpg")) self.right_img.setPixmap(QPixmap("images/tmp/result_image.jpg")) else: self.right_img.setPixmap(QPixmap("images/UI/error2.png")) def open_cam(self): self.webcam_detection_btn.setEnabled(False) # 禁用检测按钮 self.mp4_detection_btn.setEnabled(False) # 禁用 MP4 检测按钮 self.vid_stop_btn.setEnabled(True) # 启用停止按钮 self.cap = cv2.VideoCapture(0) self.webcam=True th = threading.Thread(target=self.process_video) # 创建一个新的线程 th.start() # 启动新线程 def open_mp4(self): fileName, fileType = QFileDialog.getOpenFileName(self, 'Choose file', '', '*.mp4 *.avi *.mov') if fileName: self.webcam_detection_btn.setEnabled(False) self.mp4_detection_btn.setEnabled(False) self.vid_stop_btn.setEnabled(True) # 启用停止按钮 self.cap = cv2.VideoCapture(fileName) th = threading.Thread(target=self.process_video) # 创建一个新的线程 th.start() # 启动新线程 def process_video(self): while True: ret, frame = self.cap.read() if self.webcam: frame = cv2.flip(frame, 1) if ret: new_height = 400 * frame.shape[0] / frame.shape[1] frame = cv2.resize(frame, (400, int(new_height)), interpolation=cv2.INTER_CUBIC) processed_image, box, rotation_angle = self.find_campus_card(frame) cv2.imwrite("images/tmp/processed_video_image.jpg", processed_image) self.vid_img.setPixmap(QPixmap("images/tmp/processed_video_image.jpg")) if self.stopEvent.is_set() == True: self.stopEvent.clear() break def closeEvent(self, event): reply = QMessageBox.question(self,'退出',"是否退出?",QMessageBox.Yes | QMessageBox.No,QMessageBox.No) if reply == QMessageBox.Yes: self.close() event.accept() else: event.ignore() # 界面重置 def reset_vid(self): self.webcam=False self.webcam_detection_btn.setEnabled(True) self.mp4_detection_btn.setEnabled(True) self.vid_img.setPixmap(QPixmap("images/UI/logo.png")) # 视频重置 def close_vid(self): if self.cap: self.cap.release() self.stopEvent.set() self.reset_vid() if __name__ == "__main__": app = QApplication(sys.argv) mainWindow = MainWindow() mainWindow.show() sys.exit(app.exec_())