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 相关文章推荐
python代码 if not x: 和 if x is not None: 和 if not x is None:使用介绍
Sep 21 Python
Python实现并行抓取整站40万条房价数据(可更换抓取城市)
Dec 14 Python
一个基于flask的web应用诞生 组织结构调整(7)
Apr 11 Python
python re正则匹配网页中图片url地址的方法
Dec 20 Python
python mac下安装虚拟环境的图文教程
Apr 12 Python
pyqt5与matplotlib的完美结合实例
Jun 21 Python
Python如何爬取微信公众号文章和评论(基于 Fiddler 抓包分析)
Jun 28 Python
pytorch使用指定GPU训练的实例
Aug 19 Python
django和vue实现数据交互的方法
Aug 21 Python
Pycharm激活码激活两种快速方式(附最新激活码和插件)
Mar 12 Python
Python3操作读写CSV文件使用包过程解析
Apr 10 Python
matplotlib基础绘图命令之imshow的使用
Aug 13 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 header函数使用教程
2013/09/05 PHP
php Session存储到Redis的方法
2013/11/04 PHP
PHP动态输出JavaScript代码实例
2015/02/12 PHP
PHP设计模式(三)建造者模式Builder实例详解【创建型】
2020/05/02 PHP
javascript利用控件对windows的操作实现原理与应用
2012/12/23 Javascript
jquery队列函数用法实例
2014/12/16 Javascript
举例讲解AngularJS中的模块
2015/06/17 Javascript
TypeScript 中接口详解
2015/06/19 Javascript
详解jQuery向动态生成的内容添加事件响应jQuery live()方法
2015/11/02 Javascript
js替换字符串中所有指定的字符(实现代码)
2016/08/17 Javascript
jquery实现一个全局计时器(商城可用)
2017/06/30 jQuery
Vue-cli Eslint在vscode里代码自动格式化的方法
2018/02/23 Javascript
layer.open关闭父窗口 以及调用父页面的方法
2018/08/17 Javascript
vue elementui form表单验证的实现
2018/11/11 Javascript
深入浅析Python中join 和 split详解(推荐)
2016/06/30 Python
利用Python抓取行政区划码的方法
2016/11/28 Python
MAC中PyCharm设置python3解释器
2017/12/15 Python
基于numpy.random.randn()与rand()的区别详解
2018/04/17 Python
对python中的xlsxwriter库简单分析
2018/05/04 Python
对Python生成汉字字库文字,以及转换为文字图片的实例详解
2019/01/29 Python
pymysql的简单封装代码实例
2020/01/08 Python
python实现坦克大战
2020/04/24 Python
python编写一个会算账的脚本的示例代码
2020/06/02 Python
python+requests实现接口测试的完整步骤
2020/10/27 Python
如何利用python生成MD5并去重
2020/12/07 Python
通用的Django注册功能模块实现方法
2021/02/05 Python
CSS3美化表单控件全集
2016/06/29 HTML / CSS
简述Linux文件系统通过i节点把文件的逻辑结构和物理结构转换的工作过程
2012/04/17 面试题
师范生自荐信范文
2013/10/06 职场文书
期末考试动员演讲稿
2014/01/10 职场文书
2014年五一促销活动方案
2014/03/09 职场文书
艺术节主持词
2014/04/02 职场文书
应届硕士毕业生自荐信
2014/05/26 职场文书
护士年终个人总结
2015/02/13 职场文书
Css预编语言及区别详解
2021/04/25 HTML / CSS
微软PC Health Check电脑健康状况检查应用下载(Win11配置检测工具)
2021/06/26 数码科技