Search code examples
python-3.xopencvfor-loopamazon-rekognition

How to use python function to iterate through files received from another function


I'm stuck with a problem with my functions. I'm building a face recognition system which should work like this:

  1. Read a video file
  2. Use opencv and haarcascade to recognize the frames from the video where are human faces detected
  3. Save those frames locally`

To this point, everything goes fine, here's the function I use to do these steps:

import cv2
from imutils.video import FPS
import numpy as np
import argparse
import imutils
import os

async def take_snapshots(file):
    cascPath = "haarcascade_frontalface_default.xml"

    faceCascade = cv2.CascadeClassifier(cascPath)


    # construct the argument parse and parse the arguments
    # ap = argparse.ArgumentParser()
    # ap.add_argument("-v", "--video", required=True,
    #                 help="video.mp4")
    # args = vars(ap.parse_args())

    # open a pointer to the video stream and start the FPS timer
    # stream = cv2.VideoCapture(args["video"])
    stream = cv2.VideoCapture(file)
    fps = FPS().start()

    try:

        # creating a folder named data
        if not os.path.exists('data'):
            os.makedirs('data')

    # if not created then raise error
    except OSError:
        print('Error: Creating directory of data')

    # frame
    currentframe = 0

    # loop over frames from the video file stream
    while True:
        # grab the frame from the threaded video file stream
        (grabbed, frame) = stream.read()

        # if the frame was not grabbed, then we have reached the end
        # of the stream
        if not grabbed:
            break

        # resize the frame and convert it to grayscale (while still
        # retaining 3 channels)
        frame = imutils.resize(frame, width=980)
        frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        frame = np.dstack([frame, frame, frame])
        faces = faceCascade.detectMultiScale(
                            frame,
                            scaleFactor=1.1,
                            minNeighbors=4,
                            minSize=(20, 20),
                            maxSize=(40, 40),
                            # flags=cv2.CASCADE_SCALE_IMAGE
                    )

        for (x, y, w, h) in faces:
                # cv2.rectangle(frame, (x, y), (x+w, y+h), (255, 0, 0), 3)
                if grabbed:
                        # Save just the rectangle faces in SubRecFaces
                        # sub_face = frame[y:y+h, x:x+w]
                        name = './data/frame' + str(currentframe) + '.jpg'
                        print('Creating...' + name)

                        # writing the extracted images
                        cv2.imwrite(name, frame)

                        currentframe += 1

                        if cv2.waitKey(1) & 0xFF == ord('q'):
                                break

                else:
                        break


        # show the frame and update the FPS counter
        # cv2.imshow("Frame", frame)
        # cv2.waitKey(1)
        fps.update()

    # stop the timer and display FPS information
    fps.stop()
    print("[INFO] elasped time: {:.2f}".format(fps.elapsed()))
    print("[INFO] approx. FPS: {:.2f}".format(fps.fps()))

    # do a bit of cleanup
    stream.release()
    cv2.destroyAllWindows()
  1. Take a saved frame and use aws rekognition api to detect faces from it (if you want to know why, it's because of accuracy)
  2. Save a new copy from the frames that include faces, with bounding boxes

Step 4 goes well, no problems there. when the code reaches step 5, I encounter a problem where my code goes through every frame saved at step 3, it also skips the two first frames where are no faces. After that it saves frame2.jpg as pic0.jpg, as it should. Problem is, after that it should save a new picture (pic1, pic2 etc) from every frame but instead it just overwrites the pic0.jpg with every round. I know the problem is with my loop, I just can't figure out how to fix it.

Here are the functions I use for steps 4 and 5:

import boto3
from pathlib import Path
import os
import cv2
import io
from PIL import Image, ImageDraw, ExifTags, ImageColor


async def detect_faces(photo):
    image = Image.open(open(photo, 'rb'))
    stream = io.BytesIO()
    image.save(stream, format=image.format)
    image_binary = stream.getvalue()

    client = boto3.client('rekognition')
    response = client.detect_faces(
        Image={'Bytes': image_binary}, Attributes=['ALL'])

    draw = ImageDraw.Draw(image)

    print('Detected faces in ' + photo)

    currentpic = 0
    for face in response['FaceDetails']:
        print('Confidence: ' + str(face['Confidence']))


        box = face['BoundingBox']
        imgWidth, imgHeight = image.size
        left = imgWidth * box['Left']
        top = imgHeight * box['Top']
        width = imgWidth * box['Width']
        height = imgHeight * box['Height']

        # print('Left: ' + '{0:.0f}'.format(left))
        # print('Top: ' + '{0:.0f}'.format(top))
        # print('Face Width: ' + "{0:.0f}".format(width))
        # print('Face Height: ' + "{0:.0f}".format(height))

        points = (
            (left, top),
            (left + width, top),
            (left + width, top + height),
            (left, top + height),
            (left, top)

        )
        draw.line(points, fill='#00d400', width=2)


        name = './results/pic' + str(currentpic) + '.jpg'
        print('Creating final pic.....' + name)
        image.save(name)
        currentpic += 1

    return len(response['FaceDetails'])



async def main():
    directory_in_str = './data'
    pathlist = Path(directory_in_str).glob('**/*.jpg')

    try:
        if not os.path.exists('results'):
            os.makedirs('results')

        # if not created then raise error
    except OSError:
        print('Error: Creating directory of data')

    for path in pathlist:
        # path is object not string
        path_in_str = str(path)
        # print(path_in_str)
        photo = path_in_str

        face_count = await detect_faces(photo)
        print("Faces detected: " + str(face_count))

Finally, here is the main function I use to run these functions:

import read_frames_slow
import detect_faces
import asyncio


async def main():
  await read_frames_slow.take_snapshots('video.mp4')
  await detect_faces.main()

loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()

Would really appreciate a help to solve this problem.


Solution

  • Use a pathlib.Path object instead of a string (https://pillow.readthedocs.io/en/stable/reference/Image.html)

    like

    im.save(os.path.join(r"C:\Users\abc123\Desktop\Movie", f + ext), "JPEG" )

    in file is not saved in particular directory using PIL im.save

    because name = './results/pic' + str(currentpic) + '.jpg' is interpreted as a string or file name, not as a path (see https://pillow.readthedocs.io/en/stable/reference/Image.html, Python convert a str to path type?)