基于opencv使用python实现人脸识别与视频生成
Published in:2024-03-25 |
Words: 2.1k | Reading time: 10min | reading:

基于opencv使用python实现人脸识别与视频生成

概述

OpenCV是一个基于Apache2.0许可(开源)发行的跨平台计算机视觉和机器学习软件库,可以运行在Linux、Windows、Android和Mac OS操作系统上。

OpenCV由一系列C函数和少量C++类构成,同时提供了Python、Ruby、MATLAB等语言的接口,实现了图像处理和计算机视觉方面的很多通用算法,主要倾向于实时视觉应用。

基础用法

  • **cv2.imread()**:读入图像。第一个参数为图像路径,第二个参数指定读入图像的颜色,例如cv2.IMREAD_COLOR表示读入彩色图像,cv2.IMREAD_GRAYSCALE表示读入灰度图像。
  • **cv2.imshow()**:显示图像。第一个参数是窗口的名字,第二个参数是要显示的图像。
  • **cv2.VideoCapture()**:获取摄像头视频或者读取视频文件。参数可以是设备的索引号,也可以是视频文件的路径。
  • **cv2.imwrite()**:保存图像。第一个参数是文件名,包括文件路径和文件扩展名,第二个参数是要保存的图像。
  • **cv2.cvtColor()**:转换图像颜色空间。例如将彩色图像转换为灰度图像。

实际案例

图像转视频

图像尺寸统一化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
@logger.catch
def image_fill_black(target_dir, image_path):
"""
循环处理输入图像:如果输入尺寸大于输出尺寸则:缩小;如果输入尺寸小于输出尺寸,则用黑边填充。尺寸相等 输出图像
:param target_dir:输出图像路径
:param image_path:输入图像路径
:return:是否转换成功
"""
# Step 1: import opencv lib
import cv2
try:
# Step 2: Define the target size
target_size = (output_video_width, output_video_height)
# 读取图片
img = cv2.imread(image_path)
# 检查图片尺寸
height, width = img.shape[:2]
border_width = 0
border_height = 0
grap_width = 0
grap_height = 0
while True:
if width < target_size[0] and height < target_size[1]:
if width < target_size[0]:
border_width = abs(target_size[0] - width) // 2
grap_width = output_video_width - (border_width * 2) - width
elif width == target_size[0]:
border_width = 0
grap_width = 0
if height < target_size[1]:
border_height = abs(target_size[1] - height) // 2
grap_height = output_video_height - (border_height * 2) - height
elif height == target_size[1]:
border_height = 0
grap_height = 0
border = border_width, border_height
img = cv2.copyMakeBorder(img, border[1], border[1] + grap_height, border[0], border[0] + grap_width,
cv2.BORDER_CONSTANT, value=[0, 0, 0])
out_height, out_width = img.shape[:2]
if out_height == output_video_height and out_width == output_video_width:
break
else:
continue
elif width > target_size[0] or height > target_size[1]:
img = cv2.resize(img, target_size, interpolation=cv2.INTER_LINEAR)
out_height, out_width = img.shape[:2]
if out_height == output_video_height and out_width == output_video_width:
break
else:
continue
else:
logger.info(f"Image size matches the target size: {image_path}")
break
file_path, file_name = os.path.split(image_path)
cv2.imwrite(os.path.join(target_dir, "result_" + file_name), img)
cv2.waitKey(0)
cv2.destroyAllWindows()
except AttributeError as uie:
logger.error("error, AttributeError: 'NoneType' object has no attribute 'shape'! detail: " + str(uie) +
", ""file_name or path: " + str(image_path))
return False
except Exception as e:
logger.error("error, unknown error! detail: " + str(e) + ", file_name or path: " + str(image_path))
return False
return True

上述代码讲不同尺寸图像转换为统一目标尺寸,过小用黑边填充,过大进行缩放。

图像转为视频

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66

@logger.catch
def generate_video_from_images(images_input_path, video_out_path):
"""
generate video from images list
:param images_input_path:
:param video_out_path:
:return:
"""
image_paths = []
for root, dirs, files in os.walk(images_input_path):
for file in files:
if "square" in file or "custom" in file or "error_images" in root or "small_images" in root \
or "gif_unzip" in root:
logger.warning(f"skip file, because images error or small, name: {file}")
continue
elif file.endswith('.jpg') or file.endswith('.png'): # 仅处理jpg和png图片文件
image_paths.append(os.path.join(root, file))
logger.debug(
"scan image coule use image length: " + str(len(image_paths)) + ", scan dir: " + str(constants.data_path))
if len(image_paths) <= 0:
return False
width = 0
height = 0

for image_path in image_paths:
try:
image = cv2.imread(image_path)
if width == 0:
width = image.shape[1]
height = image.shape[0]
except Exception as e:
logger.error("error! detail: " + "file name or path: " + image_path + ", error detail: " + str(e))
continue

# 检查输出路径是否存在,如果不存在则创建目录
if not os.path.exists(video_out_path):
os.makedirs(video_out_path)

fourcc = cv2.VideoWriter.fourcc(*'MJPG')
# 创建VideoWriter对象
video_name = video_out_path + id_generate_time() + "test.mp4"
video = cv2.VideoWriter(video_name, fourcc, int(output_video_fps), (width, height)) # 设置视频帧率、输出视频大小
if not video.isOpened():
logger.error("not open video watch!")
return False

try:
export_index = 0
image_size_len = len(image_paths)
for image_path in os.listdir(images_input_path):
export_index += 1
percent_cur = int((export_index / image_size_len) * 100)
image = cv2.imread(os.path.join(images_input_path, image_path))
if image is None: # 增加对图像是否正确读取的检查
logger.error("Image not loaded: " + image_path)
continue
resized_image = cv2.resize(image, (width, height)) # 将图像的宽度和高度设置为适合MPEG-4的尺寸
if image is not None:
video.write(resized_image)
if percent_cur % 100 == 0:
logger.info(f"export process to export_index / image_size_len: {export_index} / {image_size_len} * "
f"100%100: {percent_cur}%")
finally:
video.release()
return video_name

上述代码从list中读取图像,使用width = image.shape[1]获取图像宽度或高度;使用 fourcc = cv2.VideoWriter.fourcc(*'MJPG') # 创建VideoWriter对象 video_name = video_out_path + id_generate_time() + "test.mp4" video = cv2.VideoWriter(video_name, fourcc, int(output_video_fps), (width, height))创建videowriter对象,其中output_video_fps规定了视频输出帧率,(width, height)规定了视频输出宽高度;后边使用 image = cv2.imread(os.path.join(images_input_path, image_path))读取传入图片对象;使用resized_image = cv2.resize(image, (width, height))再次调整图片大小至合适尺寸;最后使用video.write(resized_image)写入图像到视频中。写入完成后,使用video.release()释放对象。

人脸特征检测

分类器下载与使用

  • 下载地址
    1
    https://github.com/opencv/opencv/tree/master/data/haarcascades。
  • 读取与使用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24

    @logger.catch
    def get_data_file(filename):
    """

    获取数据文件的路径,无论是直接运行还是通过 PyInstaller 打包
    :param filename
    :return
    """
    if getattr(sys, 'frozen', False):
    # 如果程序是“冷冻的”,即打包后的 exe
    basedir = sys._MEIPASS
    else:
    # 如果程序是直接运行的,即没有打包
    basedir = os.path.dirname(__file__)

    return os.path.join(basedir, filename)

    def main():
    face_xml_path = get_data_file("xml_data/haarcascade_frontalface_default.xml")
    eye_xml_path = get_data_file('xml_data/haarcascade_eye.xml')
    # 加载分类器
    face_cascade = cv2.CascadeClassifier(face_xml_path)
    eye_cascade = cv2.CascadeClassifier(eye_xml_path)

实际案例

  • 图像读取与人脸特征分割
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67

    @logger.catch
    def face_detect(path, image_path):
    """
    使用OpenCV进行人脸和眼睛检测。

    :param path:
    :param image_path: 图像文件的路径。
    :return: None
    """
    file_path, file_name = os.path.split(image_path)
    folder_name = find_img_result(image_path)

    if folder_name is None:
    folder_name = 'unknown_name'
    logger.warning('folder_name is None, default use unknown_name.')

    base_path = os.path.join(path, "face_detect_result")
    base_path = os.path.join(base_path, folder_name)
    img_file_path = os.path.join(base_path, "split_face")
    img_file_path_line = os.path.join(base_path, "red_line")
    img_file_name = os.path.join(img_file_path, file_name)
    img_file_name_line = os.path.join(img_file_path_line, file_name)

    if not os.path.exists(img_file_path):
    os.makedirs(img_file_path)
    logger.warning("face detect split_face dir not exists, create it.")
    if not os.path.exists(img_file_path_line):
    os.makedirs(img_file_path_line)
    logger.warning(f"face detect red_line dir not exists, create it: {img_file_path}.")

    # 只生成没有的
    if os.path.exists(img_file_name) or os.path.exists(img_file_name_line):
    logger.warning(f"{img_file_name} already exists, will skip!")
    return False
    try:
    face_xml_path = get_data_file("xml_data/haarcascade_frontalface_default.xml")
    eye_xml_path = get_data_file('xml_data/haarcascade_eye.xml')
    # 加载分类器
    face_cascade = cv2.CascadeClassifier(face_xml_path)
    eye_cascade = cv2.CascadeClassifier(eye_xml_path)

    # 读取图像
    img = cv2.imread(image_path)
    if img is None:
    logger.error(f"Error: Unable to load image at {image_path}")
    return False

    # 转换为灰度图像
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    # 检测人脸
    faces = face_cascade.detectMultiScale(gray, 1.1, 5)
    # eye_cascade.detectMultiScale(gray, 1.1, 5)
    if len(faces) == 0:
    # logger.warning(f"No faces detected:{image_path}")
    return False
    else:
    save_face(img_file_name, img_file_name_line, img, faces, gray, eye_cascade)
    # 显示结果
    cv2.waitKey(0)
    cv2.destroyAllWindows()

    except Exception as e:
    logger.error(f"An error occurred: {e}")
    return False

  • 识别结果保存
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61

    @logger.catch
    def save_face(img_file_name, img_file_name_line, img, faces, gray, eye_cascade):
    """

    :param eye_cascade:
    :param gray:
    :param img_file_name_line:
    :param img_file_name:
    :param img:
    :param faces:
    :return:
    """

    target_size = (200, 200)

    # Calculate the dimensions of the faces_image based on the number of faces and target size
    num_faces = len(faces)
    faces_image_height = num_faces * target_size[0]
    faces_image_width = target_size[1] # Assuming we want to fit only one face horizontally for simplicity

    try:
    # Create a blank image to hold the resized faces
    faces_image = np.zeros((faces_image_height, faces_image_width, 3), dtype=np.uint8)

    for (x, y, w, h) in faces:
    cv2.rectangle(img, (x, y), (x + w, y + h), (255, 0, 0), 2)
    roi_gray = gray[y:y + h, x:x + w]
    roi_color = img[y:y + h, x:x + w]

    # 在人脸区域内检测眼睛
    eyes = eye_cascade.detectMultiScale(roi_gray)
    for (ex, ey, ew, eh) in eyes:
    cv2.rectangle(roi_color, (ex, ey), (ex + ew, ey + eh), (0, 255, 0), 2)

    # 将绘制了眼睛矩形框的人脸区域放回原图的正确位置
    img[y:y + h, x:x + w] = roi_color

    # Iterate over the detected faces
    for i, (x, y, w, h) in enumerate(faces):
    # Resize the face to the target size
    face_resized = cv2.resize(img[y:y + h, x:x + w], target_size)

    row = i
    col = 0

    # Place the resized face in the faces_image array
    faces_image[row * target_size[0]:(row + 1) * target_size[0],
    col * target_size[1]:(col + 1) * target_size[1]] = face_resized

    cv2.waitKey(0)
    cv2.destroyAllWindows()

    cv2.imwrite(img_file_name, faces_image)
    cv2.imwrite(img_file_name_line, img)
    except Exception as e:
    logger.error(f"unknown error, detail: {e}, name: {img_file_name[-27:]}")
    return False
    logger.success(f"save success, name: {img_file_name[-27:]}")
    return True

    上述代码,在人脸中检测了眼睛、脸部特征点,并使用对应颜色框框选出来,最终保存至目标路径。

Prev:
使用nuitka打包python为exe并发布
Next:
python_proxy_server