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