import logging
import time
import uuid
from concurrent.futures import ThreadPoolExecutor

import cv2

from app import redis_service

from libs.face_recognition import ALG, FaceRecognitionStatusEnum

import numpy as np

from settings import NUM_MAX_THREADS


class FaceRecognition:
    """Service for using opencv face recognition."""
    def __init__(self, file_id, video_path, threshold=80):
        """
        Sets model’s parameters.
        Args:
            file_id (str): file id
            video_path (str): path to video
            threshold (int): model’s threshold
        """
        self.face_cascade_path = cv2.data.haarcascades + ALG
        self.face_cascade = cv2.CascadeClassifier(self.face_cascade_path)
        self.faces_list = []
        self.names_list = []
        self.threshold = threshold
        self.video_path = video_path
        self.video = cv2.VideoCapture(self.video_path)
        self.frame_count = int(self.video.get(cv2.CAP_PROP_FRAME_COUNT))
        self.frame_num = int(self.video.get(cv2.CAP_PROP_POS_FRAMES))
        self.file_id = file_id
        self.status = FaceRecognitionStatusEnum.PROCESS
        self.persons = 0

    def process(self):
        """
        Process of recognition faces in video by frames.
        Writes id as uuid4.
        Returns:
            tuple: with list of faces and list of names
        """
        pool = ThreadPoolExecutor(max_workers=NUM_MAX_THREADS)

        # redis_data = redis_service.get(self.file_id)
        # self.status = redis_data.get("status")

        while True:
            if self.status == FaceRecognitionStatusEnum.PAUSE:
                logging.info(
                    f"File id: {self.file_id} | Paused by user"
                    f"Frame: {self.frame_num}")
                break

            if self.status == FaceRecognitionStatusEnum.RESUME:
                resume_data = redis_service.get(self.file_id)
                self.faces_list = resume_data.get("faces_list")
                self.names_list = resume_data.get("names_list")
                self.frame_num = resume_data.get("frame")
                self.persons = resume_data.get("persons")

                # @@@
                logging.info(f"RESUME"
                             f"faces {self.faces_list} / {type(self.faces_list)}"
                             f"names {self.names_list} / {type(self.names_list)}"
                             f"frame {self.frame_num} / {type(self.frame_num)}"
                             f"persons {self.persons} / {type(self.persons)}")

                logging.info(
                    f"\nFile id: {self.file_id} | Resume\n"
                    f"Frame: {self.frame_num}")
                for i in range(self.frame_num + 1):
                    self.video.read()
                self.status = FaceRecognitionStatusEnum.PROCESS

            ret, frame = self.video.read()
            self.frame_num = int(self.video.get(cv2.CAP_PROP_POS_FRAMES))

            if not ret:
                return

            pool.submit(self._process_frame, frame)

        pool.shutdown()
        self._close()

        logging.info(f"@@@ {self.frame_count} {self.frame_num}")

        if self.frame_count == self.frame_num:
            self.status = FaceRecognitionStatusEnum.READY

        logging.info(
            f"\n-----\n"
            f"File id: {self.file_id} CLOSED!\n"
            f"Frame: {self.frame_num}/{self.frame_count}\n"
            f"Persons: {self.persons}\n"
            f"Status: {self.status}\n"
            f"-----\n")
        return self.faces_list, self.names_list

    def _process_frame(self, frame):
        """
        Frame processing.
        Args:
            frame (cv2.typing.MatLike): cv2 frame

        Returns:
            None:
        """
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        faces = self.face_cascade.detectMultiScale(
            gray, scaleFactor=1.1, minNeighbors=5, minSize=(100, 100))

        for (x, y, w, h) in faces:
            cur_face = gray[y:y + h, x:x + w]
            rec = cv2.face.LBPHFaceRecognizer.create()
            rec.train([cur_face], np.array(0))
            f = True
            for face in self.faces_list:
                _, confidence = rec.predict(face)
                if confidence < self.threshold:
                    f = False

            if f:
                label = str(uuid.uuid4())
                self.faces_list.append(cur_face.tolist())
                self.names_list.append(label)
                self.persons += 1

        redis_service.update(
            self.file_id,
            {
                "file_id": self.file_id,
                "faces_list": self.faces_list,
                "names_list": self.names_list,
                "status": str(self.status),
                "frame": str(self.frame_num),
                "persons": str(self.persons),
                "filepath": self.video_path
            }
        )
        logging.info(
            f"\n-----\n"
            f"File id: {self.file_id}\n"
            f"Frame: {self.frame_num}/{self.frame_count} passed\n"
            f"Persons: {self.persons}\n"
            f"Status: {self.status}\n"
            f"-----\n")

    def _close(self):
        """
        Closes video and destroys all windows.
        Returns:
            None:
        """
        self.video.release()
        cv2.destroyAllWindows()

При таком коде я вижу в логах это:
File id: 6f95411a-83ae-4cd9-bf5e-bea6f0798650
2023-09-01T10:05:10.608582087Z Frame: 7/1104 passed
2023-09-01T10:05:10.608589247Z Persons: 0
2023-09-01T10:05:10.608596180Z Status: FaceRecognitionStatusEnum.PROCESS
2023-09-01T10:05:10.608603363Z -----
2023-09-01T10:05:10.608610332Z 
2023-09-01T10:05:10.617082688Z 2023-09-01 10:05:10,616 - root - INFO - 
2023-09-01T10:05:10.617142612Z -----
2023-09-01T10:05:10.617152651Z File id: 6f95411a-83ae-4cd9-bf5e-bea6f0798650
2023-09-01T10:05:10.617160653Z Frame: 9/1104 passed
2023-09-01T10:05:10.617198838Z Persons: 0
2023-09-01T10:05:10.617207752Z Status: FaceRecognitionStatusEnum.PROCESS
2023-09-01T10:05:10.617216275Z -----
2023-09-01T10:05:10.617223828Z 
2023-09-01T10:05:10.625078084Z 2023-09-01 10:05:10,624 - root - INFO - 
2023-09-01T10:05:10.625111060Z -----
2023-09-01T10:05:10.625119196Z File id: 6f95411a-83ae-4cd9-bf5e-bea6f0798650
2023-09-01T10:05:10.625126777Z Frame: 11/1104 passed
2023-09-01T10:05:10.625134790Z Persons: 0
2023-09-01T10:05:10.625141743Z Status: FaceRecognitionStatusEnum.PROCESS
2023-09-01T10:05:10.625149257Z -----
2023-09-01T10:05:10.625156428Z 
2023-09-01T10:05:10.633831648Z 2023-09-01 10:05:10,633 - root - INFO - 
2023-09-01T10:05:10.633880141Z -----
2023-09-01T10:05:10.633890014Z File id: 6f95411a-83ae-4cd9-bf5e-bea6f0798650
2023-09-01T10:05:10.633897476Z Frame: 13/1104 passed
2023-09-01T10:05:10.633904708Z Persons: 0
2023-09-01T10:05:10.633912736Z Status: FaceRecognitionStatusEnum.PROCESS
2023-09-01T10:05:10.633920556Z -----
2023-09-01T10:05:10.633927612Z 
2023-09-01T10:05:10.643055683Z 2023-09-01 10:05:10,642 - root - INFO - 
2023-09-01T10:05:10.643098971Z -----
2023-09-01T10:05:10.643110143Z File id: 6f95411a-83ae-4cd9-bf5e-bea6f0798650
2023-09-01T10:05:10.643118501Z Frame: 14/1104 passed
2023-09-01T10:05:10.643146388Z Persons: 0
2023-09-01T10:05:10.643165820Z Status: FaceRecognitionStatusEnum.PROCESS
2023-09-01T10:05:10.643173137Z -----
2023-09-01T10:05:10.643180116Z 
2023-09-01T10:05:10.650327925Z 2023-09-01 10:05:10,650 - root - INFO - 
2023-09-01T10:05:10.650371503Z -----
2023-09-01T10:05:10.650379598Z File id: 6f95411a-83ae-4cd9-bf5e-bea6f0798650
2023-09-01T10:05:10.650386596Z Frame: 15/1104 passed
2023-09-01T10:05:10.650394358Z Persons: 0
2023-09-01T10:05:10.650402364Z Status: FaceRecognitionStatusEnum.PROCESS
2023-09-01T10:05:10.650409419Z -----
2023-09-01T10:05:10.650416290Z 
2023-09-01T10:05:10.654174595Z 2023-09-01 10:05:10,653 - root - INFO - 
2023-09-01T10:05:10.654222958Z -----
2023-09-01T10:05:10.654232245Z File id: 6f95411a-83ae-4cd9-bf5e-bea6f0798650
2023-09-01T10:05:10.654241438Z Frame: 15/1104 passed
2023-09-01T10:05:10.654248645Z Persons: 0
2023-09-01T10:05:10.654256864Z Status: FaceRecognitionStatusEnum.PROCESS
2023-09-01T10:05:10.654263813Z -----
2023-09-01T10:05:10.654271947Z 
2023-09-01T10:05:10.662678493Z 2023-09-01 10:05:10,662 - root - INFO - 
2023-09-01T10:05:10.662774357Z -----
2023-09-01T10:05:10.662786233Z File id: 6f95411a-83ae-4cd9-bf5e-bea6f0798650
2023-09-01T10:05:10.662794704Z Frame: 17/1104 passed
2023-09-01T10:05:10.662801955Z Persons: 0
2023-09-01T10:05:10.662810091Z Status: FaceRecognitionStatusEnum.PROCESS
2023-09-01T10:05:10.662818633Z -----
2023-09-01T10:05:10.662826704Z 
2023-09-01T10:05:10.671762973Z 2023-09-01 10:05:10,671 - root - INFO - 
2023-09-01T10:05:10.671823619Z -----


То есть кадры будто бы повторяются, а потом перескакивают, как мне получать корректное значение текущего кадра?
Я обновляю frame_num вот здесь:
ret, frame = self.video.read()
self.frame_num = int(self.video.get(cv2.CAP_PROP_POS_FRAMES))