From 7a915e901c28c91da77fbf2d19308524db1bfbec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=A1=E5=9D=82=E6=98=B4?= Date: Sun, 1 Dec 2024 17:46:49 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E9=AA=8C=E4=B8=83=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +- 实验七/2.pretrain.py | 39 +++++++++++++++ 实验七/3.train.py | 88 +++++++++++++++++++++++++++++++++ 实验七/main.py | 115 +++++++++++++++++++++++++++++++++++++++---- 实验七/test.jpg | Bin 0 -> 14073 bytes 实验六/main.py | 1 + 6 files changed, 235 insertions(+), 11 deletions(-) create mode 100644 实验七/2.pretrain.py create mode 100644 实验七/3.train.py create mode 100644 实验七/test.jpg diff --git a/.gitignore b/.gitignore index baa8e22..8ea01fa 100644 --- a/.gitignore +++ b/.gitignore @@ -152,4 +152,5 @@ cython_debug/ 实验六/models/ # dataset -实验七/cache/ \ No newline at end of file +实验七/cache/ +实验七/models/ \ No newline at end of file diff --git a/实验七/2.pretrain.py b/实验七/2.pretrain.py new file mode 100644 index 0000000..406425b --- /dev/null +++ b/实验七/2.pretrain.py @@ -0,0 +1,39 @@ +import cv2 as cv +import os +import tqdm + +# 处理图像 +def pretrain(img_path,output_path): + img=cv.imread(img_path, cv.IMREAD_GRAYSCALE) + img = cv.resize(img, (50, 50)) + _,img=cv.threshold(img,127,255,cv.THRESH_BINARY) + img=cv.blur(img,(3,3)) + os.makedirs(os.path.dirname(output_path), exist_ok=True) + cv.imwrite(output_path, img) + +# 获取文件路径 +def get_img_name(directory, extensions=None): + if extensions is None: + extensions = ['.png', '.jpg','.jpeg'] + files = [] + for root, dirs, file_names in os.walk(directory): + for file_name in file_names: + if any(file_name.lower().endswith(ext) for ext in extensions): + files.append(os.path.join(root, file_name)) + return files + +# 处理非人脸数据集 +os.makedirs('cache/pretrained/non_face/', exist_ok=True) +non_face_files=get_img_name('cache/dataset/non_face/') +print('预处理非人脸数据中:') +for img_path in tqdm.tqdm(non_face_files): + pretrain(img_path, os.path.join('cache/pretrained/non_face', os.path.basename(img_path))) + +# 处理人脸数据集 +os.makedirs('cache/pretrained/face/', exist_ok=True) +face_files=get_img_name('cache/dataset/face/') +print('预处理人脸数据中:') +for img_path in tqdm.tqdm(face_files): + relative_path=os.path.relpath(img_path, 'cache/dataset/face/') + output_path=os.path.join('cache/pretrained/face', relative_path) + pretrain(img_path, output_path) diff --git a/实验七/3.train.py b/实验七/3.train.py new file mode 100644 index 0000000..c42acba --- /dev/null +++ b/实验七/3.train.py @@ -0,0 +1,88 @@ +import numpy as np +import cv2 as cv +import os +import tqdm +from sklearn.metrics import accuracy_score +from sklearn.model_selection import train_test_split +from sklearn.svm import SVC +from joblib import dump + + +# 加载图像 +def load_dataset(directory): + files = [] + for root, dirs, file_list in os.walk(directory): + for file in file_list: + files.append(os.path.join(root, file)) + return files + +# 提取特征 +## 提取轮廓特征 +def extract_contour_features(img): + contours, _ = cv.findContours(img, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE) + contour = contours[0] + area = cv.contourArea(contour) + perimeter = cv.arcLength(contour, True) + return [area, perimeter] + +## 提取形状特征 +def extract_shape_features(contour): + x, y, w, h = cv.boundingRect(contour) + aspect_ratio = float(w) / h + rect_area = w * h + shape_factor = cv.contourArea(contour) / rect_area + return [aspect_ratio, shape_factor] + +## 计算HU矩 +def extract_hu_moments(contour): + moments = cv.moments(contour) + hu_moments = cv.HuMoments(moments) + return hu_moments.flatten() + +## 特征向量构建 +def extract_features(img_path): + img = cv.imread(img_path, cv.IMREAD_GRAYSCALE) + if img is None: + raise FileNotFoundError(f"无法加载图像: {img_path}") + + _, img_bin = cv.threshold(img, 128, 255, cv.THRESH_BINARY) + contours, _ = cv.findContours(img_bin, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE) + if len(contours) == 0: + return [0] * 11 + contour = contours[0] + contour_features = extract_contour_features(img_bin) + shape_features = extract_shape_features(contour) + hu_moments = extract_hu_moments(contour) + feature_vector = contour_features + shape_features + hu_moments.tolist() + return feature_vector + [0] * (11 - len(feature_vector)) + +# 加载数据集和标签 +face_paths = load_dataset("cache/pretrained/face/") +face_train_data = [(path, True) for path in face_paths] +non_face_paths = load_dataset("cache/pretrained/non_face/") +non_face_train_data = [(path, False) for path in non_face_paths] + +train_test_data = face_train_data + non_face_train_data +train_data, test_data = train_test_split(train_test_data, test_size=0.3) + +# 提取特征和标签 +X_train = np.vstack([extract_features(train_path) for train_path, _ in tqdm.tqdm(train_data, desc="数据集特征提取中:")]) +X_test = np.vstack([extract_features(test_path) for test_path, _ in tqdm.tqdm(test_data, desc="测试集特征提取中:")]) + +Y_train = np.array([label for _, label in train_data]) +Y_test = np.array([label for _, label in test_data]) + + +# 训练分类器 +classifier = SVC(kernel='linear', max_iter=10000) +classifier.fit(X_train, Y_train) + +# 评估 +Y_pred = classifier.predict(X_test) + +accuracy = accuracy_score(Y_test, Y_pred) +print(f"准确率: {accuracy * 100:.2f}%") + +# 保存模型 +os.makedirs("models", exist_ok=True) +dump(classifier, "models/classifier.pkl") diff --git a/实验七/main.py b/实验七/main.py index eb389a0..97c77ad 100644 --- a/实验七/main.py +++ b/实验七/main.py @@ -1,16 +1,111 @@ -# 这是一个示例 Python 脚本。 +import cv2 as cv +import numpy as np +from joblib import load -# 按 Shift+F10 执行或将其替换为您的代码。 -# 按 双击 Shift 在所有地方搜索类、文件、工具窗口、操作和设置。 +classifier = load('models/classifier.pkl') + +# 预处理图像 +def pre_detect(img): + pre_img = img.copy() + pre_img = cv.resize(pre_img, (50, 50)) + pre_img = cv.cvtColor(pre_img, cv.COLOR_BGR2GRAY) + _, pre_img = cv.threshold(pre_img, 127, 255, cv.THRESH_BINARY) + pre_img = cv.blur(pre_img, (3, 3)) + return pre_img + +# 特征提取 +def pre_detect(img): + pre_img = img.copy() + pre_img = cv.resize(pre_img, (50, 50)) + pre_img = cv.cvtColor(pre_img, cv.COLOR_BGR2GRAY) + _, pre_img = cv.threshold(pre_img, 127, 255, cv.THRESH_BINARY) + pre_img = cv.blur(pre_img, (3, 3)) + return pre_img + +# 特征提取 +def extract_contour_features(img): + contours, _ = cv.findContours(img, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE) + if len(contours) == 0: + return [0, 0] # 如果没有轮廓,返回默认值 + contour = contours[0] + area = cv.contourArea(contour) + perimeter = cv.arcLength(contour, True) + return [area, perimeter] + +def extract_shape_features(contour): + x, y, w, h = cv.boundingRect(contour) + aspect_ratio = float(w) / h + rect_area = w * h + shape_factor = cv.contourArea(contour) / rect_area + return [aspect_ratio, shape_factor] + +def extract_hu_moments(contour): + moments = cv.moments(contour) + hu_moments = cv.HuMoments(moments) + return hu_moments.flatten() -def print_hi(name): - # 在下面的代码行中使用断点来调试脚本。 - print(f'Hi, {name}') # 按 Ctrl+F8 切换断点。 +def extract_features(img): + contours, _ = cv.findContours(img, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE) + if len(contours) == 0: + # 如果没有找到轮廓,返回 11 个零,保持特征数量一致 + return [0] * 11 + contour = contours[0] + + # 提取轮廓特征 + contour_features = extract_contour_features(img) + + # 提取形状特征 + shape_features = extract_shape_features(contour) + + # 提取 Hu 矩 + hu_moments = extract_hu_moments(contour) + + # 合并所有特征为一个特征向量,确保总共有 11 个特征 + feature_vector = contour_features + shape_features + hu_moments.tolist() + return feature_vector -# 按装订区域中的绿色按钮以运行脚本。 -if __name__ == '__main__': - print_hi('PyCharm') +# 读取图像并进行预处理 +img = cv.imread('test.jpg') +pre_img = pre_detect(img) -# 访问 https://www.jetbrains.com/help/pycharm/ 获取 PyCharm 帮助 +features = extract_features(pre_img) + +features = np.array(features).reshape(1, -1) + +predict_label = classifier.predict(features) + +cv.imshow('face? ' + str(predict_label[0]), img) +cv.waitKey(0) +cv.destroyAllWindows() + +# 读取视频并进行检测 +cap = cv.VideoCapture(1) + +while True: + ret, frame = cap.read() + if not ret: + break + + # 预处理 + pre_img = pre_detect(frame) + + # 提取特征 + features = extract_features(pre_img) + features=np.array(features).reshape(1, -1) + + predict_label = classifier.predict(features) + + # 在帧上显示预测结果 + label_text = 'Face' if predict_label[0] else 'Non-Face' + cv.putText(frame, label_text, (50, 50), cv.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2, cv.LINE_AA) + + # 展示 + cv.imshow('video', frame) + + if cv.waitKey(1) & 0xFF == ord('q'): + break + +cap.release() +cv.destroyAllWindows() diff --git a/实验七/test.jpg b/实验七/test.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a89466b7cb2dd4a962c2115efb0443437bf86c73 GIT binary patch literal 14073 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