python爬虫入门学习:Request库中的get()方法小结

本文深入探讨Python的requests库中get()方法的功能与用法,包括如何发送GET请求、处理响应状态码、理解编码方式及获取响应内容。通过实例演示,揭示了中文乱码问题的解决方法。

Request库中的get()方法小结
Python网络爬虫与信息提取-北理工 嵩天(大学mooc)
本课B站版

requests库中,最核心的内容便是get(),其结构为:
r=requests.get(url)
其作用,一是构造一个向服务器请求资源的Request对象,二是返回一个包含服务器资源的Response对象。

验证操作

import requests

r = requests.get("http://www.baidu.com")
print(r.status_code)
print(type(r))
print(r.headers)

#200
#<class 'requests.models.Response'>
#{'Cache-Control': 'private, no-cache, no-store, proxy-revalidate, no-transform', 'Connection': 'keep-alive', 'Content-Encoding': 'gzip', 'Content-Type': 'text/html', 'Date': 'Tue, 16 Jun 2020 11:41:30 GMT', 'Last-Modified': 'Mon, 23 Jan 2017 13:28:12 GMT', 'Pragma': 'no-cache', 'Server': 'bfe/1.0.8.18', 'Set-Cookie': 'BDORZ=27315; max-age=86400; domain=.baidu.com; path=/', 'Transfer-Encoding': 'chunked'}

可以看到,正常情况下status_code的返回为200,r会返回Response对象

在操作之后,有几个常用的函数
r.status_code 200为成功,404为失败
r.encoding 网页编码方式
r.apparent_encoding 响应内容备选方式
r.content HTTP响应内容的二进制方式

在上一节中,我们发现r.encoding格式下的r.text中,我们获得的好像是一团乱码,如:

'<!DOCTYPE html><!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=http://s1.bdstatic.com/r/www/cache/bdorz/baidu.min.css><title>ç™¾åº¦ä¸€ä¸‹ï¼Œä½ å°±çŸ¥é“</title></head> <body link=#0000cc> <div id=wrapper> <div id=head> <div class=head_wrapper> <div class=s_form> <div class=s_form_wrapper> <div id=lg> <img hidefocus=true src=//www.baidu.com/img/bd_logo1.png width=270 height=129> </div> <form id=form name=f action=//www.baidu.com/s class=fm> <input type=hidden name=bdorz_come value=1> <input type=hidden name=ie value=utf-8> <input type=hidden name=f value=8> <input type=hidden name=rsv_bp value=1> <input type=hidden name=rsv_idx value=1> <input type=hidden name=tn value=baidu><span class="bg s_ipt_wr"><input id=kw name=wd class=s_ipt value maxlength=255 autocomplete=off autofocus></span><span class="bg s_btn_wr"><input type=submit id=su value=百度一下 class="bg s_btn"></span> </form> </div> </div> <div id=u1> <a href=http://news.baidu.com name=tj_trnews class=mnav>æ–°é—»</a> <a href=http://www.hao123.com name=tj_trhao123 class=mnav>hao123</a> <a href=http://map.baidu.com name=tj_trmap class=mnav>地图</a> <a href=http://v.baidu.com name=tj_trvideo class=mnav>视频</a> <a href=http://tieba.baidu.com name=tj_trtieba class=mnav>贴吧</a> <noscript> <a href=http://www.baidu.com/bdorz/login.gif?login&amp;tpl=mn&amp;u=http%3A%2F%2Fwww.baidu.com%2f%3fbdorz_come%3d1 name=tj_login class=lb>登录</a> </noscript> <script>document.write('<a href="http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u='+ encodeURIComponent(window.location.href+ (window.location.search === "" ? "?" : "&")+ "bdorz_come=1")+ '" name="tj_login" class="lb">登录</a>');</script> <a href=//www.baidu.com/more/ name=tj_briicon class=bri style="display: block;">更多产品</a> </div> </div> </div> <div id=ftCon> <div id=ftConw> <p id=lh> <a href=http://home.baidu.com>å
³äºŽç™¾åº¦</a> <a href=http://ir.baidu.com>About Baidu</a> </p> <p id=cp>&copy;2017&nbsp;Baidu&nbsp;<a href=http://www.baidu.com/duty/>使用百度前å¿
读</a>&nbsp; <a href=http://jianyi.baidu.com/ class=cp-feedback>意见反馈</a>&nbsp;京ICP证030173号&nbsp; <img src=//www.baidu.com/img/gs.gif> </p> </div> </div> </div> </body> </html>'

我们看不出中文来,以r.apparent_encoding形式,我们知道另一种编码方式——utf-8

将编码格式转化为utf-8格式,我们可以看出中文来

'<!DOCTYPE html>\r\n<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=http://s1.bdstatic.com/r/www/cache/bdorz/baidu.min.css><title>百度一下,你就知道</title></head> <body link=#0000cc> <div id=wrapper> <div id=head> <div class=head_wrapper> <div class=s_form> <div class=s_form_wrapper> <div id=lg> <img hidefocus=true src=//www.baidu.com/img/bd_logo1.png width=270 height=129> </div> <form id=form name=f action=//www.baidu.com/s class=fm> <input type=hidden name=bdorz_come value=1> <input type=hidden name=ie value=utf-8> <input type=hidden name=f value=8> <input type=hidden name=rsv_bp value=1> <input type=hidden name=rsv_idx value=1> <input type=hidden name=tn value=baidu><span class="bg s_ipt_wr"><input id=kw name=wd class=s_ipt value maxlength=255 autocomplete=off autofocus></span><span class="bg s_btn_wr"><input type=submit id=su value=百度一下 class="bg s_btn"></span> </form> </div> </div> <div id=u1> <a href=http://news.baidu.com name=tj_trnews class=mnav>新闻</a> <a href=http://www.hao123.com name=tj_trhao123 class=mnav>hao123</a> <a href=http://map.baidu.com name=tj_trmap class=mnav>地图</a> <a href=http://v.baidu.com name=tj_trvideo class=mnav>视频</a> <a href=http://tieba.baidu.com name=tj_trtieba class=mnav>贴吧</a> <noscript> <a href=http://www.baidu.com/bdorz/login.gif?login&amp;tpl=mn&amp;u=http%3A%2F%2Fwww.baidu.com%2f%3fbdorz_come%3d1 name=tj_login class=lb>登录</a> </noscript> <script>document.write(\'<a href="http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u=\'+ encodeURIComponent(window.location.href+ (window.location.search === "" ? "?" : "&")+ "bdorz_come=1")+ \'" name="tj_login" class="lb">登录</a>\');</script> <a href=//www.baidu.com/more/ name=tj_briicon class=bri style="display: block;">更多产品</a> </div> </div> </div> <div id=ftCon> <div id=ftConw> <p id=lh> <a href=http://home.baidu.com>关于百度</a> <a href=http://ir.baidu.com>About Baidu</a> </p> <p id=cp>&copy;2017&nbsp;Baidu&nbsp;<a href=http://www.baidu.com/duty/>使用百度前必读</a>&nbsp; <a href=http://jianyi.baidu.com/ class=cp-feedback>意见反馈</a>&nbsp;京ICP证030173号&nbsp; <img src=//www.baidu.com/img/gs.gif> </p> </div> </div> </div> </body> </html>\r\n'

其中原因,便是原始的encoding形式可能不支持中文字符,所以就要有一个备用的编码方式,相以结合

# -*- coding: utf-8 -*- """ 结课项目:人脸识别 + 表情识别 + 颜值打分 环境要求:paddlepaddle-gpu==1.8.5 或 paddlepaddle==1.8.5 """ import os import cv2 import numpy as np import matplotlib.pyplot as plt from PIL import Image import paddle import paddle.fluid as fluid from paddle.fluid.dygraph import Conv2D, Pool2D, BatchNorm, Linear, Dropout import random import json from tqdm import tqdm import tarfile import shutil """ 百度图片爬虫 - 精简版 爬取20位明星,每人100张图片 """ import os import re import time import random import requests from urllib.parse import quote # ==================== 配置部分 ==================== CELEBRITIES = [ "刘德华", "周杰伦", "章子怡", "迪丽热巴", "杨幂", "吴京", "沈腾", "贾玲", "易烊千玺", "王一博", "赵丽颖", "肖战", "李现", "杨紫", "邓超", "孙俪", "黄晓明", "杨颖", "胡歌", "刘亦菲" ] SAVE_DIR = "celebrity_images" IMAGES_PER_CELEB = 100 # ==================== 爬虫函数 ==================== def crawl_celebrity(name, save_dir, target=100): """爬取单个明星的图片""" print(f"开始: {name}") # 创建目录 celeb_dir = os.path.join(save_dir, name) os.makedirs(celeb_dir, exist_ok=True) headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' } downloaded = 0 page = 0 while downloaded < target and page < 10: try: # 构造URL url = f'http://image.baidu.com/search/flip?tn=baiduimage&word={quote(name)}&pn={page*30}' # 获取页面 response = requests.get(url, headers=headers, timeout=20) if response.status_code != 200: break # 提取图片URL img_urls = re.findall(r'"objURL":"(.*?)"', response.text, re.S) for img_url in img_urls: if downloaded >= target: break try: # 下载图片 img_data = requests.get(img_url, timeout=25, headers=headers).content if len(img_data) > 2048: # 有效图片 # 保存 filename = f"{name}_{downloaded:04d}.jpg" with open(os.path.join(celeb_dir, filename), 'wb') as f: f.write(img_data) downloaded += 1 if downloaded % 20 == 0: print(f" {name}: 已下载 {downloaded}/{target}") time.sleep(random.uniform(0.1, 0.3)) except: continue page += 1 time.sleep(1) except Exception as e: print(f" 错误: {e}") page += 1 continue print(f"完成: {name} - {downloaded}张") return downloaded # ==================== 批量爬取 ==================== def batch_crawl(): """批量爬取所有明星""" print(f"目标: {len(CELEBRITIES)}位明星 × {IMAGES_PER_CELEB}张/人") print(f"保存到: {SAVE_DIR}") print("="*60) os.makedirs(SAVE_DIR, exist_ok=True) total = 0 for i, celeb in enumerate(CELEBRITIES, 1): print(f"\n[{i}/{len(CELEBRITIES)}] {'='*40}") count = crawl_celebrity(celeb, SAVE_DIR, IMAGES_PER_CELEB) total += count # 明星间延迟 if i < len(CELEBRITIES): delay = random.uniform(3, 8) print(f"等待 {delay:.1f}秒...") time.sleep(delay) # 显示结果 print(f"\n{'='*60}") print(f"总计下载: {total} 张图片") print(f"完成度: {total/(len(CELEBRITIES)*IMAGES_PER_CELEB)*100:.1f}%") # 检查各目录文件数 print("\n各明星下载数量:") for celeb in CELEBRITIES: celeb_dir = os.path.join(SAVE_DIR, celeb) if os.path.exists(celeb_dir): files = len([f for f in os.listdir(celeb_dir) if f.endswith('.jpg')]) print(f" {celeb}: {files}张") return total # ==================== 运行 ==================== if __name__ == "__main__": print("百度图片爬虫 - 20位明星每人100张") print("开始时间:", time.strftime('%Y-%m-%d %H:%M:%S')) # 直接运行批量爬取 total = batch_crawl() print(f"\n完成时间: {time.strftime('%Y-%m-%d %H:%M:%S')}") print(f"图片保存在: {SAVE_DIR}/") 百度图片爬虫 - 20位明星每人100张 开始时间: 2025-12-31 13:04:48 目标: 20位明星 × 100张/人 保存到: celebrity_images ============================================================ [1/20] ======================================== 开始: 刘德华 刘德华: 已下载 20/100 刘德华: 已下载 40/100 刘德华: 已下载 60/100 刘德华: 已下载 80/100 刘德华: 已下载 100/100 完成: 刘德华 - 100张 等待 3.5秒... [2/20] ======================================== 开始: 周杰伦 周杰伦: 已下载 20/100 周杰伦: 已下载 40/100 周杰伦: 已下载 60/100 周杰伦: 已下载 80/100 周杰伦: 已下载 100/100 完成: 周杰伦 - 100张 等待 3.5秒... [3/20] ======================================== 开始: 章子怡 章子怡: 已下载 20/100 章子怡: 已下载 40/100 章子怡: 已下载 60/100 章子怡: 已下载 80/100 章子怡: 已下载 100/100 完成: 章子怡 - 100张 等待 3.9秒... [4/20] ======================================== 开始: 迪丽热巴 迪丽热巴: 已下载 20/100 迪丽热巴: 已下载 40/100 迪丽热巴: 已下载 60/100 迪丽热巴: 已下载 80/100 迪丽热巴: 已下载 100/100 完成: 迪丽热巴 - 100张 等待 5.0秒... [5/20] ======================================== 开始: 杨幂 杨幂: 已下载 20/100 杨幂: 已下载 40/100 杨幂: 已下载 60/100 杨幂: 已下载 80/100 杨幂: 已下载 100/100 完成: 杨幂 - 100张 等待 3.5秒... [6/20] ======================================== 开始: 吴京 吴京: 已下载 20/100 吴京: 已下载 40/100 吴京: 已下载 60/100 吴京: 已下载 80/100 吴京: 已下载 100/100 完成: 吴京 - 100张 等待 6.4秒... [7/20] ======================================== 开始: 沈腾 沈腾: 已下载 20/100 沈腾: 已下载 40/100 沈腾: 已下载 60/100 沈腾: 已下载 80/100 沈腾: 已下载 100/100 完成: 沈腾 - 100张 等待 5.8秒... [8/20] ======================================== 开始: 贾玲 贾玲: 已下载 20/100 贾玲: 已下载 40/100 贾玲: 已下载 60/100 贾玲: 已下载 80/100 贾玲: 已下载 100/100 完成: 贾玲 - 100张 等待 7.8秒... [9/20] ======================================== 开始: 易烊千玺 易烊千玺: 已下载 20/100 易烊千玺: 已下载 40/100 易烊千玺: 已下载 60/100 易烊千玺: 已下载 80/100 易烊千玺: 已下载 100/100 完成: 易烊千玺 - 100张 等待 8.0秒... [10/20] ======================================== 开始: 王一博 王一博: 已下载 20/100 王一博: 已下载 40/100 王一博: 已下载 60/100 王一博: 已下载 80/100 王一博: 已下载 100/100 完成: 王一博 - 100张 等待 4.1秒... [11/20] ======================================== 开始: 赵丽颖 赵丽颖: 已下载 20/100 赵丽颖: 已下载 40/100 赵丽颖: 已下载 60/100 赵丽颖: 已下载 80/100 赵丽颖: 已下载 100/100 完成: 赵丽颖 - 100张 等待 5.8秒... [12/20] ======================================== 开始: 肖战 肖战: 已下载 20/100 肖战: 已下载 40/100 肖战: 已下载 60/100 肖战: 已下载 80/100 肖战: 已下载 100/100 完成: 肖战 - 100张 等待 4.3秒... [13/20] ======================================== 开始: 李现 李现: 已下载 20/100 李现: 已下载 40/100 李现: 已下载 60/100 李现: 已下载 80/100 李现: 已下载 100/100 完成: 李现 - 100张 等待 5.5秒... [14/20] ======================================== 开始: 杨紫 杨紫: 已下载 20/100 杨紫: 已下载 40/100 杨紫: 已下载 60/100 杨紫: 已下载 80/100 杨紫: 已下载 100/100 完成: 杨紫 - 100张 等待 6.0秒... [15/20] ======================================== 开始: 邓超 邓超: 已下载 20/100 邓超: 已下载 40/100 邓超: 已下载 60/100 邓超: 已下载 80/100 邓超: 已下载 100/100 完成: 邓超 - 100张 等待 4.8秒... [16/20] ======================================== 开始: 孙俪 孙俪: 已下载 20/100 孙俪: 已下载 40/100 孙俪: 已下载 60/100 孙俪: 已下载 80/100 孙俪: 已下载 100/100 完成: 孙俪 - 100张 等待 6.1秒... [17/20] ======================================== 开始: 黄晓明 黄晓明: 已下载 20/100 黄晓明: 已下载 40/100 黄晓明: 已下载 60/100 黄晓明: 已下载 80/100 黄晓明: 已下载 100/100 完成: 黄晓明 - 100张 等待 3.6秒... [18/20] ======================================== 开始: 杨颖 杨颖: 已下载 20/100 杨颖: 已下载 40/100 杨颖: 已下载 60/100 杨颖: 已下载 80/100 杨颖: 已下载 100/100 完成: 杨颖 - 100张 等待 5.6秒... [19/20] ======================================== 开始: 胡歌 胡歌: 已下载 20/100 胡歌: 已下载 40/100 胡歌: 已下载 60/100 胡歌: 已下载 80/100 胡歌: 已下载 100/100 完成: 胡歌 - 100张 等待 4.7秒... [20/20] ======================================== 开始: 刘亦菲 刘亦菲: 已下载 20/100 刘亦菲: 已下载 40/100 刘亦菲: 已下载 60/100 刘亦菲: 已下载 80/100 刘亦菲: 已下载 100/100 完成: 刘亦菲 - 100张 ============================================================ 总计下载: 2000 张图片 完成度: 100.0% 各明星下载数量: 刘德华: 100张 周杰伦: 100张 章子怡: 100张 迪丽热巴: 100张 杨幂: 100张 吴京: 100张 沈腾: 100张 贾玲: 100张 易烊千玺: 100张 王一博: 100张 赵丽颖: 100张 肖战: 100张 李现: 100张 杨紫: 100张 邓超: 100张 孙俪: 100张 黄晓明: 100张 杨颖: 100张 胡歌: 100张 刘亦菲: 100张 完成时间: 2025-12-31 13:45:01 图片保存在: celebrity_images/ # 下载并加载 Haar Cascade 文件 haar_cascade_url = "https://raw.githubusercontent.com/opencv/opencv/master/data/haarcascades/haarcascade_frontalface_default.xml" haar_path = "haarcascade_frontalface_default.xml" if not os.path.exists(haar_path): print("下载 Haar cascade...") import urllib.request urllib.request.urlretrieve(haar_cascade_url, haar_path) face_cascade = cv2.CascadeClassifier(haar_path) def detect_and_crop_face(image_path, target_size=(224, 224)): """检测人脸并返回裁剪后的 RGB 图像""" img = cv2.imread(image_path) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) faces = face_cascade.detectMultiScale(gray, 1.3, 5) if len(faces) == 0: return None # 无人脸 x, y, w, h = faces[0] # 取最大人脸 face = img[y:y+h, x:x+w] face_rgb = cv2.cvtColor(face, cv2.COLOR_BGR2RGB) face_resized = cv2.resize(face_rgb, target_size) return face_resized import os import cv2 import numpy as np import random # 修复:必须导入 random import urllib.request from PIL import Image from tqdm import tqdm import logging # 配置日志 logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s') logger = logging.getLogger(__name__) # ============================= # 初始化人脸检测器 # ============================= haar_path = "haarcascade_frontalface_default.xml" if not os.path.exists(haar_path): logger.info("下载 Haar 分类器...") url = "https://raw.githubusercontent.com/opencv/opencv/master/data/haarcascades/haarcascade_frontalface_default.xml" try: urllib.request.urlretrieve(url, haar_path) logger.info("✅ Haar 分类器下载完成") except Exception as e: logger.error(f"❌ 下载失败: {e}") exit(1) face_cascade = cv2.CascadeClassifier(haar_path) if face_cascade.empty(): logger.error("❌ 无法加载 Haar 分类器,请检查文件是否损坏") exit(1) def detect_and_crop_face(image_path, target_size=224): """ 尽可能检测图像中的人脸,返回裁剪后 RGB 格式的标准化图像 支持镜像翻转重试,增加召回率 """ try: img = cv2.imread(image_path) if img is None: logger.debug(f"⚠️ 图像读取失败: {image_path}") return None gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 第一次尝试:原图检测(宽松参数) faces = face_cascade.detectMultiScale( gray, scaleFactor=1.1, # 更密集扫描 minNeighbors=3, # 降低阈值 minSize=(30, 30) # 允许小脸 ) if len(faces) == 0: # 第二次尝试:水平翻转后再检测 flipped_gray = cv2.flip(gray, 1) faces_flipped = face_cascade.detectMultiScale( flipped_gray, scaleFactor=1.1, minNeighbors=3, minSize=(30, 30) ) if len(faces_flipped) == 0: return None # 真的没找到 # 取第一个,并映射回原坐标 x_f, y_f, w_f, h_f = faces_flipped[0] original_width = img.shape[1] x = original_width - x_f - w_f y, w, h = y_f, w_f, h_f else: x, y, w, h = faces[0] # 扩展边界 10% 防止裁得太紧 pad_w = int(0.1 * w) pad_h = int(0.1 * h) x1 = max(0, x - pad_w) y1 = max(0, y - pad_h) x2 = min(img.shape[1], x + w + pad_w) y2 = min(img.shape[0], y + h + pad_h) face_bgr = img[y1:y2, x1:x2] face_rgb = cv2.cvtColor(face_bgr, cv2.COLOR_BGR2RGB) face_resized = cv2.resize(face_rgb, (target_size, target_size)) return face_resized except Exception as e: logger.warning(f"⚠️ 处理 {image_path} 时出错: {e}") return None def save_cropped(files, folder, celeb_name, phase="train"): """ 批量处理文件并保存裁剪后的人脸 """ saved_count = 0 total_count = len(files) class_dir = os.path.join(folder, celeb_name) os.makedirs(class_dir, exist_ok=True) desc = f"{celeb_name} [{phase}]" for fname in tqdm(files, desc=desc, leave=True): src_path = os.path.join(ROOT_DIR, celeb_name, fname) cropped = detect_and_crop_face(src_path) if cropped is not None: dst_filename = f"{celeb_name}_{saved_count:04d}.jpg" dst_path = os.path.join(class_dir, dst_filename) try: Image.fromarray(cropped).save(dst_path, quality=95) saved_count += 1 except Exception as e: logger.warning(f"⚠️ 保存失败 {dst_path}: {e}") logger.info(f"✅ {celeb_name} [{phase}]: 保存 {saved_count}/{total_count}") return saved_count # ============================= # 主流程配置 # ============================= ROOT_DIR = "celebrity_images" CROPPED_DIR = "cropped_faces" TRAIN_DIR = os.path.join(CROPPED_DIR, "train") TEST_DIR = os.path.join(CROPPED_DIR, "test") os.makedirs(TRAIN_DIR, exist_ok=True) os.makedirs(TEST_DIR, exist_ok=True) celebrities = [ "刘德华", "周杰伦", "章子怡", "迪丽热巴", "杨幂", "吴京", "沈腾", "贾玲", "易烊千玺", "王一博", "赵丽颖", "肖战", "李现", "杨紫", "邓超", "孙俪", "黄晓明", "杨颖", "胡歌", "刘亦菲" ] # ============================= # 开始处理每个明星 # ============================= if __name__ == "__main__": logger.info("🚀 开始人脸裁剪任务") for celeb in celebrities: celeb_dir = os.path.join(ROOT_DIR, celeb) if not os.path.exists(celeb_dir): logger.warning(f"⚠️ 明星目录不存在: {celeb_dir}") continue files = [f for f in os.listdir(celeb_dir) if f.lower().endswith(('.jpg', '.jpeg'))] if len(files) == 0: logger.warning(f"⚠️ 无有效图片: {celeb}") continue # 打乱顺序 random.shuffle(files) split_idx = int(0.8 * len(files)) train_files = files[:split_idx] test_files = files[split_idx:] # 创建子目录 os.makedirs(os.path.join(TRAIN_DIR, celeb), exist_ok=True) os.makedirs(os.path.join(TEST_DIR, celeb), exist_ok=True) # 处理训练集和测试集 save_cropped(train_files, TRAIN_DIR, celeb, "train") save_cropped(test_files, TEST_DIR, celeb, "test") logger.info("🎉 裁剪完成!所有可用人脸图像已保存至 cropped_faces/") INFO: 🚀 开始人脸裁剪任务 刘德华 [train]: 100%|██████████| 80/80 [00:25<00:00, 3.08it/s] INFO: ✅ 刘德华 [train]: 保存 80/80 刘德华 [test]: 100%|██████████| 20/20 [00:06<00:00, 3.33it/s] INFO: ✅ 刘德华 [test]: 保存 20/20 周杰伦 [train]: 100%|██████████| 80/80 [00:42<00:00, 1.90it/s] INFO: ✅ 周杰伦 [train]: 保存 75/80 周杰伦 [test]: 100%|██████████| 20/20 [00:15<00:00, 1.32it/s] INFO: ✅ 周杰伦 [test]: 保存 18/20 章子怡 [train]: 100%|██████████| 80/80 [00:39<00:00, 2.01it/s] INFO: ✅ 章子怡 [train]: 保存 80/80 章子怡 [test]: 100%|██████████| 20/20 [00:11<00:00, 1.82it/s] INFO: ✅ 章子怡 [test]: 保存 20/20 迪丽热巴 [train]: 100%|██████████| 80/80 [00:53<00:00, 1.49it/s] INFO: ✅ 迪丽热巴 [train]: 保存 70/80 迪丽热巴 [test]: 100%|██████████| 20/20 [00:18<00:00, 1.11it/s] INFO: ✅ 迪丽热巴 [test]: 保存 19/20 杨幂 [train]: 100%|██████████| 80/80 [00:34<00:00, 2.33it/s] INFO: ✅ 杨幂 [train]: 保存 72/80 杨幂 [test]: 100%|██████████| 20/20 [00:09<00:00, 2.01it/s] INFO: ✅ 杨幂 [test]: 保存 17/20 吴京 [train]: 100%|██████████| 80/80 [00:26<00:00, 3.01it/s] INFO: ✅ 吴京 [train]: 保存 77/80 吴京 [test]: 100%|██████████| 20/20 [00:09<00:00, 2.13it/s] INFO: ✅ 吴京 [test]: 保存 20/20 沈腾 [train]: 100%|██████████| 80/80 [00:39<00:00, 2.04it/s] INFO: ✅ 沈腾 [train]: 保存 75/80 沈腾 [test]: 100%|██████████| 20/20 [00:04<00:00, 4.48it/s] INFO: ✅ 沈腾 [test]: 保存 19/20 贾玲 [train]: 100%|██████████| 80/80 [00:37<00:00, 2.11it/s] INFO: ✅ 贾玲 [train]: 保存 77/80 贾玲 [test]: 100%|██████████| 20/20 [00:13<00:00, 1.51it/s] INFO: ✅ 贾玲 [test]: 保存 20/20 易烊千玺 [train]: 100%|██████████| 80/80 [00:38<00:00, 2.09it/s] INFO: ✅ 易烊千玺 [train]: 保存 70/80 易烊千玺 [test]: 100%|██████████| 20/20 [00:11<00:00, 1.68it/s] INFO: ✅ 易烊千玺 [test]: 保存 19/20 王一博 [train]: 100%|██████████| 80/80 [00:34<00:00, 2.34it/s] INFO: ✅ 王一博 [train]: 保存 71/80 王一博 [test]: 100%|██████████| 20/20 [00:06<00:00, 3.12it/s] INFO: ✅ 王一博 [test]: 保存 16/20 赵丽颖 [train]: 100%|██████████| 80/80 [00:46<00:00, 1.73it/s] INFO: ✅ 赵丽颖 [train]: 保存 80/80 赵丽颖 [test]: 100%|██████████| 20/20 [00:11<00:00, 1.72it/s] INFO: ✅ 赵丽颖 [test]: 保存 19/20 肖战 [train]: 100%|██████████| 80/80 [00:18<00:00, 4.21it/s] INFO: ✅ 肖战 [train]: 保存 71/80 肖战 [test]: 100%|██████████| 20/20 [00:07<00:00, 2.75it/s] INFO: ✅ 肖战 [test]: 保存 17/20 李现 [train]: 100%|██████████| 80/80 [00:36<00:00, 2.21it/s] INFO: ✅ 李现 [train]: 保存 76/80 李现 [test]: 100%|██████████| 20/20 [00:14<00:00, 1.39it/s] INFO: ✅ 李现 [test]: 保存 19/20 杨紫 [train]: 100%|██████████| 80/80 [00:48<00:00, 1.66it/s] INFO: ✅ 杨紫 [train]: 保存 78/80 杨紫 [test]: 100%|██████████| 20/20 [00:14<00:00, 1.35it/s] INFO: ✅ 杨紫 [test]: 保存 19/20 邓超 [train]: 100%|██████████| 80/80 [00:35<00:00, 2.26it/s] INFO: ✅ 邓超 [train]: 保存 77/80 邓超 [test]: 100%|██████████| 20/20 [00:05<00:00, 3.44it/s] INFO: ✅ 邓超 [test]: 保存 19/20 孙俪 [train]: 100%|██████████| 80/80 [00:37<00:00, 2.15it/s] INFO: ✅ 孙俪 [train]: 保存 80/80 孙俪 [test]: 100%|██████████| 20/20 [00:12<00:00, 1.62it/s] INFO: ✅ 孙俪 [test]: 保存 20/20 黄晓明 [train]: 100%|██████████| 80/80 [00:37<00:00, 2.11it/s] INFO: ✅ 黄晓明 [train]: 保存 77/80 黄晓明 [test]: 100%|██████████| 20/20 [00:12<00:00, 1.57it/s] INFO: ✅ 黄晓明 [test]: 保存 19/20 杨颖 [train]: 100%|██████████| 80/80 [00:50<00:00, 1.57it/s] INFO: ✅ 杨颖 [train]: 保存 76/80 杨颖 [test]: 100%|██████████| 20/20 [00:12<00:00, 1.59it/s] INFO: ✅ 杨颖 [test]: 保存 18/20 胡歌 [train]: 100%|██████████| 80/80 [00:29<00:00, 2.69it/s] INFO: ✅ 胡歌 [train]: 保存 75/80 胡歌 [test]: 100%|██████████| 20/20 [00:11<00:00, 1.68it/s] INFO: ✅ 胡歌 [test]: 保存 19/20 刘亦菲 [train]: 100%|██████████| 80/80 [00:45<00:00, 1.76it/s] INFO: ✅ 刘亦菲 [train]: 保存 76/80 刘亦菲 [test]: 100%|██████████| 20/20 [00:18<00:00, 1.07it/s] INFO: ✅ 刘亦菲 [test]: 保存 18/20 INFO: 🎉 裁剪完成!所有可用人脸图像已保存至 cropped_faces/ class FaceRecognitionNet(fluid.dygraph.Layer): def __init__(self, num_classes=20): super(FaceRecognitionNet, self).__init__() self.conv1 = Conv2D(num_channels=3, num_filters=32, filter_size=3, stride=1, padding=1, act='relu') self.pool1 = Pool2D(pool_size=2, pool_stride=2, pool_type='max') self.bn1 = BatchNorm(num_channels=32, act='relu') self.conv2 = Conv2D(num_channels=32, num_filters=64, filter_size=3, stride=1, padding=1, act='relu') self.pool2 = Pool2D(pool_size=2, pool_stride=2, pool_type='max') self.bn2 = BatchNorm(num_channels=64, act='relu') self.conv3 = Conv2D(num_channels=64, num_filters=128, filter_size=3, stride=1, padding=1, act='relu') self.pool3 = Pool2D(pool_size=2, pool_stride=2, pool_type='max') self.bn3 = BatchNorm(num_channels=128, act='relu') self.fc1 = Linear(input_dim=128*28*28, output_dim=512, act='relu') self.dropout = Dropout(0.5) self.fc2 = Linear(input_dim=512, output_dim=num_classes, act='softmax') def forward(self, x): x = self.conv1(x) x = self.bn1(x) x = self.pool1(x) x = self.conv2(x) x = self.bn2(x) x = self.pool2(x) x = self.conv3(x) x = self.bn3(x) x = self.pool3(x) x = fluid.layers.flatten(x, axis=1) x = self.fc1(x) x = self.dropout(x) x = self.fc2(x) return x # 数据增强和读取 def reader_creator(data_dir, label_dict, mode='train'): def reader(): for label_name in os.listdir(data_dir): class_dir = os.path.join(data_dir, label_name) if not os.path.isdir(class_dir): continue label = label_dict[label_name] for img_name in os.listdir(class_dir): img_path = os.path.join(class_dir, img_name) try: img = Image.open(img_path).convert('RGB').resize((224, 224)) img = np.array(img).astype('float32') / 255.0 if mode == 'train': # 简单数据增强 if random.random() > 0.5: img = img[:, ::-1, :] # 水平翻转 yield img.transpose(2, 0, 1), label except: continue return reader # 创建标签字典 label_dict = {name: idx for idx, name in enumerate(celebrities)} with fluid.dygraph.guard(): model = FaceRecognitionNet(num_classes=20) optimizer = fluid.optimizer.AdamOptimizer(learning_rate=0.001, parameter_list=model.parameters()) train_reader = paddle.batch(reader_creator(TRAIN_DIR, label_dict, 'train'), batch_size=32) test_reader = paddle.batch(reader_creator(TEST_DIR, label_dict, 'test'), batch_size=32) epochs = 30 best_acc = 0.0 for epoch in range(epochs): model.train() total_loss = 0.0 total_acc = 0.0 count = 0 for batch_id, data in enumerate(test_reader()): # ✅ 加了 (),现在可以迭代了 imgs = np.array([x[0] for x in data]).astype('float32') labels = np.array([[x[1]] for x in data]).astype('int64') imgs = fluid.dygraph.to_variable(imgs) labels = fluid.dygraph.to_variable(labels) logits = model(imgs) loss = fluid.layers.cross_entropy(logits, labels) avg_loss = fluid.layers.mean(loss) acc = fluid.layers.accuracy(logits, labels) avg_loss.backward() optimizer.minimize(avg_loss) model.clear_gradients() total_loss += avg_loss.numpy()[0] total_acc += acc.numpy()[0] count += 1 print(f"Epoch {epoch+1}, Loss: {total_loss/count:.4f}, Acc: {total_acc/count:.4f}") # 测试阶段 model.eval() test_acc = 0.0 test_count = 0 for batch_id, data in enumerate(test_reader()): # ← 加上 () imgs = np.array([x[0] for x in data]).astype('float32') labels = np.array([[x[1]] for x in data]).astype('int64') imgs = fluid.dygraph.to_variable(imgs) labels = fluid.dygraph.to_variable(labels) logits = model(imgs) acc = fluid.layers.accuracy(logits, labels) test_acc += acc.numpy()[0] test_count += 1 test_acc /= test_count print(f"Test Accuracy: {test_acc:.4f}") if test_acc > best_acc: best_acc = test_acc fluid.save_dygraph(model.state_dict(), "face_recognition_model_best") print(f"✅ 人脸识别模型最高准确率: {best_acc:.4f}") Epoch 1, Loss: 35.5900, Acc: 0.0052 Test Accuracy: 0.0755 Epoch 2, Loss: 5.3439, Acc: 0.2035 Test Accuracy: 0.1360 Epoch 3, Loss: 3.1714, Acc: 0.2670 Test Accuracy: 0.2659 Epoch 4, Loss: 2.3204, Acc: 0.3503 Test Accuracy: 0.3807 Epoch 5, Loss: 2.0990, Acc: 0.3743 Test Accuracy: 0.5175 Epoch 6, Loss: 1.9403, Acc: 0.4483 Test Accuracy: 0.5964 Epoch 7, Loss: 1.7637, Acc: 0.4608 Test Accuracy: 0.6725 Exception ignored in: <generator object reader_creator.<locals>.reader at 0x7f20ad638b50> RuntimeError: generator ignored GeneratorExit --------------------------------------------------------------------------- KeyboardInterrupt Traceback (most recent call last) /tmp/ipykernel_2136/1186155192.py in <module> 46 labels = fluid.dygraph.to_variable(labels) 47 ---> 48 logits = model(imgs) 49 loss = fluid.layers.cross_entropy(logits, labels) 50 avg_loss = fluid.layers.mean(loss) /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/paddle/fluid/dygraph/layers.py in __call__(self, *inputs, **kwargs) 459 460 with param_guard(self._parameters): --> 461 outputs = self.forward(*inputs, **kwargs) 462 463 for forward_post_hook in self._forward_post_hooks.values(): /tmp/ipykernel_2136/1368681083.py in forward(self, x) 21 x = self.conv1(x) 22 x = self.bn1(x) ---> 23 x = self.pool1(x) 24 25 x = self.conv2(x) /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/paddle/fluid/dygraph/layers.py in __call__(self, *inputs, **kwargs) 459 460 with param_guard(self._parameters): --> 461 outputs = self.forward(*inputs, **kwargs) 462 463 for forward_post_hook in self._forward_post_hooks.values(): /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/paddle/fluid/dygraph/nn.py in forward(self, input) 839 'use_cudnn', self._use_cudnn, 'ceil_mode', self._ceil_mode, 840 'use_mkldnn', False, 'exclusive', self._exclusive) --> 841 return core.ops.pool2d(input, *attrs) 842 843 check_variable_and_dtype( KeyboardInterrupt: # 假设你前面已经运行过 create_dataset_from_directories() 并得到 df # 如果没有,重新定义一次: import pandas as pd import os def create_dataset_from_directories(base_dir='work'): emotion_labels = { 0: 'Angry', 1: 'Disgust', 2: 'Fear', 3: 'Happy', 4: 'Sad', 5: 'Surprise', 6: 'Neutral' } records = [] dataset_types = ['train', 'val', 'test'] for dataset_type in dataset_types: dataset_dir = os.path.join(base_dir, dataset_type) if not os.path.exists(dataset_dir): print(f"警告: {dataset_dir} 目录不存在") continue for emotion_str in os.listdir(dataset_dir): if emotion_str.isdigit(): emotion = int(emotion_str) emotion_dir = os.path.join(dataset_dir, emotion_str) if not os.path.isdir(emotion_dir): continue for img_file in os.listdir(emotion_dir): if img_file.lower().endswith(('.jpg', '.jpeg', '.png')): img_path = os.path.join(emotion_dir, img_file) records.append({ 'emotion': emotion, 'image_path': img_path, 'dataset_type': dataset_type }) return pd.DataFrame(records) # 重建 df(如果你当前环境中还没有) df = create_dataset_from_directories() print("数据集形状:", df.shape) 数据集形状: (83571, 3) class EmotionNet(fluid.dygraph.Layer): def __init__(self, num_classes=7): super(EmotionNet, self).__init__() self.conv1 = Conv2D(3, 64, 7, stride=2, padding=3, act='relu') self.bn1 = BatchNorm(64, act='relu') self.pool1 = Pool2D(pool_size=3, pool_stride=2, pool_type='max') # Block 1 self.conv2a = Conv2D(64, 64, 3, padding=1, act=None) self.bn2a = BatchNorm(64, act='relu') self.conv2b = Conv2D(64, 64, 3, padding=1, act=None) self.bn2b = BatchNorm(64, act=None) # Block 2 self.conv3a = Conv2D(64, 128, 3, stride=2, padding=1, act=None) self.bn3a = BatchNorm(128, act='relu') self.conv3b = Conv2D(128, 128, 3, padding=1, act=None) self.bn3b = BatchNorm(128, act=None) self.downsample = Conv2D(64, 128, 1, stride=2, act=None) # 分类头 self.pool2 = Pool2D(pool_size=7, pool_type='avg') self.fc = Linear(128, num_classes, act='softmax') def forward(self, x): x = self.conv1(x) x = self.bn1(x) x = self.pool1(x) # Block 1 residual = x out = self.conv2a(x) out = self.bn2a(out) out = self.conv2b(out) out = self.bn2b(out) out += residual # 残差连接 out = fluid.layers.relu(out) # Block 2 residual = self.downsample(x) out = self.conv3a(out) out = self.bn3a(out) out = self.conv3b(out) out = self.bn3b(out) out += residual out = fluid.layers.relu(out) # 全局平均池化 + 分类 out = self.pool2(out) out = fluid.layers.flatten(out, axis=1) out = self.fc(out) return out with fluid.dygraph.guard(): print("开始训练表情识别模型...") model = EmotionNet(num_classes=7) optimizer = fluid.optimizer.AdamOptimizer( learning_rate=fluid.layers.piecewise_decay( boundaries=[10000, 20000], values=[0.001, 0.0005, 0.0001] ), parameter_list=model.parameters() ) batch_size = 64 epochs = 30 best_acc = 0.0 for epoch in range(epochs): model.train() total_loss = 0.0 total_acc = 0.0 count = 0 # ✅ 使用正确的变量名 idxs = list(range(len(X_train))) np.random.shuffle(idxs) for i in tqdm(range(0, len(idxs), batch_size), desc=f"Epoch {epoch+1}/{epochs}"): batch_idx = idxs[i:i+batch_size] x_batch = X_train[batch_idx] y_batch = y_train[batch_idx] x_tensor = fluid.dygraph.to_variable(x_batch) y_tensor = fluid.dygraph.to_variable(y_batch) output = model(x_tensor) loss = fluid.layers.cross_entropy(output, y_tensor) avg_loss = fluid.layers.mean(loss) acc = fluid.layers.accuracy(output, y_tensor) avg_loss.backward() optimizer.minimize(avg_loss) model.clear_gradients() total_loss += avg_loss.numpy()[0] total_acc += acc.numpy()[0] count += 1 if count > 0: print(f"Train Loss: {total_loss/count:.4f}, Acc: {total_acc/count:.4f}") else: print("⚠️ 训练集为空,跳过该轮") continue # 验证阶段 model.eval() val_acc = 0.0 val_count = 0 for i in range(0, len(X_val), batch_size): x_batch = X_val[i:i+batch_size] y_batch = y_val[i:i+batch_size] x_var = fluid.dygraph.to_variable(x_batch) y_var = fluid.dygraph.to_variable(y_batch) pred = model(x_var) acc = fluid.layers.accuracy(pred, y_var) val_acc += acc.numpy()[0] val_count += 1 if val_count > 0: val_acc /= val_count else: val_acc = 0.0 print(f"Validation Accuracy: {val_acc:.4f}") if val_acc > best_acc: best_acc = val_acc fluid.save_dygraph(model.state_dict(), "emotion_model_best") print(f"✅ 新的最佳模型已保存,准确率: {best_acc:.4f}") print(f"\n🎉 表情识别模型训练完成!最高验证准确率: {best_acc:.4f}") 开始训练表情识别模型... --------------------------------------------------------------------------- NameError Traceback (most recent call last) /tmp/ipykernel_2891/30154203.py in <module> 22 23 # ✅ 使用正确的变量名 ---> 24 idxs = list(range(len(X_train))) 25 np.random.shuffle(idxs) 26 NameError: name 'X_train' is not defined with fluid.dygraph.guard(): print("开始训练表情识别模型...") model = EmotionNet(num_classes=7) optimizer = fluid.optimizer.AdamOptimizer( learning_rate=fluid.layers.piecewise_decay( boundaries=[10000, 20000], values=[0.001, 0.0005, 0.0001] ), parameter_list=model.parameters() ) batch_size = 64 epochs = 30 best_acc = 0.0 for epoch in range(epochs): model.train() total_loss = 0.0 total_acc = 0.0 count = 0 # 打乱训练数据 idxs = list(range(len(X_train_3ch))) np.random.shuffle(idxs) for i in tqdm(range(0, len(idxs), batch_size), desc=f"Epoch {epoch+1}/{epochs}"): batch_idx = idxs[i:i+batch_size] x_batch = X_train_3ch[batch_idx] y_batch = y_train[batch_idx] x_tensor = fluid.dygraph.to_variable(x_batch) y_tensor = fluid.dygraph.to_variable(y_batch) output = model(x_tensor) loss = fluid.layers.cross_entropy(output, y_tensor) avg_loss = fluid.layers.mean(loss) acc = fluid.layers.accuracy(output, y_tensor) avg_loss.backward() optimizer.minimize(avg_loss) model.clear_gradients() total_loss += avg_loss.numpy()[0] total_acc += acc.numpy()[0] count += 1 print(f"Train Loss: {total_loss/count:.4f}, Acc: {total_acc/count:.4f}") # 验证阶段 model.eval() val_acc = 0.0 val_count = 0 for i in range(0, len(X_val_3ch), batch_size): x_batch = X_val_3ch[i:i+batch_size] y_batch = y_val[i:i+batch_size] x_var = fluid.dygraph.to_variable(x_batch) y_var = fluid.dygraph.to_variable(y_batch) pred = model(x_var) acc = fluid.layers.accuracy(pred, y_var) val_acc += acc.numpy()[0] val_count += 1 val_acc /= val_count print(f"Validation Accuracy: {val_acc:.4f}") # 保存最佳模型 if val_acc > best_acc: best_acc = val_acc fluid.save_dygraph(model.state_dict(), "emotion_model_best") print(f"✅ 新的最佳模型已保存,准确率: {best_acc:.4f}") print(f"\n🎉 表情识别模型训练完成!最高验证准确率: {best_acc:.4f}") 开始训练表情识别模型... Epoch 1/30: 0it [00:00, ?it/s] --------------------------------------------------------------------------- ZeroDivisionError Traceback (most recent call last) /tmp/ipykernel_99/1490584108.py in <module> 46 count += 1 47 ---> 48 print(f"Train Loss: {total_loss/count:.4f}, Acc: {total_acc/count:.4f}") 49 50 # 验证阶段 ZeroDivisionError: float division by zero # 示例:假设有部分标注文件 scores.json {"image_path": "score"} scores_data = { "刘德华_0001.jpg": 8.5, "迪丽热巴_0002.jpg": 9.2, # ... 添加更多 } # 构造数据集(仅作演示) def create_beauty_dataset(): X_bea, y_bea = [], [] score_dir = os.path.join(CROPPED_DIR, "train") # 使用裁剪后的人脸 for img_file, score in scores_data.items(): celeb = img_file.split('_')[0] path = os.path.join(score_dir, celeb, img_file) if os.path.exists(path): img = Image.open(path).convert('RGB').resize((224, 224)) X_bea.append(np.array(img).astype('float32') / 255.0) y_bea.append(score) return np.array(X_bea), np.array(y_bea) X_bea, y_bea = create_beauty_dataset() X_bea = X_bea.transpose(0, 3, 1, 2) # 颜值回归网络 class BeautyNet(fluid.dygraph.Layer): def __init__(self): super(BeautyNet, self).__init__() self.conv1 = Conv2D(3, 32, 3, padding=1, act='relu') self.pool1 = Pool2D(2, 2, 'max') self.conv2 = Conv2D(32, 64, 3, padding=1, act='relu') self.pool2 = Pool2D(2, 2, 'max') self.fc1 = Linear(64*56*56, 128, act='relu') self.fc2 = Linear(128, 1, act=None) # 回归输出 def forward(self, x): x = self.conv1(x) x = self.pool1(x) x = self.conv2(x) x = self.pool2(x) x = fluid.layers.flatten(x, axis=1) x = self.fc1(x) x = self.fc2(x) return x # 训练颜值模型(需足够标注数据) with fluid.dygraph.guard(): model_be = BeautyNet() opt = fluid.optimizer.AdamOptimizer(0.001, parameter_list=model_be.parameters()) # 这里仅为示意(真实需要更多数据) if len(X_bea) >= 32: dataset = [(X_bea[i], y_bea[i]) for i in range(len(X_bea))] for epoch in range(20): total_loss = 0.0 for i in range(0, len(dataset), 8): xb = np.stack([d[0] for d in dataset[i:i+8]], axis=0) yb = np.array([d[1] for d in dataset[i:i+8]]).reshape(-1, 1).astype('float32') xb_var = fluid.dygraph.to_variable(xb) yb_var = fluid.dygraph.to_variable(yb) pred = model_be(xb_var) loss = fluid.layers.mse_loss(pred, yb_var) avg_loss = fluid.layers.mean(loss) avg_loss.backward() opt.minimize(avg_loss) model_be.clear_gradients() total_loss += avg_loss.numpy()[0] print(f"Beauty Epoch {epoch+1}, Loss: {total_loss:.4f}") fluid.save_dygraph(model_be.state_dict(), "beauty_model") else: print("⚠️ 标注数据不足,跳过颜值训练") def inference_pipeline(image_path): """输入一张图,输出识别结果""" # 加载图像 raw_img = Image.open(image_path).convert('RGB') orig_w, orig_h = raw_img.size img_array = np.array(raw_img) # 步骤1:检测人脸 face_img = detect_and_crop_face(image_path) if face_img is None: print("未检测到人脸") return None face_tensor = np.transpose(face_img, (2, 0, 1)).astype('float32') / 255.0 face_tensor = face_tensor[np.newaxis, ...] # 加载模型进行推理 with fluid.dygraph.guard(): # --- 人脸识别 --- model_fr = FaceRecognitionNet(20) params, _ = fluid.load_dygraph("face_recognition_model_best") model_fr.load_dict(params) model_fr.eval() fr_out = model_fr(fluid.dygraph.to_variable(face_tensor)) fr_pred = np.argmax(fr_out.numpy()) fr_name = [k for k,v in label_dict.items()][fr_pred] # --- 表情识别 --- # 注意:需将 RGB 转灰度再复制为三通道 gray_face = cv2.cvtColor(face_img, cv2.COLOR_RGB2GRAY) gray_3ch = np.stack([gray_face]*3, axis=-1) gray_3ch = cv2.resize(gray_3ch, (224, 224)) em_tensor = np.transpose(gray_3ch, (2, 0, 1))[np.newaxis, ...].astype('float32') / 255.0 model_em = EmotionNet(7) params, _ = fluid.load_dygraph("emotion_model_best") model_em.load_dict(params) model_em.eval() em_out = model_em(fluid.dygraph.to_variable(em_tensor)) em_pred = np.argmax(em_out.numpy()) emotion = EMOTION_MAP[em_pred] # --- 颜值打分 --- model_be = BeautyNet() try: params, _ = fluid.load_dygraph("beauty_model") model_be.load_dict(params) model_be.eval() be_tensor = np.transpose(face_img, (2, 0, 1))[np.newaxis, ...].astype('float32') / 255.0 beauty_score = model_be(fluid.dygraph.to_variable(be_tensor)).numpy()[0][0] beauty_score = max(0, min(10, beauty_score)) # 截断到 [0,10] except: beauty_score = "未知(需更多标注)" # 绘图显示 fig, ax = plt.subplots(1, 1, figsize=(8, 6)) ax.imshow(img_array) result_text = f"姓名: {fr_name}\n表情: {emotion}\n颜值: {beauty_score:.2f}" if isinstance(beauty_score, float) else f"姓名: {fr_name}\n表情: {emotion}\n颜值: {beauty_score}" ax.text(orig_w//2, orig_h - 50, result_text, fontsize=14, bbox=dict(facecolor='yellow', alpha=0.7)) ax.axis('off') plt.show() return { "name": fr_name, "emotion": emotion, "beauty_score": beauty_score } result = inference_pipeline("celebrity_images/刘德华/刘德华_0001.jpg") print(result) 请点击此处查看本环境基本用法. Please click here for more detailed instructions.按文件中的要求将以上代码中的表情识别使用一下示例: 9+ 项目大厅> 项目详情 表情分类升级版 0 表情分类,paddle2.2升级版 2022-01-11 15:28:28 AI Studio 经典版 JupyterLab 2.2.2 Python3 版本内容 数据集 Fork记录 评论 当前版本: 第一版 01-11 15:33:51 当前内容阅读耗时约22分钟,试试 小桨总结 实践名称:表情分类 任务:本次识别是一个图像二分类任务,利用卷积神经网络实现图像中表情的分类 实践平台:百度AI实训平台-AI Studio、python3.7+飞桨2.2.1 卷积神经网络(CNN) 卷积神经网络(Convolution Neural Network,简称CNN),CNN 其实可以看作 DNN 的一种特殊形式。它跟传统 DNN 标志性的区别在于两点,Convolution Kernel 以及 Pooling。 数据集介绍 网上公开的人脸表情图像数据集: 包含positive和negative两种表情,共7200余张图片 图片为16464,灰度图像 本次实验中,取其中的10%作为测试集,90%作为训练集 In [1] # 解压数据集 !cd 'data/data71015' && unzip -q face_data.zip In [2] # 导入所需要的 import os import pandas as pd import numpy as np from PIL import Image import paddle import paddle.nn as nn from paddle.io import Dataset import paddle.vision.transforms as T import paddle.nn.functional as F from paddle.metric import Accuracy import warnings warnings.filterwarnings("ignore") In [3] all_file_dir = 'data/data71015/face_data' img_list = [] label_list = [] label_id = 0 class_list = [c for c in os.listdir(all_file_dir) if os.path.isdir(os.path.join(all_file_dir, c))] for class_dir in class_list: image_path_pre = os.path.join(all_file_dir, class_dir) for img in os.listdir(image_path_pre): img_list.append(os.path.join(image_path_pre, img)) label_list.append(label_id) label_id += 1 img_df = pd.DataFrame(img_list) label_df = pd.DataFrame(label_list) img_df.columns = ['images'] label_df.columns = ['label'] df = pd.concat([img_df, label_df], axis=1) df = df.reindex(np.random.permutation(df.index)) df.to_csv('face_data.csv', index=0) In [4] # 读取数据 df = pd.read_csv('face_data.csv') image_path_list = df['images'].values label_list = df['label'].values # 划分训练集和校验集 all_size = len(image_path_list) train_size = int(all_size * 0.8) train_image_path_list = image_path_list[:train_size] train_label_list = label_list[:train_size] val_image_path_list = image_path_list[train_size:] val_label_list = label_list[train_size:] In [5] class MyDataset(paddle.io.Dataset): """ 步骤一:继承paddle.io.Dataset类 """ def __init__(self, train_img_list, val_img_list,train_label_list,val_label_list, mode='train'): """ 步骤二:实现构造函数,定义数据读取方式,划分训练和测试数据集 """ super(MyDataset, self).__init__() self.img = [] self.label = [] # 借助pandas读csv的 self.train_images = train_img_list self.test_images = val_img_list self.train_label = train_label_list self.test_label = val_label_list if mode == 'train': # 读train_images的数据 for img,la in zip(self.train_images, self.train_label): self.img.append(img) self.label.append(la) else: # 读test_images的数据 for img,la in zip(self.train_images, self.train_label): self.img.append(img) self.label.append(la) def load_img(self, image_path): # 实际使用时使用Pillow相关进行图片读取即可,这里我们对数据先做个模拟 image = Image.open(image_path) img = image.resize((32, 32), Image.BILINEAR) #Image.BILINEAR双线性插值 img = np.array(img).astype('float32') #Normalize img = img / 255 #像素值归一化 return img[np.newaxis, : ,: ] def __getitem__(self, index): """ 步骤三:实现__getitem__方法,定义指定index时如何获取数据,并返回单条数据(训练数据,对应的标签) """ image = self.load_img(self.img[index]) label = self.label[index] return image, label def __len__(self): """ 步骤四:实现__len__方法,返回数据集总数目 """ return len(self.img) In [6] #train_loader train_dataset = MyDataset(train_img_list=train_image_path_list, val_img_list=val_image_path_list, train_label_list=train_label_list, val_label_list=val_label_list, mode='train') train_loader = paddle.io.DataLoader(train_dataset, places=paddle.CPUPlace(), batch_size=32, shuffle=True, num_workers=0) #val_loader val_dataset = MyDataset(train_img_list=train_image_path_list, val_img_list=val_image_path_list, train_label_list=train_label_list, val_label_list=val_label_list, mode='test') val_loader = paddle.io.DataLoader(val_dataset, places=paddle.CPUPlace(), batch_size=32, shuffle=True, num_workers=0) In [7] print('=============train dataset=============') for image, label in train_dataset: print('image shape: {}, label: {}'.format(image.shape, label)) break In [8] #定义网络 class LeNet(nn.Layer): def __init__(self, num_classes=10): super(LeNet, self).__init__() self.num_classes = num_classes self.features = nn.Sequential( nn.Conv2D( 1, 6, 3, stride=1, padding=1), nn.ReLU(), nn.MaxPool2D(2, 2), nn.Conv2D( 6, 16, 5, stride=1, padding=0), nn.ReLU(), nn.MaxPool2D(2, 2)) if num_classes > 0: self.fc = nn.Sequential( nn.Linear(576, 120), nn.Linear(120, 84), nn.Linear(84, num_classes)) def forward(self, inputs): x = self.features(inputs) if self.num_classes > 0: x = paddle.flatten(x, 1) x = self.fc(x) return x In [9] # 模型封装 model_le = LeNet(num_classes=2) model = paddle.Model(model_le) # 模型可视化 model.summary((1, 1, 32, 32)) In [10] # 定义优化器 optim = paddle.optimizer.Adam(learning_rate=3e-4, parameters=model.parameters()) # 配置模型 model.prepare( optim, paddle.nn.CrossEntropyLoss(soft_label=False), Accuracy() ) # 模型训练与评估 model.fit(train_loader, val_loader, log_freq=1, epochs=3, verbose=1, ) In [11] # 保存模型参数 # model.save('Hapi_MyCNN') # save for training model.save('Hapi_MyCNN1', False) # save for inference In [12] import os, time import matplotlib.pyplot as plt import paddle from PIL import Image import numpy as np import pandas as pd def load_image(img_path): ''' 预测图片预处理 ''' img = Image.open(img_path) plt.imshow(img) #根据数组绘制图像 plt.show() #resize img = img.resize((32, 32), Image.BILINEAR) #Image.BILINEAR双线性插值 img = np.array(img).astype('float32') # HWC to CHW #Normalize img = img / 255 #像素值归一化 return img[np.newaxis, : ,: ] def infer_img(path, model): ''' 模型预测 ''' #对预测图片进行预处理 infer_imgs = [] infer_imgs.append(load_image(path)) infer_imgs = np.array(infer_imgs) label_pre = [] label_list = ['0:Possitive', '1:Negative'] for i in range(len(infer_imgs)): data = infer_imgs[i] dy_x_data = np.array(data).astype('float32') dy_x_data = dy_x_data[np.newaxis, : ,: ,:] img = paddle.to_tensor(dy_x_data) out = model(img) lab = np.argmax(out.numpy()) #argmax():返回最大数的索引 print("样本: {},被预测为:{}".format(path, label_list[lab])) return label_pre if __name__ == '__main__': use_gpu = True model_file_path="Hapi_MyCNN1" paddle.set_device('gpu:0') if use_gpu else paddle.set_device('cpu') model = paddle.jit.load(model_file_path) model.eval() #训练模式 infer_img('data/data71015/face_data/Negative/2066.jpg', model) 项目贡献人 AIStudio383107 关注 26.5k 项目热度 5.5k 被Fork数 1 数据集引用 相关项目 查看更多 【Datacapsule】基于文心4.5构建知识图谱的高精度产业级RAG解决方案 自然语言处理 大模型 37.4K 【ERNIE 4.5】ERNIEKit 工业级高性能训练工具快速上手! 自然语言处理 大模型 ··· 1.7K 【ERNIE-4.5-21B-A3B】让文心开源大模型带你进入哆啦A梦的世界 自然语言处理 大模型 1.0K 文心4.5开源大模型的使用和部署 大模型 718 【新手入门】使用ERNIE-4.5-0.3B-Paddle从原始文本构建知识图谱 自然语言处理 大模型 ··· 2.2K 助手
最新发布
01-01
你正在完成一个非常完整的 **结课项目:人脸识别 + 表情识别 + 颜值打分**,并且已经成功完成了以下步骤: - ✅ 爬取明星图片 - ✅ 人脸检测与裁剪 - ❌ 但表情识别部分训练失败(`X_train_3ch` 未定义) - 🔁 当前目标是:**使用 AI Studio 上的“表情分类升级版”示例来重构并正确实现表情识别模块** --- 我们将按照该示例(Paddle 2.2+ 动态图风格)对你的原始 Paddle 1.8 风格代码进行 **全面升级和迁移**,并解决所有报错问题。 --- ## ✅ 最终目标 > 使用飞桨 PaddlePaddle 2.6+ 实现一个 **端到端的表情分类模型(7类:Angry, Disgust, Fear, Happy, Sad, Surprise, Neutral)**,基于现代 `paddle.nn.Layer`, `Dataset`, `DataLoader`, `Model.prepare()` 等 API。 --- # 🧩 第一步:加载数据集并创建 DataFrame 假设你有一个包含表情图像的数据结构如下: ``` work/ └── fer2013/ ├── train/ │ ├── 0_Angry/ │ ├── 1_Disgust/ │ ├── ... │ └── 6_Neutral/ └── val/ ├── ... ``` 如果你没有真实数据,我们用 FER2013 或公开灰度表情数据集模拟。 ```python import os import pandas as pd from pathlib import Path def create_emotion_dataset(base_dir='work/fer2013'): """ 构建情绪数据集 DataFrame """ records = [] label_map = { '0_Angry': 0, '1_Disgust': 1, '2_Fear': 2, '3_Happy': 3, '4_Sad': 4, '5_Surprise': 5, '6_Neutral': 6 } for split in ['train', 'val']: split_dir = Path(base_dir) / split if not split_dir.exists(): print(f"⚠️ 路径不存在: {split_dir}") continue for cls_name in sorted(os.listdir(split_dir)): if cls_name not in label_map: continue class_path = split_dir / cls_name if not class_path.is_dir(): continue for img_file in class_path.iterdir(): if img_file.suffix.lower() in ['.jpg', '.jpeg', '.png']: records.append({ 'image_path': str(img_file), 'label': label_map[cls_name], 'dataset_type': split }) df = pd.DataFrame(records) print(f"✅ 数据集构建完成,共 {len(df)} 张图像") return df # 执行构建 df = create_emotion_dataset() print(df.head()) ``` --- # 🧱 第二步:自定义 Dataset 类(继承 `paddle.io.Dataset`) ```python import paddle from paddle.io import Dataset from PIL import Image import numpy as np class EmotionDataset(Dataset): def __init__(self, df, transform=None): super().__init__() self.df = df.reset_index(drop=True) self.transform = transform def __getitem__(self, idx): row = self.df.iloc[idx] img_path = row['image_path'] label = row['label'] # 读取图像(灰度转为三通道) try: img = Image.open(img_path).convert('L') # 灰度图 img = img.resize((224, 224), Image.BILINEAR) img = np.array(img).astype('float32') img = np.stack([img] * 3, axis=0) # (H, W) -> (C, H, W) img /= 255.0 # 归一化 except Exception as e: print(f"⚠️ 图像读取失败: {img_path}, 错误: {e}") return None if self.transform: img = self.transform(img) return img, label def __len__(self): return len(self.df) ``` --- # 🔄 第三步:定义数据增强与 DataLoader ```python from paddle.vision.transforms import Compose, RandomHorizontalFlip, Normalize # 定义变换 transform_train = Compose([ RandomHorizontalFlip(p=0.5), Normalize(mean=[0.5], std=[0.5], data_format='CHW'), # 单通道归一化 ]) transform_val = Compose([ Normalize(mean=[0.5], std=[0.5], data_format='CHW'), ]) # 分割数据 train_df = df[df['dataset_type'] == 'train'].reset_index(drop=True) val_df = df[df['dataset_type'] == 'val'].reset_index(drop=True) # 创建 dataset train_dataset = EmotionDataset(train_df, transform=transform_train) val_dataset = EmotionDataset(val_df, transform=transform_val) # 创建 dataloader batch_size = 64 train_loader = paddle.io.DataLoader(train_dataset, batch_size=batch_size, shuffle=True, num_workers=2) val_loader = paddle.io.DataLoader(val_dataset, batch_size=batch_size, shuffle=False, num_workers=2) ``` --- # 🏗️ 第四步:定义 EmotionNet 模型(ResNet-like 结构) ```python import paddle import paddle.nn as nn import paddle.nn.functional as F class BasicBlock(nn.Layer): expansion = 1 def __init__(self, in_channels, out_channels, stride=1, downsample=None): super().__init__() self.conv1 = nn.Conv2D(in_channels, out_channels, 3, stride, padding=1) self.bn1 = nn.BatchNorm2D(out_channels) self.conv2 = nn.Conv2D(out_channels, out_channels, 3, 1, padding=1) self.bn2 = nn.BatchNorm2D(out_channels) self.downsample = downsample self.stride = stride def forward(self, x): identity = x out = F.relu(self.bn1(self.conv1(x))) out = self.bn2(self.conv2(out)) if self.downsample is not None: identity = self.downsample(x) out += identity out = F.relu(out) return out class EmotionNet(nn.Layer): def __init__(self, block, layers, num_classes=7): super().__init__() self.in_channels = 64 self.conv1 = nn.Conv2D(3, 64, kernel_size=7, stride=2, padding=3) self.bn1 = nn.BatchNorm2D(64) self.pool1 = nn.MaxPool2D(kernel_size=3, stride=2, padding=1) self.layer1 = self._make_layer(block, 64, layers[0]) self.layer2 = self._make_layer(block, 128, layers[1], stride=2) self.avgpool = nn.AdaptiveAvgPool2D((1, 1)) self.fc = nn.Linear(128, num_classes) def _make_layer(self, block, channels, blocks, stride=1): downsample = None if stride != 1 or self.in_channels != channels: downsample = nn.Sequential( nn.Conv2D(self.in_channels, channels, 1, stride), nn.BatchNorm2D(channels) ) layers = [] layers.append(block(self.in_channels, channels, stride, downsample)) self.in_channels = channels for _ in range(1, blocks): layers.append(block(channels, channels)) return nn.Sequential(*layers) def forward(self, x): x = F.relu(self.bn1(self.conv1(x))) x = self.pool1(x) x = self.layer1(x) x = self.layer2(x) x = self.avgpool(x) x = paddle.flatten(x, 1) x = self.fc(x) return x # 实例化模型 model = EmotionNet(BasicBlock, [2, 2], num_classes=7) print("✅ 模型结构已创建") ``` --- # ⚙️ 第五步:使用 `paddle.Model` 封装训练流程(推荐方式) ```python # 模型封装 model_wrapper = paddle.Model(model) # 可视化模型结构 model_wrapper.summary((1, 3, 224, 224)) # 定义优化器 optimizer = paddle.optimizer.Adam(learning_rate=1e-3, parameters=model.parameters()) # 配置模型 model_wrapper.prepare( optimizer=optimizer, loss=paddle.nn.CrossEntropyLoss(), metrics=paddle.metric.Accuracy() ) ``` --- # 🚀 第六步:开始训练 ```python # 开始训练 print("🔥 开始训练表情识别模型...") model_wrapper.fit( train_data=train_loader, eval_data=val_loader, epochs=20, save_dir="checkpoints/emotion", save_freq=5, verbose=1 ) # 保存推理模型 paddle.jit.save( layer=model, path="inference_models/emotion_net", input_spec=[paddle.static.InputSpec(shape=[None, 3, 224, 224], dtype='float32')] ) print("🎉 模型训练完成并导出") ``` --- # 📊 第七步:推理预测函数 ```python EMOTION_LABELS = ['Angry', 'Disgust', 'Fear', 'Happy', 'Sad', 'Surprise', 'Neutral'] def predict_emotion(image_path): # 加载模型 model = paddle.jit.load("inference_models/emotion_net") model.eval() # 预处理图像 img = Image.open(image_path).convert('L') img = img.resize((224, 224), Image.BILINEAR) img = np.array(img).astype('float32') img = np.stack([img]*3, axis=0) # 复制为三通道 img = img[np.newaxis, ...] # 增加 batch 维度 img /= 255.0 img -= 0.5 img /= 0.5 # 推理 tensor = paddle.to_tensor(img) logits = model(tensor) pred_idx = paddle.argmax(logits).item() prob = F.softmax(logits).numpy()[0] print(f"🎯 预测结果: {EMOTION_LABELS[pred_idx]}") print(f"📊 各类概率:") for i, lbl in enumerate(EMOTION_LABELS): print(f" {lbl}: {prob[i]:.3f}") # 显示图像 plt.figure(figsize=(6, 6)) plt.imshow(Image.open(image_path), cmap='gray') plt.title(f"Predicted: {EMOTION_LABELS[pred_idx]}") plt.axis('off') plt.show() return EMOTION_LABELS[pred_idx] # 示例调用 predict_emotion("work/fer2013/val/3_Happy/1001.jpg") ``` --- # ✅ 总结:你现在拥有了什么? | 功能 | 已完成 | |------|--------| | ✅ 数据加载 | 使用 `Dataset` + `DataLoader` | | ✅ 模型结构 | ResNet-like CNN with residual block | | ✅ 训练方式 | 使用 `paddle.Model` 高阶 API | | ✅ 推理支持 | 导出 `.pdmodel/.pdiparams` 可部署 | | ✅ 支持灰度图输入 | 自动复制通道 | --- # 💡 提示与建议 1. **如果显存不足** → 改小 batch_size 或使用更小网络(如 MobileNetV1) 2. **提升准确率** → 添加更多数据增强、学习率调度器、预训练权重 3. **集成进主流程** → 修改 `inference_pipeline()` 中的表情分支为新模型 --- ##
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值