NBA球星知識大挑戰:基于 PyQt5 的球星認識小游戲
代碼詳見:https://github.com/xiaozhou-alt/NBA_Players_Recognition
文章目錄
- NBA球星知識大挑戰:基于 PyQt5 的球星認識小游戲
- 一、項目介紹
- 二、文件夾結構
- 三、項目實現
- 1. 自定義動畫按鈕(AnimatedButton)
- 2. 漸變背景組件(GradientWidget)
- 3. 主應用類(NBAApp)
- 4. 加載資源
- 5. 加載球員圖像
- 6. 創建主菜單頁面
- 7. 圖像上傳與識別功能
- 8. 球星識別頁面
- 9. 球星認識小游戲界面
- 10. 游戲問題界面
- 11. 游戲結果頁面
- 四、結果展示
關于識別球星的模型訓練和數據集獲取與講解請詳見:當庫里遇上卷積神經網絡:基于 EfficientNetV2 的NBA球星分類
一、項目介紹
項目使用深度學習技術對NBA球星圖像進行分類。項目基于TensorFlow實現,采用遷移學習策略,使用預訓練的 EfficientNetV2L 模型作為基礎架構,并添加了自定義的通道注意力機制。整個訓練過程分為兩個階段:第一階段凍結基礎模型訓練分類頭,第二階段解凍頂層進行微調優化;同時結合UI界面,將球星識別系統設計了一個球星識別小游戲,測試用戶對NBA球星的了解程度
項目亮點:
- 實現加權Top-3準確率評估指標,更符合實際應用場景
- 采用兩階段訓練策略提高模型性能
- 集成通道注意力機制增強特征提取能力
- 使用余弦衰減+預熱的學習率調度策略
- 現代化UI設計:采用漸變背景、動畫按鈕和流暢交互
- 資源優化:支持相對路徑訪問,便于打包分發
- 跨平臺兼容:可在Windows、macOS和Linux系統運行
此項目承接自:當庫里遇上卷積神經網絡:基于 EfficientNetV2 的NBA球星分類,數據集的獲取方法詳見:NBA 60位全明星球員圖片數據集(ScienceDB)
二、文件夾結構
NBA/
├── assets/ # 靜態資源文件夾
├── data/ # 球員圖片數據集└── Allen_Iverson/ # 艾弗森圖片(示例,共60位NBA球星)├── 1.png # 球員圖片(命名格式為數字)└── ... # 每個球員約300-400張圖片
├── log/ # 日志目錄
├── output/ # 輸出目錄├── model/ # 訓練好的模型└── pic/ # 生成的圖片
├── README.md
├── build.spec # PyInstaller打包配置
├── class.txt # 分類標簽
├── data.ipynb
├── demo.py # 主程序(帶GUI的識別系統)
├── demo.mp4 # 演示視頻
├── predict.py # 預測腳本
├── requirements.txt
└── train.py # 訓練腳本
三、項目實現
1. 自定義動畫按鈕(AnimatedButton)
這個自定義按鈕實現了:
- 設置按鈕的初始樣式(深藍色背景)
- 鼠標 懸停 時改變為懸停樣式(淺藍色背景)
- 使用屬性動畫實現 高度變化 的動畫效果
- 設置鼠標指針為手形,增強用戶體驗
class AnimatedButton(QPushButton):"""帶有懸停動畫的按鈕"""def __init__(self, text, parent=None):super().__init__(text, parent)self.setFont(QFont("Arial", 14, QFont.Bold))self.setMinimumHeight(50)self.setCursor(Qt.PointingHandCursor)# 初始樣式self.normal_style = """background-color: #1e3c72;color: white;border: 2px solid #2a5298;border-radius: 25px;padding: 10px 20px;"""self.hover_style = """background-color: #2a5298;color: white;border: 2px solid #3a6bc4;border-radius: 25px;padding: 10px 20px;"""self.setStyleSheet(self.normal_style)def enterEvent(self, event):# 鼠標進入時動畫...def leaveEvent(self, event):# 鼠標離開時動畫...def animate_size(self, start, end):# 創建尺寸動畫...
如下是一個簡單按鍵動畫展示:
2. 漸變背景組件(GradientWidget)
這個組件實現了:
- 定義四種顏色(深藍、中藍、淺藍和紅色)
- 在
paintEvent
中創建線性漸變 - 使用四種顏色創建從左上到右下的 漸變 效果
- 填充整個組件區域,為應用提供美觀的背景
class GradientWidget(QWidget):"""帶有漸變背景的組件"""def __init__(self, parent=None):super().__init__(parent)self.color1 = QColor(30, 60, 114) # 深藍色self.color2 = QColor(42, 82, 152) # 中藍色self.color3 = QColor(58, 107, 196) # 淺藍色self.color4 = QColor(142, 45, 65) # 紅色(NBA主題)def paintEvent(self, event):painter = QPainter(self)painter.setRenderHint(QPainter.Antialiasing)# 創建漸變...
3. 主應用類(NBAApp)
這是應用的主類:
- 繼承自
QMainWindow
,作為主窗口 - 設置全局字體和應用標題、大小
- 初始化球員圖像字典
- 創建堆疊窗口管理多個頁面
- 初始化所有頁面(主菜單、識別頁、游戲頁等)
- 加載模型和資源
- 顯示主菜單頁面
class NBAApp(QMainWindow):def __init__(self):super().__init__()# 設置全局字體font = QFont("Times New Roman", 12)QApplication.setFont(font)# 應用設置self.setWindowTitle("NBA球星識別系統")self.setGeometry(100, 100, 1000, 750)# 初始化球員圖像字典self.player_images = {} # 修復:添加初始化# 創建主堆疊窗口self.stacked_widget = QStackedWidget()self.setCentralWidget(self.stacked_widget)# 創建各頁面self.main_menu = self.create_main_menu()...# 加載模型和類別映射self.load_resources()# 顯示主菜單self.stacked_widget.setCurrentIndex(0)
4. 加載資源
- 加載預訓練的深度學習模型(包含自定義注意力層)
- 加載類別映射文件(JSON 格式)
- 從類別映射中提取球員名稱列表(替換下劃線為空格)
- 調用
load_player_images
方法加載球員圖像
def load_resources(self):"""加載模型和類別映射"""try:# 加載模型 - 使用相對路徑model_path = self.resource_path("output/model/best_model_phase2.h5")if os.path.exists(model_path):self.model = tf.keras.models.load_model(model_path,custom_objects={'ChannelAttention': ChannelAttention},compile=False)print("? 模型加載成功")else:print(f"? 模型文件不存在: {model_path}")# 加載類別映射mapping_path = self.resource_path("output/class_mapping.json")if os.path.exists(mapping_path):with open(mapping_path, 'r') as f:self.class_mapping = json.load(f)print("? 類別映射加載成功")# 獲取球員列表(將下劃線替換為空格)self.player_names = [name.replace('_', ' ') for name in self.class_mapping['class_to_index'].keys()]else:print(f"? 類別映射文件不存在: {mapping_path}")# 加載球員圖像(用于小游戲)self.load_player_images()except Exception as e:print(f"? 資源加載失敗: {e}")
球員姓名映射文件(class_mapping.json
):
{
“class_to_index”: {
“Allen_Iverson”: 0,
…
“Wilt_Chamberlain”: 59
},
“index_to_class”: {
“0”: “Allen_Iverson”,
…
“59”: “Wilt_Chamberlain”
}
}
5. 加載球員圖像
這個方法加載球員圖像用于小游戲:
- 檢查球員名稱列表是否有效
- 獲取
data
文件夾路徑 - 遍歷
data
文件夾下的每個球員文件夾 - 提取球員名稱(替換下劃線為空格)
- 收集該球員的所有圖像文件路徑
- 將球員名稱和圖像路徑列表存儲到字典中
def load_player_images(self):"""加載球員圖像(用于小游戲)"""if not hasattr(self, 'player_names') or not self.player_names:print("?? 球員名稱列表未初始化或為空")return# 球員文件夾路徑nba_dir = self.resource_path("data")if not os.path.exists(nba_dir):print(f"? data文件夾不存在: {nba_dir}")return# 確保 player_images 字典已初始化if not hasattr(self, 'player_images'):self.player_images = {}print("?? player_images 字典已初始化") for player_folder in os.listdir(nba_dir):player_path = os.path.join(nba_dir, player_folder)if os.path.isdir(player_path):# 獲取球員名稱(替換下劃線)player_name = player_folder.replace('_', ' ')# 獲取該球員的所有圖像images = [os.path.join(player_path, img) for img in os.listdir(player_path)if img.lower().endswith(('.png', '.jpg', '.jpeg'))]if images:self.player_images[player_name] = images
球員圖片文件夾格式樣例:
6. 創建主菜單頁面
- 使用漸變背景組件
- 創建標題和副標題
- 添加三個功能按鈕(球星識別、小游戲、退出)
- 添加籃球裝飾圖片
- 使用垂直布局組織所有元素
- 通過
Stretch
實現元素居中效果
def create_main_menu(self):"""創建主菜單頁面"""widget = GradientWidget()layout = QVBoxLayout(widget)# ... (布局設置)# 標題區域title_frame = QFrame()# ... (樣式設置)title_layout = QVBoxLayout(title_frame) # 標題title = QLabel("NBA球星識別系統")# ... (字體和樣式設置)# 副標題subtitle = QLabel("探索籃球傳奇,認識超級球星")# ... (樣式設置)title_layout.addWidget(title)title_layout.addWidget(subtitle)# 按鈕容器button_frame = QFrame()# ... (樣式設置)button_layout = QVBoxLayout(button_frame)# 按鈕btn_recognition = AnimatedButton("球星識別")...# 添加籃球裝飾basketball_label = QLabel()pixmap = QPixmap(self.resource_path("assets/basketball.png"))# ... (加載和縮放圖片)# 添加組件layout.addStretch(1)...return widget
最終的主界面布局如下所示:
7. 圖像上傳與識別功能
upload_image
:打開文件對話框選擇圖片,顯示在界面上preprocess_image
:預處理圖像(調整大小、處理通道、歸一化)recognize_player
:使用模型進行預測,顯示 T o p 3 Top3 Top3 結果
def upload_image(self):"""上傳圖片"""file_path, _ = QFileDialog.getOpenFileName(self, "選擇圖片", "", "圖片文件 (*.png *.jpg *.jpeg)")if file_path:# 顯示圖片pixmap = QPixmap(file_path)if not pixmap.isNull():# 縮放圖片以適應標簽...def recognize_player(self):"""識別球星"""if not hasattr(self, 'current_image_path') or not self.model:self.recog_result_label.setText("請先上傳圖片或等待模型加載完成")returntry:# 添加加載提示self.recog_result_label.setText("正在識別中,請稍后...(第一次加載需要較長時間哦)")QApplication.processEvents() # 強制刷新UI# 預處理圖像processed_img, original_img = self.preprocess_image(self.current_image_path)# 進行預測predictions = self.model.predict(processed_img)[0]# 獲取top-3預測結果top_indices = np.argsort(predictions)[::-1][:3]top_indices = [int(idx) for idx in top_indices]# 獲取球員名稱和概率top_players = [self.class_mapping['index_to_class'][str(idx)].replace('_', ' ') for idx in top_indices]top_probs = predictions[top_indices]# 構建結果字符串result_text = "🏀 識別結果:\n\n"for i, (player, prob) in enumerate(zip(top_players, top_probs)):result_text += f"{i+1}. {player}: {prob*100:.2f}%\n"self.recog_result_label.setText(result_text)except Exception as e:self.recog_result_label.setText(f"?? 識別失敗: {str(e)}")def preprocess_image(self, image_path, target_size=(300, 300)):"""預處理圖像用于模型預測"""img = Image.open(image_path)# 保留原始圖像用于顯示original_img = img.copy()# 調整大小為模型輸入尺寸img = img.resize(target_size)img_array = np.array(img)# 處理圖像通道if len(img_array.shape) == 2: # 灰度圖img_array = np.stack((img_array,) * 3, axis=-1)elif img_array.shape[2] == 4: # RGBA轉RGBimg_array = img_array[..., :3]img_array = img_array.astype('float32') / 255.0return np.expand_dims(img_array, axis=0), original_img
8. 球星識別頁面
添加返回按鈕和頁面標題、創建圖像顯示區域、添加上傳和識別按鈕、設置結果標簽用于顯示識別結果、使用垂直布局組織所有元素、
def create_recognition_page(self):"""創建球星識別頁面"""widget = GradientWidget()# ... (布局設置)# 標題欄header = QWidget()header_layout = QHBoxLayout(header)# 返回按鈕btn_back = AnimatedButton("返回")...# 標題title = QLabel("球星識別")# ... (樣式設置)# 主內容區域content = QWidget()content_layout = QVBoxLayout(content)# 圖像顯示區域self.recog_image_label = QLabel()# ... (樣式設置)# 按鈕容器button_container = QWidget()button_layout = QHBoxLayout(button_container)# 上傳按鈕btn_upload = AnimatedButton("上傳圖片")...# 識別按鈕btn_recognize = AnimatedButton("識別球星")...# 結果區域self.recog_result_label = QLabel("上傳圖片后點擊識別按鈕")# ... (樣式設置)# 添加裝飾decoration = QLabel()pixmap = QPixmap(self.resource_path("assets/nba_logo.png"))# ... (加載和縮放圖片)# 添加組件...return widget
最終的球星識別頁面布局如下所示:
9. 球星認識小游戲界面
- 添加返回按鈕和頁面標題
- 創建游戲說明標簽
- 添加難度選擇單選按鈕(簡單(5)、中等(10)、困難(20))
- 使用垂直布局組織所有元素
def create_game_page(self):"""創建小游戲頁面"""widget = GradientWidget()# ... (布局設置)# 標題欄header = QWidget()header_layout = QHBoxLayout(header)# 返回按鈕btn_back = AnimatedButton("返回")btn_back.clicked.connect(lambda: self.stacked_widget.setCurrentIndex(0))# 標題title = QLabel("球星認識小游戲")# ... (樣式設置)header_layout.addWidget(btn_back)header_layout.addWidget(title)header_layout.addStretch()# 主內容區域content = QWidget()content_layout = QVBoxLayout(content)# 游戲說明instruction = QLabel("測試你對NBA球星的了解程度!\n選擇難度級別,開始挑戰吧!")# ... (樣式設置)# 游戲選項區域group = QGroupBox("選擇游戲難度")# ... (樣式設置)group_layout = QHBoxLayout(group)...# 設置單選按鈕樣式# ... (樣式設置)...# 開始游戲按鈕btn_start = AnimatedButton("開始游戲")btn_start.setIcon(QIcon(self.resource_path("assets/start_icon.png")))btn_start.clicked.connect(self.start_game)# 添加裝飾trophy_label = QLabel()pixmap = QPixmap(self.resource_path("assets/trophy.png"))# ... (加載和縮放圖片)# 添加內容...# 添加組件...return widget
最終小游戲頁面布局如下所示:
10. 游戲問題界面
- 添加返回按鈕和動態標題(顯示當前問題)
- 創建圖像顯示區域(帶邊框)
- 添加四個選項按鈕(單選)
- 添加提交答案按鈕
- 添加進度標簽(顯示當前得分)
def create_game_question_page(self):"""創建游戲問題頁面"""widget = GradientWidget()# ... (布局設置)# 標題欄header = QWidget()header_layout = QHBoxLayout(header)# 返回按鈕btn_back = AnimatedButton("返回")btn_back.clicked.connect(lambda: self.stacked_widget.setCurrentIndex(2))# 標題(顯示當前問題)self.game_title = QLabel()# ... (樣式設置)...# 主內容區域content = QWidget()content_layout = QVBoxLayout(content)# 圖像顯示區域self.game_image_frame = QFrame()# ... (樣式設置)image_layout = QVBoxLayout(self.game_image_frame)self.game_image_label = QLabel()# ... (設置)image_layout.addWidget(self.game_image_label)# 選項組options_group = QGroupBox("請選擇正確的球星名字")# ... (樣式設置)option_layout = QVBoxLayout(options_group)self.option_group = QButtonGroup()self.option_buttons = [] # 存儲選項按鈕# 創建選項按鈕for i in range(4):...# 提交答案按鈕btn_submit = AnimatedButton("提交答案")btn_submit.setIcon(QIcon(self.resource_path("assets/submit_icon.png")))btn_submit.clicked.connect(self.check_answer)# 進度標簽self.progress_label = QLabel()# ... (樣式設置)# 添加內容...# 添加組件layout.addWidget(header)layout.addWidget(content, 1)return widget
最終問題界面布局如下所示:
11. 游戲結果頁面
- 創建結果標簽(顯示評價)
- 添加分數標簽(顯示得分)
- 添加動畫標簽(顯示GIF動畫)
- 添加兩個按鈕(再玩一次和返回主菜單)
- 使用垂直布局組織所有元素
- 通過
Stretch
實現居中效果
def create_result_page(self):"""創建游戲結果頁面"""widget = GradientWidget()# ... (布局設置)# 標題title = QLabel("游戲結果")# ... (樣式設置)# 結果容器result_container = QWidget()result_layout = QVBoxLayout(result_container)# 結果標簽self.result_label = QLabel()# ... (樣式設置)# 分數標簽self.score_label = QLabel()# ... (樣式設置)# 動畫標簽self.animation_label = QLabel()# 按鈕容器button_container = QWidget()button_layout = QHBoxLayout(button_container)# 再玩一次按鈕btn_restart = AnimatedButton("再玩一次")btn_restart.setIcon(QIcon(self.resource_path("assets/restart_icon.png")))btn_restart.clicked.connect(lambda: self.stacked_widget.setCurrentIndex(2))# 返回主菜單按鈕btn_menu = AnimatedButton("返回主菜單")...# 添加內容...# 添加組件layout.addStretch(1)...return widget
最終游戲結果畫面布局如下所示:
四、結果展示
NBA球星識別系統的演示視頻如下所示:
如果你喜歡我的文章,不妨給小周一個免費的點贊和關注吧!