精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

使用Pytorch和OpenCV實現視頻人臉替換

人工智能
“DeepFaceLab”項目已經發布了很長時間了,作為研究的目的,本文將介紹他的原理,并使用Pytorch和OpenCV創建一個簡化版本。

“DeepFaceLab”項目已經發布了很長時間了,作為研究的目的,本文將介紹他的原理,并使用Pytorch和OpenCV創建一個簡化版本。

本文將分成3個部分,第一部分從兩個視頻中提取人臉并構建標準人臉數據集。第二部分使用數據集與神經網絡一起學習如何在潛在空間中表示人臉,并從該表示中重建人臉圖像。最后部分使用神經網絡在視頻的每一幀中創建與源視頻中相同但具有目標視頻中人物表情的人臉。然后將原人臉替換為假人臉,并將新幀保存為新的假視頻。

項目的基本結構(在第一次運行之前)如下所示

├── face_masking.py
├── main.py
├── face_extraction_tools.py
├── quick96.py
├── merge_frame_to_fake_video.py
├── data
│ ├── data_dst.mp4
│ ├── data_src.mp4

main.py是主腳本,data文件夾包含程序需要的的data_dst.mp4和data_src.mp4文件。

提取和對齊-構建數據集

在第一部分中,我們主要介紹face_extraction_tools.py文件中的代碼。

因為第一步是從視頻中提取幀,所以需要構建一個將幀保存為JPEG圖像的函數。這個函數接受一個視頻的路徑和另一個輸出文件夾的路徑。

 def extract_frames_from_video(video_path: Union[str, Path], output_folder: Union[str, Path], frames_to_skip: int=0) -> None:
    """
    Extract frame from video as a JPG images.
    Args:
        video_path (str | Path): the path to the input video from it the frame will be extracted
        output_folder (str | Path): the folder where the frames will be saved
        frames_to_skip (int): how many frames to skip after a frame which is saved. 0 will save all the frames.
            If, for example, this value is 2, the first frame will be saved, then frame 2 and 3 will be skipped,
            the 4th frame will be saved, and so on.
 
    Returns:
 
    """
 
    video_path = Path(video_path)
    output_folder = Path(output_folder)
 
    if not video_path.exists():
        raise ValueError(f'The path to the video file {video_path.absolute()} is not exist')
    if not output_folder.exists():
        output_folder.mkdir(parents=True)
 
    video_capture = cv2.VideoCapture(str(video_path))
 
    extract_frame_counter = 0
    saved_frame_counter = 0
    while True:
        ret, frame = video_capture.read()
        if not ret:
            break
 
        if extract_frame_counter % (frames_to_skip + 1) == 0:
            cv2.imwrite(str(output_folder / f'{saved_frame_counter:05d}.jpg'), frame, [cv2.IMWRITE_JPEG_QUALITY, 90])
            saved_frame_counter += 1
 
        extract_frame_counter += 1
 
    print(f'{saved_frame_counter} of {extract_frame_counter} frames saved')

函數首先檢查視頻文件是否存在,以及輸出文件夾是否存在,如果不存在則自動創建。然后使用OpenCV 的videoccapture類來創建一個對象來讀取視頻,然后逐幀保存為輸出文件夾中的JPEG文件。也可以根據frames_to_skip參數跳過幀。

然后就是需要構建人臉提取器。該工具應該能夠檢測圖像中的人臉,提取并對齊它。構建這樣一個工具的最佳方法是創建一個FaceExtractor類,其中包含檢測、提取和對齊的方法。

對于檢測部分,我們將使用帶有OpenCV的YuNet。YuNet是一個快速準確的基于cnn的人臉檢測器,可以由OpenCV中的FaceDetectorYN類使用。要創建這樣一個FaceDetectorYN對象,我們需要一個帶有權重的ONNX文件。該文件可以在OpenCV Zoo中找到,當前版本名為“face_detection_yunet_2023mar.onnx”。

我們的init()方法如下:

 def __init__(self, image_size):
        """
        Create a YuNet face detector to get face from image of size 'image_size'. The YuNet model
        will be downloaded from opencv zoo, if it's not already exist.
        Args:
            image_size (tuple): a tuple of (width: int, height: int) of the image to be analyzed
        """
        detection_model_path = Path('models/face_detection_yunet_2023mar.onnx')
        if not detection_model_path.exists():
            detection_model_path.parent.mkdir(parents=True, exist_ok=True)
            url = "https://github.com/opencv/opencv_zoo/blob/main/models/face_detection_yunet/face_detection_yunet_2023mar.onnx"
            print('Downloading face detection model...')
            filename, headers = urlretrieve(url, filename=str(detection_model_path))
            print('Download finish!')
 
        self.detector = cv2.FaceDetectorYN.create(str(detection_model_path), "", image_size)

函數首先檢查權重文件是否存在,如果不存在,則從web下載。然后使用權重文件和要分析的圖像大小創建FaceDetectorYN對象。檢測方法采用YuNet檢測方法在圖像中尋找人臉

def detect(self, image):
    ret, faces = self.detector.detect(image)
    return ret, faces

YuNet的輸出是一個大小為[num_faces, 15]的2D數組,包含以下信息:

  • 0-1:邊界框左上角的x, y
  • 2-3:邊框的寬度、高度
  • 4-5:右眼的x, y(樣圖中藍點)
  • 6-7:左眼x, y(樣圖中紅點)
  • 8-9:鼻尖x, y(示例圖中綠色點)
  • 10-11:嘴巴右角的x, y(樣例圖像中的粉色點)
  • 12-13:嘴角左角x, y(樣例圖中黃色點)
  • 14:面部評分

現在已經有了臉部位置數據,我們可以用它來獲得臉部的對齊圖像。這里主要利用眼睛位置的信息。我們希望眼睛在對齊后的圖像中處于相同的水平(相同的y坐標)。

@staticmethod
    def align(image, face, desired_face_width=256, left_eye_desired_coordinate=np.array((0.37, 0.37))):
        """
        Align the face so the eyes will be at the same level
        Args:
            image (np.ndarray): image with face
            face (np.ndarray): face coordinates from the detection step
            desired_face_width (int): the final width of the aligned face image
            left_eye_desired_coordinate (np.ndarray): a length 2 array of values between
              0 and 1 where the left eye should be in the aligned image
 
        Returns:
            (np.ndarray): aligned face image
        """
        desired_face_height = desired_face_width
        right_eye_desired_coordinate = np.array((1 - left_eye_desired_coordinate[0], left_eye_desired_coordinate[1]))
 
        # get coordinate of the center of the eyes in the image
        right_eye = face[4:6]
        left_eye = face[6:8]
 
        # compute the angle of the right eye relative to the left eye
        dist_eyes_x = right_eye[0] - left_eye[0]
        dist_eyes_y = right_eye[1] - left_eye[1]
        dist_between_eyes = np.sqrt(dist_eyes_x ** 2 + dist_eyes_y ** 2)
        angles_between_eyes = np.rad2deg(np.arctan2(dist_eyes_y, dist_eyes_x) - np.pi)
        eyes_center = (left_eye + right_eye) // 2
 
        desired_dist_between_eyes = desired_face_width * (
                    right_eye_desired_coordinate[0] - left_eye_desired_coordinate[0])
        scale = desired_dist_between_eyes / dist_between_eyes
 
        M = cv2.getRotationMatrix2D(eyes_center, angles_between_eyes, scale)
 
        M[0, 2] += 0.5 * desired_face_width - eyes_center[0]
        M[1, 2] += left_eye_desired_coordinate[1] * desired_face_height - eyes_center[1]
 
        face_aligned = cv2.warpAffine(image, M, (desired_face_width, desired_face_height), flags=cv2.INTER_CUBIC)
        return face_aligned

這個方法獲取單張人臉的圖像和信息,輸出圖像的寬度和期望的左眼相對位置。我們假設輸出圖像是平方的,并且右眼的期望位置具有相同的y位置和x位置的1 - left_eye_x。計算兩眼之間的距離和角度,以及兩眼之間的中心點。

最后一個方法是extract方法,它類似于align方法,但沒有轉換,它也返回圖像中人臉的邊界框。

def extract_and_align_face_from_image(input_dir: Union[str, Path], desired_face_width: int=256) -> None:
    """
    Extract the face from an image, align it and save to a directory inside in the input directory
    Args:
        input_dir (str|Path): path to the directory contains the images extracted from a video
        desired_face_width (int): the width of the aligned imaged in pixels
 
    Returns:
 
    """
 
    input_dir = Path(input_dir)
    output_dir = input_dir / 'aligned'
    if output_dir.exists():
        rmtree(output_dir)
    output_dir.mkdir()
 
 
    image = cv2.imread(str(input_dir / '00000.jpg'))
    image_height = image.shape[0]
    image_width = image.shape[1]
 
    detector = FaceExtractor((image_width, image_height))
 
    for image_path in tqdm(list(input_dir.glob('*.jpg'))):
        image = cv2.imread(str(image_path))
 
        ret, faces = detector.detect(image)
        if faces is None:
            continue
 
        face_aligned = detector.align(image, faces[0, :], desired_face_width)
        cv2.imwrite(str(output_dir / f'{image_path.name}'), face_aligned, [cv2.IMWRITE_JPEG_QUALITY, 90])

訓練

對于網絡,我們將使用AutoEncoder。在AutoEncoder中,有兩個主要組件——編碼器和解碼器。編碼器獲取原始圖像并找到它的潛在表示,解碼器利用潛在表示重構原始圖像。

對于我們的任務,要訓練一個編碼器來找到一個潛在的人臉表示和兩個解碼器——一個可以重建源人臉,另一個可以重建目標人臉。

在這三個組件被訓練之后,我們回到最初的目標:創建一個源面部但具有目標表情的圖像。也就是說使用解碼器A和人臉B的圖像。

面孔的潛在空間保留了面部的主要特征,如位置、方向和表情。解碼器獲取這些編碼信息并學習如何構建全臉圖像。由于解碼器A只知道如何構造A類型的臉,因此它從編碼器中獲取圖像B的特征并從中構造A類型的圖像。

在本文中,我們將使用來自原始DeepFaceLab項目的Quick96架構的一個小修改版本。

模型的全部細節可以在quick96.py文件中。

在我們訓練模型之前,還需要處理數據。為了使模型具有魯棒性并避免過擬合,我們還需要在原始人臉圖像上應用兩種類型的增強。第一個是一般的轉換,包括旋轉,縮放,在x和y方向上的平移,以及水平翻轉。對于每個轉換,我們為參數或概率定義一個范圍(例如,我們可以用來旋轉的角度范圍),然后從范圍中選擇一個隨機值來應用于圖像。

 random_transform_args = {
    'rotation_range': 10,
    'zoom_range': 0.05,
    'shift_range': 0.05,
    'random_flip': 0.5,
  }
 
 def random_transform(image, rotation_range, zoom_range, shift_range, random_flip):
    """
    Make a random transformation for an image, including rotation, zoom, shift and flip.
    Args:
        image (np.array): an image to be transformed
        rotation_range (float): the range of possible angles to rotate - [-rotation_range, rotation_range]
        zoom_range (float): range of possible scales - [1 - zoom_range, 1 + zoom_range]
        shift_range (float): the percent of translation for x and y
        random_flip (float): the probability of horizontal flip
 
    Returns:
        (np.array): transformed image
    """
    h, w = image.shape[0:2]
    rotation = np.random.uniform(-rotation_range, rotation_range)
    scale = np.random.uniform(1 - zoom_range, 1 + zoom_range)
    tx = np.random.uniform(-shift_range, shift_range) * w
    ty = np.random.uniform(-shift_range, shift_range) * h
    mat = cv2.getRotationMatrix2D((w // 2, h // 2), rotation, scale)
    mat[:, 2] += (tx, ty)
    result = cv2.warpAffine(image, mat, (w, h), borderMode=cv2.BORDER_REPLICATE)
    if np.random.random() < random_flip:
        result = result[:, ::-1]
    return result

第2個是通過使用帶噪聲的插值圖產生的失真。這種扭曲將迫使模型理解人臉的關鍵特征,并使其更加一般化。

def random_warp(image):
    """
    Create a distorted face image and a target undistorted image
    Args:
        image (np.array): image to warp
 
    Returns:
        (np.array): warped version of the image
        (np.array): target image to construct from the warped version
    """
    h, w = image.shape[:2]
 
    # build coordinate map to wrap the image according to
    range_ = np.linspace(h / 2 - h * 0.4, h / 2 + h * 0.4, 5)
    mapx = np.broadcast_to(range_, (5, 5))
    mapy = mapx.T
 
    # add noise to get a distortion of the face while warp the image
    mapx = mapx + np.random.normal(size=(5, 5), scale=5*h/256)
    mapy = mapy + np.random.normal(size=(5, 5), scale=5*h/256)
 
    # get interpolation map for the center of the face with size of (96, 96)
    interp_mapx = cv2.resize(mapx, (int(w / 2 * (1 + 0.25)) , int(h / 2 * (1 + 0.25))))[int(w/2 * 0.25/2):int(w / 2 * (1 + 0.25) - w/2 * 0.25/2), int(w/2 * 0.25/2):int(w / 2 * (1 + 0.25) - w/2 * 0.25/2)].astype('float32')
    interp_mapy = cv2.resize(mapy, (int(w / 2 * (1 + 0.25)) , int(h / 2 * (1 + 0.25))))[int(w/2 * 0.25/2):int(w / 2 * (1 + 0.25) - w/2 * 0.25/2), int(w/2 * 0.25/2):int(w / 2 * (1 + 0.25) - w/2 * 0.25/2)].astype('float32')
 
    # remap the face image according to the interpolation map to get warp version
    warped_image = cv2.remap(image, interp_mapx, interp_mapy, cv2.INTER_LINEAR)
 
    # create the target (undistorted) image
    # find a transformation to go from the source coordinates to the destination coordinate
    src_points = np.stack([mapx.ravel(), mapy.ravel()], axis=-1)
    dst_points = np.mgrid[0:w//2+1:w//8, 0:h//2+1:h//8].T.reshape(-1, 2)
 
    # We want to find a similarity matrix (scale rotation and translation) between the
    # source and destination points. The matrix should have the structure
    # [[a, -b, c],
    # [b, a, d]]
    # so we can construct unknown vector [a, b, c, d] and solve for it using least
    # squares with the source and destination x and y points.
    A = np.zeros((2 * src_points.shape[0], 2))
    A[0::2, :] = src_points # [x, y]
    A[0::2, 1] = -A[0::2, 1] # [x, -y]
    A[1::2, :] = src_points[:, ::-1] # [y, x]
    A = np.hstack((A, np.tile(np.eye(2), (src_points.shape[0], 1)))) # [x, -y, 1, 0] for x coordinate and [y, x, 0 ,1] for y coordinate
    b = dst_points.flatten() # arrange as [x0, y0, x1, y1, ..., xN, yN]
 
    similarity_mat = np.linalg.lstsq(A, b, rcond=None)[0] # get the similarity matrix elements as vector [a, b, c, d]
    # construct the similarity matrix from the result vector of the least squares
    similarity_mat = np.array([[similarity_mat[0], -similarity_mat[1], similarity_mat[2]],
                                [similarity_mat[1], similarity_mat[0], similarity_mat[3]]])
    # use the similarity matrix to construct the target image using affine transformation
    target_image = cv2.warpAffine(image, similarity_mat, (w // 2, h // 2))
 
    return warped_image, target_image

這個函數有兩個部分,我們首先在面部周圍的區域創建圖像的坐標圖。有一個x坐標的映射和一個y坐標的映射。mapx和mapy變量中的值是以像素為單位的坐標。然后在圖像上添加一些噪聲,使坐標在隨機方向上移動。我們添加的噪聲,得到了一個扭曲的坐標(像素在隨機方向上移動一點)。然后裁剪了插值后的貼圖,使其包含臉部的中心,大小為96x96像素。現在我們可以使用扭曲的映射來重新映射圖像,得到一個新的扭曲的圖像。

在第二部分創建未扭曲的圖像,這是模型應該從扭曲的圖像中創建的目標圖像。使用噪聲作為源坐標,并為目標圖像定義一組目標坐標。然后我們使用最小二乘法找到一個相似變換矩陣(尺度旋轉和平移),將其從源坐標映射到目標坐標,并將其應用于圖像以獲得目標圖像。

然后就可以創建一個Dataset類來處理數據了。FaceData類非常簡單。它獲取包含src和dst文件夾的文件夾的路徑,其中包含我們在前一部分中創建的數據,并返回大小為(2 * 96,2 * 96)歸一化為1的隨機源和目標圖像。我們的網絡將得到的是一個經過變換和扭曲的圖像,以及源臉和目標臉的目標圖像。所以還需要實現了一個collate_fn

 def collate_fn(self, batch):
        """
        Collate function to arrange the data returns from a batch. The batch returns a list
        of tuples contains pairs of source and destination images, which is the input of this
        function, and the function returns a tuple with 4 4D tensors of the warp and target
        images for the source and destination
        Args:
            batch (list): a list of tuples contains pairs of source and destination images
                as numpy array
 
        Returns:
            (torch.Tensor): a 4D tensor of the wrap version of the source images
            (torch.Tensor): a 4D tensor of the target source images
            (torch.Tensor): a 4D tensor of the wrap version of the destination images
            (torch.Tensor): a 4D tensor of the target destination images
        """
        images_src, images_dst = list(zip(*batch)) # convert list of tuples with pairs of images into tuples of source and destination images
        warp_image_src, target_image_src = get_training_data(images_src, len(images_src))
        warp_image_src = torch.tensor(warp_image_src, dtype=torch.float32).permute(0, 3, 1, 2).to(device)
        target_image_src = torch.tensor(target_image_src, dtype=torch.float32).permute(0, 3, 1, 2).to(device)
        warp_image_dst, target_image_dst = get_training_data(images_dst, len(images_dst))
        warp_image_dst = torch.tensor(warp_image_dst, dtype=torch.float32).permute(0, 3, 1, 2).to(device)
        target_image_dst = torch.tensor(target_image_dst, dtype=torch.float32).permute(0, 3, 1, 2).to(device)
 
        return warp_image_src, target_image_src, warp_image_dst, target_image_dst

當我們從Dataloader對象獲取數據時,它將返回一個元組,其中包含來自FaceData對象的源圖像和目標圖像對。collate_fn接受這個結果,并對圖像進行變換和失真,得到目標圖像,并為扭曲的源圖像、目標源圖像、扭曲的目標圖像和目標目標圖像返回四個4D張量。

訓練使用的損失函數是MSE (L2)損失和DSSIM的組合

訓練的指標和結果如上圖所示

生成視頻

在最后一步就是創建視頻。處理此任務的函數稱為merge_frame_to_fake_video.py。我們使用MediaPipe創建了facemask類。

當初始化facemask對象時,初始化MediaPipe人臉檢測器。

 class FaceMasking:
    def __init__(self):
        landmarks_model_path = Path('models/face_landmarker.task')
        if not landmarks_model_path.exists():
            landmarks_model_path.parent.mkdir(parents=True, exist_ok=True)
            url = "https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/latest/face_landmarker.task"
            print('Downloading face landmarks model...')
            filename, headers = urlretrieve(url, filename=str(landmarks_model_path))
            print('Download finish!')
 
        base_options = python_mp.BaseOptions(model_asset_path=str(landmarks_model_path))
        options = vision.FaceLandmarkerOptions(base_options=base_options,
                                                output_face_blendshapes=False,
                                                output_facial_transformation_matrixes=False,
                                                num_faces=1)
        self.detector = vision.FaceLandmarker.create_from_options(options)

這個類也有一個從人臉圖像中獲取掩碼的方法:

 def get_mask(self, image):
        """
        return uint8 mask of the face in image
        Args:
            image (np.ndarray): RGB image with single face
 
        Returns:
            (np.ndarray): single channel uint8 mask of the face
        """
        im_mp = mp.Image(image_format=mp.ImageFormat.SRGB, data=image.astype(np.uint8).copy())
        detection_result = self.detector.detect(im_mp)
 
        x = np.array([landmark.x * image.shape[1] for landmark in detection_result.face_landmarks[0]], dtype=np.float32)
        y = np.array([landmark.y * image.shape[0] for landmark in detection_result.face_landmarks[0]], dtype=np.float32)
 
        hull = np.round(np.squeeze(cv2.convexHull(np.column_stack((x, y))))).astype(np.int32)
        mask = np.zeros(image.shape[:2], dtype=np.uint8)
        mask = cv2.fillConvexPoly(mask, hull, 255)
        kernel = np.ones((7, 7), np.uint8)
        mask = cv2.erode(mask, kernel)
 
        return mask

該函數首先將輸入圖像轉換為MediaPipe圖像結構,然后使用人臉檢測器查找人臉。然后使用OpenCV找到點的凸包,并使用OpenCV的fillConvexPoly函數填充凸包的區域,從而得到一個二進制掩碼。最后,我們應用侵蝕操作來縮小遮蔽。

 def get_mask(self, image):
        """
        return uint8 mask of the face in image
        Args:
            image (np.ndarray): RGB image with single face
 
        Returns:
            (np.ndarray): single channel uint8 mask of the face
        """
        im_mp = mp.Image(image_format=mp.ImageFormat.SRGB, data=image.astype(np.uint8).copy())
        detection_result = self.detector.detect(im_mp)
 
        x = np.array([landmark.x * image.shape[1] for landmark in detection_result.face_landmarks[0]], dtype=np.float32)
        y = np.array([landmark.y * image.shape[0] for landmark in detection_result.face_landmarks[0]], dtype=np.float32)
 
        hull = np.round(np.squeeze(cv2.convexHull(np.column_stack((x, y))))).astype(np.int32)
        mask = np.zeros(image.shape[:2], dtype=np.uint8)
        mask = cv2.fillConvexPoly(mask, hull, 255)
        kernel = np.ones((7, 7), np.uint8)
        mask = cv2.erode(mask, kernel)
 
        return mask

merge_frame_to_fake_video函數就是將上面所有的步驟整合,創建一個新的視頻對象,一個FaceExtracot對象,一個facemask對象,創建神經網絡組件,并加載它們的權重。

def merge_frames_to_fake_video(dst_frames_path, model_name='Quick96', saved_models_dir='saved_model'):
    model_path = Path(saved_models_dir) / f'{model_name}.pth'
    dst_frames_path = Path(dst_frames_path)
    image = Image.open(next(dst_frames_path.glob('*.jpg')))
    image_size = image.size
    result_video = cv2.VideoWriter(str(dst_frames_path.parent / 'fake.mp4'), cv2.VideoWriter_fourcc(*'MJPG'), 30, image.size)
 
    face_extractor = FaceExtractor(image_size)
    face_masker = FaceMasking()
 
    encoder = Encoder().to(device)
    inter = Inter().to(device)
    decoder = Decoder().to(device)
 
    saved_model = torch.load(model_path)
    encoder.load_state_dict(saved_model['encoder'])
    inter.load_state_dict(saved_model['inter'])
    decoder.load_state_dict(saved_model['decoder_src'])
 
    model = torch.nn.Sequential(encoder, inter, decoder)

然后針對目標視頻中的所有幀,找到臉。如果沒有人臉就把畫面寫入視頻。如果有人臉,將其提取出來,轉換為網絡的適當輸入,并生成新的人臉。

對原人臉和新人臉進行遮蔽,利用遮蔽圖像上的矩量找到原人臉的中心。使用無縫克隆,以逼真的方式將新臉代替原來的臉(例如,改變假臉的膚色,以適應原來的臉皮膚)。最后將結果作為一個新的幀放回原始幀,并將其寫入視頻文件。

 frames_list = sorted(dst_frames_path.glob('*.jpg'))
    for ii, frame_path in enumerate(frames_list, 1):
        print(f'Working om {ii}/{len(frames_list)}')
        frame = cv2.imread(str(frame_path))
        retval, face = face_extractor.detect(frame)
        if face is None:
            result_video.write(frame)
            continue
        face_image, face = face_extractor.extract(frame, face[0])
        face_image = face_image[..., ::-1].copy()
        face_image_cropped = cv2.resize(face_image, (96, 96)) #face_image_resized[96//2:96+96//2, 96//2:96+96//2]
        face_image_cropped_torch = torch.tensor(face_image_cropped / 255., dtype=torch.float32).permute(2, 0, 1).unsqueeze(0).to(device)
        generated_face_torch = model(face_image_cropped_torch)
        generated_face = (generated_face_torch.squeeze().permute(1,2,0).detach().cpu().numpy() * 255).astype(np.uint8)
 
 
        mask_origin = face_masker.get_mask(face_image_cropped)
        mask_fake = face_masker.get_mask(generated_face)
 
        origin_moments = cv2.moments(mask_origin)
        cx = np.round(origin_moments['m10'] / origin_moments['m00']).astype(int)
        cy = np.round(origin_moments['m01'] / origin_moments['m00']).astype(int)
        try:
            output_face = cv2.seamlessClone(generated_face, face_image_cropped, mask_fake, (cx, cy), cv2.NORMAL_CLONE)
        except:
            print('Skip')
            continue
 
        fake_face_image = cv2.resize(output_face, (face_image.shape[1], face_image.shape[0]))
        fake_face_image = fake_face_image[..., ::-1] # change to BGR
        frame[face[1]:face[1]+face[3], face[0]:face[0]+face[2]] = fake_face_image
        result_video.write(frame)
 
    result_video.release()

一幀的結果是這樣的

模型并不完美,面部的某些角度,特別是側面視圖,會導致圖像不那么好,但總體效果不錯。

整合

為了運行整個過程,還需要創建一個主腳本。

 from pathlib import Path
 import face_extraction_tools as fet
 import quick96 as q96
 from merge_frame_to_fake_video import merge_frames_to_fake_video
 
 ##### user parameters #####
 # True for executing the step
 extract_and_align_src = True
 extract_and_align_dst = True
 train = True
 eval = False
 
 model_name = 'Quick96' # use this name to save and load the model
 new_model = False # True for creating a new model even if a model with the same name already exists
 
 ##### end of user parameters #####
 
 # the path for the videos to process
 data_root = Path('./data')
 src_video_path = data_root / 'data_src.mp4'
 dst_video_path = data_root / 'data_dst.mp4'
 
 # path to folders where the intermediate product will be saved
 src_processing_folder = data_root / 'src'
 dst_processing_folder = data_root / 'dst'
 
 # step 1: extract the frames from the videos
 if extract_and_align_src:
    fet.extract_frames_from_video(video_path=src_video_path, output_folder=src_processing_folder, frames_to_skip=0)
 if extract_and_align_dst:
    fet.extract_frames_from_video(video_path=dst_video_path, output_folder=dst_processing_folder, frames_to_skip=0)
 
 # step 2: extract and align face from frames
 if extract_and_align_src:
    fet.extract_and_align_face_from_image(input_dir=src_processing_folder, desired_face_width=256)
 if extract_and_align_dst:
    fet.extract_and_align_face_from_image(input_dir=dst_processing_folder, desired_face_width=256)
 
 # step 3: train the model
 if train:
    q96.train(str(data_root), model_name, new_model, saved_models_dir='saved_model')
 
 # step 4: create the fake video
 if eval:
    merge_frames_to_fake_video(dst_processing_folder, model_name, saved_models_dir='saved_model')

總結 在這篇文章中,我們介紹了DeepFaceLab的運行流程,并使用我們自己的方法實現了該過程。我們首先從視頻中提取幀,然后從幀中提取人臉并對齊它們以創建一個數據庫。使用神經網絡來學習如何在潛在空間中表示人臉以及如何重建人臉。遍歷了目標視頻的幀,找到了人臉并替換,這就是這個項目的完整流程。

本文只做學習研究,實際項目請參見:

https://github.com/iperov/DeepFaceLab

責任編輯:華軒 來源: DeepHub IMBA
相關推薦

2023-11-14 08:38:43

Golang人臉識別

2014-03-13 11:25:43

C++OpenCV

2022-04-05 20:54:21

OpenCVPython人臉檢測

2022-05-27 09:00:00

機器學習面部結構CGI

2023-07-03 07:40:13

VueGolangweb

2018-06-29 10:15:20

PythonOpenCV人臉識別

2017-08-02 07:36:06

大數據PythonOpenCV

2025-02-17 12:00:00

PythonOpenCV提取圖像

2024-11-01 10:34:45

2018-05-08 14:25:22

Pythondlib人臉檢測

2024-07-18 00:00:25

PyTorch神經網絡

2024-01-10 16:01:28

2024-01-07 20:20:46

2024-03-14 08:24:25

MediaCodec解碼播放Android

2017-09-22 11:45:10

深度學習OpenCVPython

2013-07-01 10:53:05

2021-08-13 15:07:02

模型人工智能深度學習

2021-05-31 08:40:58

opencv人臉識別Python

2021-11-08 22:59:04

機器學習

2021-06-01 09:27:52

視頻動畫Remotion
點贊
收藏

51CTO技術棧公眾號

美女搡bbb又爽又猛又黄www| 婷婷久久伊人| 日韩精品视频免费播放| 女厕嘘嘘一区二区在线播放| 欧美熟乱第一页| 国产精品va在线观看无码| 亚洲人成色777777老人头| 日本中文字幕一区二区有限公司| 免费av一区二区| 久久精品国产亚洲av麻豆| 国产激情一区| 在线看国产日韩| 一本久道高清无码视频| 自拍视频在线| 91免费小视频| 不卡视频一区二区三区| 自拍偷拍第八页| 一本色道久久| 欧美精品手机在线| 人成免费在线视频| 亚洲精品中文字幕99999| 欧美老肥妇做.爰bbww| 久久精品.com| yellow字幕网在线| 樱桃国产成人精品视频| 亚洲最新在线| 国产黄在线看| 26uuu国产一区二区三区| 99re视频在线播放| 99久久夜色精品国产亚洲| 日韩综合小视频| 午夜精品免费视频| 精品亚洲永久免费| 综合激情一区| 欧美成人免费在线观看| 免费看一级黄色| 波多野结衣在线观看一区二区三区 | 中文字幕69页| 日韩一级精品| 久久久久久久一区二区| 欧美成人片在线观看| 欧美r级电影| 中文字幕日韩电影| 调教驯服丰满美艳麻麻在线视频| 牛牛视频精品一区二区不卡| 亚洲白虎美女被爆操| wwwww在线观看| 亚洲精品观看| 欧美精品一区二区三区很污很色的 | 亚洲视频三区| 日韩欧美精品在线视频| 熟妇无码乱子成人精品| 国产精一区二区| 日韩欧美国产午夜精品| 精品人妻一区二区三| 国产精选久久| 精品福利av导航| 久久久国产精品无码| 日韩激情网站| 亚洲欧美www| 99精品全国免费观看| 清纯唯美日韩| 久久黄色av网站| 唐朝av高清盛宴| 亚洲婷婷在线| 91国产精品91| av毛片在线免费观看| 人人爽香蕉精品| 国产精品尤物福利片在线观看| 一个人看的www日本高清视频| 久久精品国产一区二区| 91青草视频久久| 亚洲国产一二三区| 久久婷婷一区二区三区| 亚洲欧洲日本国产| 免费电影网站在线视频观看福利| 亚洲国产精品一区二区久久恐怖片| 亚洲人成无码网站久久99热国产| 成人国产二区| 欧美日韩亚洲不卡| 精品国产乱码久久久久夜深人妻| 久久综合五月婷婷| 中文字幕日韩电影| 久久久久久福利| 久久久www| 亚洲a级在线播放观看| 天天操天天干天天| 国产精品天美传媒| 国产又粗又猛又爽又黄的网站 | 亚洲va欧美va天堂v国产综合| 成人综合视频在线| 91久久精品国产| 久草视频手机在线| 亚洲国产一区二区三区高清| 欧美野外猛男的大粗鳮| 一本一道精品欧美中文字幕| 国产99久久精品| 天堂资源在线亚洲视频| 欧美24videosex性欧美| 91电影在线观看| 精品人妻在线视频| 99九九热只有国产精品| 91成人在线播放| 国产三级漂亮女教师| www久久精品| 91亚洲精品国产| 高清在线一区| 日韩精品黄色网| 国产盗摄一区二区三区在线| 久久久久.com| 国产精品一区二区三区精品| 日韩大片在线永久免费观看网站| 国产日韩av一区| 久久综合色视频| 亚洲高清在线一区| 日韩资源在线观看| 日韩精品一区不卡| 99久久免费精品| 欧美无砖专区免费| 永久免费观看精品视频| 亚洲色图狂野欧美| 国产精品午夜影院| 成人丝袜18视频在线观看| 中国一区二区三区| 国产成人免费精品| 国产一区二区三区精品久久久| 久久久久久久伊人| 丰满少妇久久久久久久| 一级黄色录像免费看| 成人精品国产| 尤物精品国产第一福利三区 | av女优在线| 色哟哟一区二区| 国产偷人妻精品一区| 亚洲成色精品| 国产一区高清视频| 国产美女精品写真福利视频| 亚洲第一精品久久忘忧草社区| 亚洲综合视频网站| 国产自产2019最新不卡| 免费久久久久久| 4438五月综合| 久久午夜a级毛片| 国产又粗又猛又黄| 国产精品久久毛片av大全日韩| 可以免费在线看黄的网站| 久久最新网址| 国产精品99免视看9| 精品视频二区| 欧美日韩精品电影| 极品色av影院| 国产精品一区二区久久精品爱涩| 特级黄色录像片| 无码国模国产在线观看| 久久久久久有精品国产| 无码国产精品高潮久久99| 精品久久久久久久久久久| 香蕉视频黄色在线观看| 久久国产精品毛片| 亚洲国产一区二区在线| 视频欧美精品| 久久久久久亚洲| 你懂的好爽在线观看| 欧美自拍丝袜亚洲| 男人晚上看的视频| 成人免费av在线| 欧在线一二三四区| 天天做天天爱天天综合网| 亚洲精品日韩av| 96av在线| 怡红院精品视频| 国产高清不卡视频| 激情懂色av一区av二区av| 蜜桃av免费看| 国产一区二区三区免费| 国产九色porny| 欧美午夜精品一区二区三区电影| 国产精品自拍网| 欧美理论电影| 亚洲图片在线综合| 99久久一区二区| 一本大道久久a久久综合婷婷 | 手机亚洲第一页| 欧美日韩视频在线第一区 | 欧美videos极品另类| 精品国产一区a| 中文字幕久久熟女蜜桃| 亚洲综合色成人| 国产三级av在线播放| 国产一区二区三区四区五区入口| 日日摸日日碰夜夜爽无码| 欧美一区2区| 99久热re在线精品996热视频| 二区三区不卡| 欧美精品在线免费观看| 日本大臀精品| 欧美不卡在线视频| 波多野结衣在线观看视频| 夜夜嗨av一区二区三区网页 | 亚洲国产美国国产综合一区二区| 97人妻人人揉人人躁人人| 高清不卡在线观看av| 手机在线免费观看毛片| 亚洲国产专区校园欧美| 污视频在线免费观看一区二区三区| 日韩精品免费视频一区二区三区 | 国产又黄又大又爽| 日韩欧美国产免费播放| 欧美成人aaa片一区国产精品| 久久九九久久九九| 天天躁日日躁狠狠躁av| 精品一区二区三区的国产在线播放| 亚洲中文字幕无码中文字| 亚洲久久久久| 自拍亚洲欧美老师丝袜| 国产精品亚洲片在线播放| 国产日韩精品一区观看| 精品国产欧美| 国产中文日韩欧美| 亚洲精品.com| 日本中文字幕久久看| av在线中出| 久99九色视频在线观看| а√天堂官网中文在线| 中文字幕日本精品| 国产高清一级毛片在线不卡| 亚洲激情小视频| 欧美一级一区二区三区| 欧美va在线播放| 亚洲成a人片77777精品| 欧美一级理论片| 91激情在线观看| 欧美日韩一区二区不卡| 精品无码一区二区三区的天堂| 精品久久久久久久久久久久| 精品一级少妇久久久久久久| 一区二区久久久| 免费麻豆国产一区二区三区四区| 亚洲人成在线播放网站岛国| 四季av中文字幕| 国产精品久久看| 91免费在线看片| 国产精品麻豆久久久| 后入内射无码人妻一区| 国产精品久久久久久久久晋中| 黄大色黄女片18免费| 国产精品欧美久久久久无广告| 久久精品三级视频| 国产精品国产三级国产aⅴ入口| 91麻豆制片厂| 日韩理论片一区二区| 青娱乐免费在线视频| 亚洲国产人成综合网站| 久久视频免费在线观看| 天天综合日日夜夜精品| 国产成人在线免费观看视频| 欧美视频裸体精品| 国产精品sm调教免费专区| 欧美无乱码久久久免费午夜一区 | 狠狠色丁香婷婷综合| 久久出品必属精品| 成人免费毛片高清视频| 久久人人爽人人爽人人片| 久久久国际精品| 国产又粗又长又黄的视频| 亚洲免费伊人电影| 日韩毛片在线播放| 在线观看av一区二区| 国产精品久久久久久久久久久久久久久久 | 亚洲另类在线制服丝袜| 日韩美女黄色片| 欧美午夜宅男影院| 99久久99久久久精品棕色圆| 精品区一区二区| 免费av在线电影| 久久伊人精品天天| 高清毛片在线观看| 国产精品美女久久久久av超清| 成人97精品毛片免费看| 国产精品一区二区三区观看| 国产精品嫩草影院在线看| 黄瓜视频免费观看在线观看www | 亚洲第一二三区| 亚洲午夜久久久影院伊人| 激情久久婷婷| 亚洲欧洲日本精品| 成人免费va视频| 992在线观看| 黄色精品一区二区| 96日本xxxxxⅹxxx17| 亚洲精品福利在线| 黄网站免费在线观看| 午夜伦理精品一区| 伊人久久一区| 日本一区二区三区视频免费看| 性欧美69xoxoxoxo| 少妇高清精品毛片在线视频| 国产一区二区电影| 欧美 日韩 国产 成人 在线观看| 亚洲免费在线视频一区 二区| 欧美啪啪小视频| 欧美一区二区三区在线观看| 牛牛澡牛牛爽一区二区| 久久99久久久久久久噜噜| 中文字幕系列一区| 精品国产综合| 午夜久久福利| 国产精品久久a| wwwwww.欧美系列| 国产一级av毛片| 欧美一区二区三区免费| www.黄在线观看| 欧美在线激情网| 开心激情综合| 久久这里只有精品8| 国产一区二区三区在线观看精品| 无码一区二区三区在线| 亚洲6080在线| 亚洲乱码精品久久久久..| 日韩在线观看高清| 国产成人精品一区二区三区视频 | 亚洲成人资源| 国产吃瓜黑料一区二区| 亚洲欧美日韩电影| 最近中文字幕在线观看视频| 亚洲精品综合精品自拍| 24小时免费看片在线观看| 99久久精品无码一区二区毛片| 久久久久久久久国产一区| 丁香婷婷激情网| 日本一区二区免费在线观看视频 | 国产aaa免费视频| 国产精品一区免费视频| 免费在线观看黄色小视频| 欧美性感一区二区三区| 国产福利免费在线观看| 日韩av三级在线观看| 亚洲精品进入| 日韩亚洲在线视频| 国产三级久久久| 九九热最新视频| 一区二区三区 在线观看视| 经典三级一区二区| 亚洲三区在线| 久久91精品国产91久久小草| 妖精视频在线观看免费| 欧美撒尿777hd撒尿| 在线观看免费版| 国产在线观看精品| 亚洲成av人电影| 性折磨bdsm欧美激情另类| 一区二区三区毛片| 黄色av网站免费在线观看| 久久久女人电视剧免费播放下载| 9l视频自拍蝌蚪9l视频成人| 人妻无码久久一区二区三区免费 | 日本不卡一区二区三区视频| 日韩av二区在线播放| 欧美成人久久久免费播放| 欧美久久久久久久久久| aaa大片在线观看| 国产精品视频免费观看| 老鸭窝毛片一区二区三区| 国产精品久久久视频| 日韩一区二区在线观看视频播放| 亚洲电影视频在线| 久久婷婷开心| 日本不卡的三区四区五区| 亚洲女人久久久| 精品福利在线导航| 日本欧美日韩| 黄色小视频大全| av高清不卡在线| 精人妻无码一区二区三区| 日韩在线www| 精品三级av在线导航| 日日摸天天爽天天爽视频| 中文字幕亚洲精品在线观看| 黄色美女一级片| 国产精品91久久久久久| 欧美精品福利| 精品成人av一区二区三区| 3d动漫精品啪啪| 亚洲天堂av影院| 日本久久高清视频| 久久免费午夜影院| 国产人妖在线播放| 国产福利精品av综合导导航| 你懂的亚洲视频| 久久久视频6r| 欧美精品一区二区蜜臀亚洲| 欧美精品资源| 国产自产在线视频| 国产精品久久午夜| 青梅竹马是消防员在线| 国产在线a不卡| 新狼窝色av性久久久久久| 欧美特级一级片| 在线一区二区日韩| 琪琪久久久久日韩精品|