Kears+Opencv实现简单人脸识别


Posted in Python onAugust 28, 2019

写在前面:这篇文章也是借鉴了一些前辈的代码和思路写的,代码有些也是复用了别人的。

先说下思路:

1.首先利用Opencv检测出人脸的区域 

2.在成功的检测出人脸区域后,将识别出的人脸区域截取成图片,储存起来,用作后续的训练数据。

3.在获取到了足够多的数据后,搭建CNN网络进行训练。

5.训练完成后,将模型存储起来。 6.在利用Opencv实时读取视频流,将检测出人脸的区域变成图片放入模型中进行预测。

以上就是这个项目的一个基本的思路了。

1.利用Opencv检测人脸的代码如下,这个代码在Opencv的官方文档中也有,最重要的是加载的xml文件,因为这个xml文件中是保存这已经训练好的人脸检测模型。

import cv2
 
def identify_face(window_name, camera_idx):
  cv2.namedWindow(window_name)
 
  # 视频来源,可以来自一段已存好的视频,也可以直接来自USB摄像头
  cap = cv2.VideoCapture(camera_idx)
 
  # 告诉OpenCV使用人脸识别分类器
  classfier = cv2.CascadeClassifier("haarcascade_frontalface_alt2.xml")
 
  # 识别出人脸后要画的边框的颜色,RGB格式
  color = (0, 255, 0)
 
  while cap.isOpened(): # 是否初始化摄像头设备
    ok, frame = cap.read() # 读取一帧数据
    if not ok:
      break
 
    # 将当前帧转换成灰度图像
    grey = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
 
 
    # 人脸检测,1.2和4分别为图片缩放比例和需要检测的有效点数
    faceRects = classfier.detectMultiScale(grey, scaleFactor=1.2, minNeighbors=4, minSize=(32, 32))
    if len(faceRects) > 0: # 大于0则检测到人脸
      for faceRect in faceRects: # 单独框出每一张人脸
        x, y, w, h = faceRect  # 获得人脸的左上角对应坐标,及宽度和高度
        cv2.rectangle(frame, (x - 10, y - 10), (x + w + 10, y + h + 10), color, 2)
 
    # 显示图像
    cv2.imshow(window_name, frame)
    c = cv2.waitKey(10)
    if c & 0xFF == ord('q'):
      break
 
  # 释放摄像头并销毁所有窗口
  cap.release()
  cv2.destroyAllWindows()
 
 
if __name__ == '__main__':
  identify_face("identify face", 0)

2.在检测出人脸区域后,将人脸区域截取成图片保存下来

import cv2
from threading import Thread
 
 
 
def identify_face_and_store_face_image(window_name, camera_idx):
  cv2.namedWindow(window_name)
 
  # 视频来源,可以来自一段已存好的视频,也可以直接来自USB摄像头
  cap = cv2.VideoCapture(camera_idx)
 
  # 告诉OpenCV使用人脸识别分类器
  classfier = cv2.CascadeClassifier("haarcascade_frontalface_alt2.xml")
 
  # 识别出人脸后要画的边框的颜色,RGB格式
  color = (0, 255, 0)
 
 
  # 保存图片的索引
  num = 0
  while cap.isOpened(): # 是否初始化摄像头设备
    ok, frame = cap.read() # 读取一帧数据
    if not ok:
      break
 
    # 将当前帧转换成灰度图像
    grey = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
 
 
    # 人脸检测,1.2和2分别为图片缩放比例和需要检测的有效点数
    faceRects = classfier.detectMultiScale(grey, scaleFactor=1.2, minNeighbors=4, minSize=(32, 32))
    if len(faceRects) > 0: # 大于0则检测到人脸
      for faceRect in faceRects: # 单独框出每一张人脸
        x, y, w, h = faceRect  # 获得人脸的左上角对应坐标,及宽度和高度
 
        cv2.rectangle(frame, (x - 10, y - 10), (x + w + 10, y + h + 10), color, 2)
 
        # store_face_image(frame, h, num, w, x, y)
        # 开启一个线程去存储人脸图片
        t = Thread(target=store_face_image, args=(frame, h, num, w, x, y, ))
        t.start()
 
 
        # 显示捕捉了多少张人脸照片
        font = cv2.FONT_HERSHEY_SIMPLEX # 字体
        cv2.putText(frame, ('num %d' % num), (x + 30, y + 30), font, 1, (255, 0, 255), 2)
 
        num += 1
        if num <= 1000: # 保存1000张图片后就退出
          break
 
 
    if num >= 1000:
      break
 
    # 显示图像
    cv2.imshow(window_name, frame)
    c = cv2.waitKey(10)
    if c & 0xFF == ord('q'):
      break
 
  # 释放摄像头并销毁所有窗口
  cap.release()
  cv2.destroyAllWindows()
 
 
def store_face_image(frame, h, num, w, x, y):
  # 将当前帧保存为图片
  img_name = '%s/%d.jpg' % (r'face_image', num)
  image = frame[y - 10: y + h + 10, x - 10: x + w + 10]
  cv2.imwrite(img_name, image)
 
if __name__ == '__main__':
  identify_face_and_store_face_image("identify face", 0)

3.将保存的数据进行处理,比如打上标签,进行归一化等等,下列代码是load_datasets.py文件中的

import os
import sys
import numpy as np
import cv2
 
IMAGE_SIZE = 64
 
 
# 按照指定图像大小调整尺寸
def resize_image(image, height=IMAGE_SIZE, width=IMAGE_SIZE):
  top, bottom, left, right = (0, 0, 0, 0)
 
  # 获取图像尺寸
  h, w, _ = image.shape
 
  # 对于长宽不相等的图片,找到最长的一边
  longest_edge = max(h, w)
 
  # 计算短边需要增加多上像素宽度使其与长边等长
  if h < longest_edge:
    dh = longest_edge - h
    top = dh // 2
    bottom = dh - top
  elif w < longest_edge:
    dw = longest_edge - w
    left = dw // 2
    right = dw - left
  else:
    pass
 
  # RGB颜色
  BLACK = [0, 0, 0]
 
  # 给图像增加边界,是图片长、宽等长,cv2.BORDER_CONSTANT指定边界颜色由value指定
  constant = cv2.copyMakeBorder(image, top, bottom, left, right, cv2.BORDER_CONSTANT, value=BLACK)
 
  # 调整图像大小并返回
  return cv2.resize(constant, (height, width))
 
 
# 读取训练数据
images = []
labels = []
 
 
def read_path(path_name):
  for dir_item in os.listdir(path_name):
    # 从初始路径开始叠加,合并成可识别的操作路径
    full_path = os.path.abspath(os.path.join(path_name, dir_item))
 
    if os.path.isdir(full_path): # 如果是文件夹,继续递归调用
      read_path(full_path)
    else: # 文件
      if dir_item.endswith('.jpg'):
        image = cv2.imread(full_path)
        image = resize_image(image, IMAGE_SIZE, IMAGE_SIZE)
 
        # 放开这个代码,可以看到resize_image()函数的实际调用效果
        # cv2.imwrite('1.jpg', image)
 
        images.append(image)
        labels.append(path_name)
 
  return images, labels
 
 
# 从指定路径读取训练数据
def load_dataset(path_name):
  images, labels = read_path(path_name)
 
  # 将输入的所有图片转成四维数组,尺寸为(图片数量*IMAGE_SIZE*IMAGE_SIZE*3)
  # 共587张图片,IMAGE_SIZE为64,故对我来说尺寸为587 * 64 * 64 * 3
  # 图片为64 * 64像素,一个像素3个颜色值(RGB)
  images = np.array(images)
  print(images.shape)
 
  # 标注数据,'my_face_image'文件夹下都是我的脸部图像,全部指定为0,你可以在获取他人的脸部图片比如说同学的,指定为1,
  labels = np.array([0 if label.endswith('my_face_image') else 1 for label in labels])
 
  return images, labels
 
 
if __name__ == '__main__':
  if len(sys.argv) != 1:
    print("Usage:%s path_name\r\n" % (sys.argv[0]))
  else:
    images, labels = load_dataset("face_image")

4.搭建模型,进行训练

import random
 
import numpy as np
from sklearn.model_selection import train_test_split
from keras.preprocessing.image import ImageDataGenerator
from keras.models import Sequential
from keras.layers import Dense, Dropout, Activation, Flatten
from keras.layers import Convolution2D, MaxPooling2D
from keras.optimizers import SGD
from keras.utils import np_utils
from keras.models import load_model
from keras import backend as K
 
from load_datasets import load_dataset, resize_image, IMAGE_SIZE
 
 
class Dataset:
  def __init__(self, path_name):
    # 训练集
    self.train_images = None
    self.train_labels = None
 
    # 验证集
    self.valid_images = None
    self.valid_labels = None
 
    # 测试集
    self.test_images = None
    self.test_labels = None
 
    # 数据集加载路径
    self.path_name = path_name
 
    # 当前库采用的维度顺序
    self.input_shape = None
 
  # 加载数据集并按照交叉验证的原则划分数据集并进行相关预处理工作
  def load(self, img_rows=IMAGE_SIZE, img_cols=IMAGE_SIZE,
       img_channels=3, nb_classes=2):
    # 加载数据集到内存
    images, labels = load_dataset(self.path_name)
 
    train_images, valid_images, train_labels, valid_labels = train_test_split(images, labels, test_size=0.3,
                                         random_state=random.randint(0, 100))
    _, test_images, _, test_labels = train_test_split(images, labels, test_size=0.5,
                             random_state=random.randint(0, 100))
 
    # 当前的维度顺序如果为'th',则输入图片数据时的顺序为:channels,rows,cols,否则:rows,cols,channels
    # 这部分代码就是根据keras库要求的维度顺序重组训练数据集
    if K.image_dim_ordering() == 'th':
      train_images = train_images.reshape(train_images.shape[0], img_channels, img_rows, img_cols)
      valid_images = valid_images.reshape(valid_images.shape[0], img_channels, img_rows, img_cols)
      test_images = test_images.reshape(test_images.shape[0], img_channels, img_rows, img_cols)
      self.input_shape = (img_channels, img_rows, img_cols)
    else:
      train_images = train_images.reshape(train_images.shape[0], img_rows, img_cols, img_channels)
      valid_images = valid_images.reshape(valid_images.shape[0], img_rows, img_cols, img_channels)
      test_images = test_images.reshape(test_images.shape[0], img_rows, img_cols, img_channels)
      self.input_shape = (img_rows, img_cols, img_channels)
 
      # 输出训练集、验证集、测试集的数量
      print(train_images.shape[0], 'train samples')
      print(valid_images.shape[0], 'valid samples')
      print(test_images.shape[0], 'test samples')
 
      # 我们的模型使用categorical_crossentropy作为损失函数,因此需要根据类别数量nb_classes将
      # 类别标签进行one-hot编码使其向量化,在这里我们的类别只有两种,经过转化后标签数据变为二维
      train_labels = np_utils.to_categorical(train_labels, nb_classes)
      valid_labels = np_utils.to_categorical(valid_labels, nb_classes)
      test_labels = np_utils.to_categorical(test_labels, nb_classes)
 
      # 像素数据浮点化以便归一化
      train_images = train_images.astype('float32')
      valid_images = valid_images.astype('float32')
      test_images = test_images.astype('float32')
 
      # 将其归一化,图像的各像素值归一化到0~1区间
      train_images /= 255
      valid_images /= 255
      test_images /= 255
 
      self.train_images = train_images
      self.valid_images = valid_images
      self.test_images = test_images
      self.train_labels = train_labels
      self.valid_labels = valid_labels
      self.test_labels = test_labels
 
 
# CNN网络模型类
class Model:
  def __init__(self):
    self.model = None
 
    # 建立模型
 
  def build_model(self, dataset, nb_classes=2):
    # 构建一个空的网络模型,它是一个线性堆叠模型,各神经网络层会被顺序添加,专业名称为序贯模型或线性堆叠模型
    self.model = Sequential()
 
    # 以下代码将顺序添加CNN网络需要的各层,一个add就是一个网络层
    self.model.add(Convolution2D(32, 3, 3, border_mode='same',
                   input_shape=dataset.input_shape)) # 1 2维卷积层
    self.model.add(Activation('relu')) # 2 激活函数层
 
    self.model.add(Convolution2D(32, 3, 3)) # 3 2维卷积层
    self.model.add(Activation('relu')) # 4 激活函数层
 
    self.model.add(MaxPooling2D(pool_size=(2, 2))) # 5 池化层
    self.model.add(Dropout(0.25)) # 6 Dropout层
 
    self.model.add(Convolution2D(64, 3, 3, border_mode='same')) # 7 2维卷积层
    self.model.add(Activation('relu')) # 8 激活函数层
 
    self.model.add(Convolution2D(64, 3, 3)) # 9 2维卷积层
    self.model.add(Activation('relu')) # 10 激活函数层
 
    self.model.add(MaxPooling2D(pool_size=(2, 2))) # 11 池化层
    self.model.add(Dropout(0.25)) # 12 Dropout层
 
    self.model.add(Flatten()) # 13 Flatten层
    self.model.add(Dense(512)) # 14 Dense层,又被称作全连接层
    self.model.add(Activation('relu')) # 15 激活函数层
    self.model.add(Dropout(0.5)) # 16 Dropout层
    self.model.add(Dense(nb_classes)) # 17 Dense层
    self.model.add(Activation('softmax')) # 18 分类层,输出最终结果
 
    # 输出模型概况
    self.model.summary()
 
  # 训练模型
  def train(self, dataset, batch_size=20, nb_epoch=10, data_augmentation=True):
    sgd = SGD(lr=0.01, decay=1e-6,
         momentum=0.9, nesterov=True) # 采用SGD+momentum的优化器进行训练,首先生成一个优化器对象
    self.model.compile(loss='categorical_crossentropy',
              optimizer=sgd,
              metrics=['accuracy']) # 完成实际的模型配置工作
 
    # 不使用数据提升,所谓的提升就是从我们提供的训练数据中利用旋转、翻转、加噪声等方法创造新的
    # 训练数据,有意识的提升训练数据规模,增加模型训练量
    if not data_augmentation:
      self.model.fit(dataset.train_images,
              dataset.train_labels,
              batch_size=batch_size,
              nb_epoch=nb_epoch,
              validation_data=(dataset.valid_images, dataset.valid_labels),
              shuffle=True)
    # 使用实时数据提升
    else:
      # 定义数据生成器用于数据提升,其返回一个生成器对象datagen,datagen每被调用一
      # 次其生成一组数据(顺序生成),节省内存,其实就是python的数据生成器
      datagen = ImageDataGenerator(
        featurewise_center=False, # 是否使输入数据去中心化(均值为0),
        samplewise_center=False, # 是否使输入数据的每个样本均值为0
        featurewise_std_normalization=False, # 是否数据标准化(输入数据除以数据集的标准差)
        samplewise_std_normalization=False, # 是否将每个样本数据除以自身的标准差
        zca_whitening=False, # 是否对输入数据施以ZCA白化
        rotation_range=20, # 数据提升时图片随机转动的角度(范围为0~180)
        width_shift_range=0.2, # 数据提升时图片水平偏移的幅度(单位为图片宽度的占比,0~1之间的浮点数)
        height_shift_range=0.2, # 同上,只不过这里是垂直
        horizontal_flip=True, # 是否进行随机水平翻转
        vertical_flip=False) # 是否进行随机垂直翻转
 
      # 计算整个训练样本集的数量以用于特征值归一化、ZCA白化等处理
      datagen.fit(dataset.train_images)
 
      # 利用生成器开始训练模型
      self.model.fit_generator(datagen.flow(dataset.train_images, dataset.train_labels,
                         batch_size=batch_size),
                   samples_per_epoch=dataset.train_images.shape[0],
                   nb_epoch=nb_epoch,
                   validation_data=(dataset.valid_images, dataset.valid_labels))
 
  MODEL_PATH = 'zhuhaipeng.face.model.h5'
 
  def save_model(self, file_path=MODEL_PATH):
    self.model.save(file_path)
 
  def load_model(self, file_path=MODEL_PATH):
    self.model = load_model(file_path)
 
  def evaluate(self, dataset):
    score = self.model.evaluate(dataset.test_images, dataset.test_labels, verbose=1)
    print("%s: %.2f%%" % (self.model.metrics_names[1], score[1] * 100))
 
  # 识别人脸
  def face_predict(self, image):
    # 依然是根据后端系统确定维度顺序
    if K.image_dim_ordering() == 'th' and image.shape != (1, 3, IMAGE_SIZE, IMAGE_SIZE):
      image = resize_image(image) # 尺寸必须与训练集一致都应该是IMAGE_SIZE x IMAGE_SIZE
      image = image.reshape((1, 3, IMAGE_SIZE, IMAGE_SIZE)) # 与模型训练不同,这次只是针对1张图片进行预测
    elif K.image_dim_ordering() == 'tf' and image.shape != (1, IMAGE_SIZE, IMAGE_SIZE, 3):
      image = resize_image(image)
      image = image.reshape((1, IMAGE_SIZE, IMAGE_SIZE, 3))
 
    # 浮点并归一化
    image = image.astype('float32')
    image /= 255
 
    # 给出输入属于各个类别的概率,我们是二值类别,则该函数会给出输入图像属于0和1的概率各为多少
    result = self.model.predict_proba(image)
    print('result:', result)
 
    # 给出类别预测:0或者1
    result = self.model.predict_classes(image)
 
    # 返回类别预测结果
    return result[0]
 
 
if __name__ == '__main__': # 训练模型
  dataset = Dataset('face_image')
  dataset.load()
 
  model = Model()
  model.build_model(dataset)
 
  # 测试训练函数的代码
  model.train(dataset)
 
if __name__ == '__main__': # 训练并保存模型
  dataset = Dataset('face_image')
  dataset.load()
 
  model = Model()
  model.build_model(dataset)
  model.train(dataset)
  model.save_model(file_path='model/zhuhaipeng.face.model.h5')
 
if __name__ == '__main__':  # 读取模型进行评估
  dataset = Dataset('face_image')
  dataset.load()
 
  # 评估模型
  model = Model()
  model.load_model(file_path='model/zhuhaipeng.face.model.h5')
  model.evaluate(dataset)

5.训练好模型后,使用Opencv实时读取视频流检测人脸位置,再将人脸放入模型中进行预测

# -*- coding: utf-8 -*-
 
import cv2
import sys
from face_train import Model
 
if __name__ == '__main__':
  if len(sys.argv) != 1:
    print("Usage:%s camera_id\r\n" % (sys.argv[0]))
    sys.exit(0)
 
  # 加载模型
  model = Model()
  model.load_model(file_path='model/zhuhaipeng.face.model.h5')
 
  # 框住人脸的矩形边框颜色
  color = (0, 255, 0)
 
  # 捕获指定摄像头的实时视频流
  cap = cv2.VideoCapture(0)
 
  # 人脸识别分类器本地存储路径
  cascade_path = "haarcascade_frontalface_alt2.xml"
 
  # 循环检测识别人脸
  while True:
    ret, frame = cap.read() # 读取一帧视频
 
    if ret is True:
 
      # 图像灰化,降低计算复杂度
      frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    else:
      continue
    # 使用人脸识别分类器,读入分类器
    cascade = cv2.CascadeClassifier(cascade_path)
 
    # 利用分类器识别出哪个区域为人脸
    faceRects = cascade.detectMultiScale(frame_gray, scaleFactor=1.2, minNeighbors=3, minSize=(32, 32))
    if len(faceRects) > 0:
      for faceRect in faceRects:
        x, y, w, h = faceRect
 
        # 截取脸部图像提交给模型识别这是谁
        image = frame[y - 10: y + h + 10, x - 10: x + w + 10]
        faceID = model.face_predict(image)
 
        # 如果是“我”
        if faceID == 0:
          cv2.rectangle(frame, (x - 10, y - 10), (x + w + 10, y + h + 10), color, thickness=2)
 
          # 文字提示是谁
          cv2.putText(frame, 'zhuhaipeng',
                (x + 30, y + 30), # 坐标
                cv2.FONT_HERSHEY_SIMPLEX, # 字体
                1, # 字号
                (255, 0, 255), # 颜色
                2) # 字的线宽
        else: # 如果不是我
          # 文字提示未知
          cv2.putText(frame, 'Unknown people ',
                (x + 30, y + 30), # 坐标
                cv2.FONT_HERSHEY_SIMPLEX, # 字体
                1, # 字号
                (255, 0, 255), # 颜色
                2) # 字的线宽
 
    cv2.imshow("identify me", frame)
 
    # 等待10毫秒看是否有按键输入
    k = cv2.waitKey(10)
    # 如果输入q则退出循环
    if k & 0xFF == ord('q'):
      break
 
  # 释放摄像头并销毁所有窗口
  cap.release()
  cv2.destroyAllWindows()

到此一个简单的人脸识别小项目就完成了,在这个项目中,只是简单的二分类,可以在此基础上进行扩展为多分类,如果识别的准确率低的话,可以尝试更改网络架构,或者对数据进行预处理等等,如果大家有兴趣改进的话,可以尝试一下。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
python3实现ftp服务功能(客户端)
Mar 24 Python
python生成圆形图片的方法
Mar 25 Python
python从子线程中获得返回值的方法
Jan 30 Python
python 并发编程 阻塞IO模型原理解析
Aug 20 Python
解决python明明pip安装成功却找不到包的问题
Aug 28 Python
使用python绘制温度变化雷达图
Oct 18 Python
python的scipy实现插值的示例代码
Nov 12 Python
使用python制作一个解压缩软件
Nov 13 Python
tensorflow查看ckpt各节点名称实例
Jan 21 Python
python中使用input()函数获取用户输入值方式
May 03 Python
python 监控logcat关键字功能
Sep 04 Python
pandas 按日期范围筛选数据的实现
Feb 20 Python
解决python 文本过滤和清理问题
Aug 28 #Python
解决Python对齐文本字符串问题
Aug 28 #Python
python+jinja2实现接口数据批量生成工具
Aug 28 #Python
解决Atom安装Hydrogen无法运行python3的问题
Aug 28 #Python
opencv实现简单人脸识别
Feb 19 #Python
Atom Python 配置Python3 解释器的方法
Aug 28 #Python
python聚类算法解决方案(rest接口/mpp数据库/json数据/下载图片及数据)
Aug 28 #Python
You might like
PHP类的静态(static)方法和静态(static)变量使用介绍
2012/02/19 PHP
探讨多键值cookie(php中cookie存取数组)的详解
2013/06/06 PHP
laravel-admin的多级联动方法
2019/09/30 PHP
6个常见的 PHP 安全性攻击实例和阻止方法
2020/12/16 PHP
JS trim去空格的最佳实践
2011/10/30 Javascript
jsp+javascript打造级连菜单的实例代码
2013/06/14 Javascript
sencha touch 模仿tabpanel导航栏TabBar的实例代码
2013/10/24 Javascript
JavaScript判断变量是否为空的自定义函数分享
2015/01/31 Javascript
JavaScript从数组中删除指定值元素的方法
2015/03/18 Javascript
ExtJs动态生成treepanel的Json格式
2015/07/19 Javascript
JavaScript实现图片轮播的方法
2015/07/31 Javascript
javascript常用经典算法详解
2017/01/11 Javascript
javascript笔记之匿名函数和闭包
2017/02/06 Javascript
JS倒计时实例_天时分秒
2017/08/22 Javascript
js获取文件里面的所有文件名(实例)
2017/10/17 Javascript
vue中倒计时组件的实例代码
2018/07/06 Javascript
浅谈vue 单文件探索
2018/09/05 Javascript
[10:14]2018DOTA2国际邀请赛寻真——paiN Gaming不仅为自己而战
2018/08/14 DOTA
[01:09:13]DOTA2-DPC中国联赛 正赛 CDEC vs XG BO3 第三场 1月19日
2021/03/11 DOTA
Python出现segfault错误解决方法
2016/04/16 Python
Python中查看文件名和文件路径
2017/03/31 Python
python Flask实现restful api service
2017/12/04 Python
Python学习之Django的管理界面代码示例
2018/02/10 Python
python实现滑雪游戏
2020/02/22 Python
Python 实现平台类游戏添加跳跃功能
2020/03/27 Python
PIL.Image.open和cv2.imread的比较与相互转换的方法
2020/06/03 Python
python matplotlib工具栏源码探析三之添加、删除自定义工具项的案例详解
2021/02/25 Python
SQL里面如何插入自动增长序列号字段
2012/03/29 面试题
Vector, ArrayList, HashTable, HashMap哪些是线程安全的,哪些不是
2015/10/12 面试题
应届生简历中的自我评价
2014/01/13 职场文书
培训主管的职业生涯规划
2014/03/06 职场文书
终止劳动合同协议书
2014/04/14 职场文书
小学生环保演讲稿
2014/04/25 职场文书
校园运动会广播稿
2014/10/06 职场文书
护理实习生带教计划
2015/01/16 职场文书
Python实现猜拳与猜数字游戏的方法详解
2022/04/06 Python