Trying to get print results from YOLOv4 python object detection in PHP but returns blank

Viewed 101

I am using this git package to run object detection in python using YOLOv4

https://github.com/erentknn/yolov4-object-detection

The script works great and I can print the objects found with confidence in the terminal, but when I execute it from PHP I get back empty results. Im guessing maybe because the PHP script is waiting for the python to finish and not returning the results in real time. I tried creating a dictionary to store the results and return at the end but still returns nothing. I was able to do it in YOLOv3 easily, not sure what changed with v4.

Edit: After some more test I cant even write the results to a file which is strange. I can if run from the terminal.

Edit: If I var_dump($output) it returns NULL. With debugging on I don't get any extra info returned.

Im running the script - yolo_video.py

# example usage: python3 yolo_video.py -i video.mp4 -o video_out.avi
import argparse
import glob
import time
import logging
from pathlib import Path
import cv2
import numpy as np

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
formatter = logging.Formatter("%(asctime)s-%(name)s-%(message)s")

stream_handler = logging.StreamHandler()
stream_handler.setFormatter(formatter)
logger.addHandler(stream_handler)

parser = argparse.ArgumentParser()
parser.add_argument("-i", "--input", type=str, default="",
                    help="video.mp4")
parser.add_argument("-o", "--output", type=str, default="",
                    help="path to (optional) output video file")
parser.add_argument("-d", "--display", type=int, default=1,
                    help="display output or not (1/0)")
parser.add_argument("-ht", "--height", type=int, default=1200,
                    help="height of output")
parser.add_argument("-wt", "--width", type=int, default=700,
                    help="width of output")
parser.add_argument("-c", "--confidence", type=float, default=0.8,
                    help="confidence threshold")
parser.add_argument("-t", "--threshold", type=float, default=0.6,
                    help="non-maximum supression threshold")

args = parser.parse_args()
logger.info("Parsed Arguments")

CONFIDENCE_THRESHOLD = args.confidence
NMS_THRESHOLD = args.threshold
if not Path(args.input).exists():
    raise FileNotFoundError("Path to video file is not exist.")

vc = cv2.VideoCapture(args.input)
weights = glob.glob("yolo/*.weights")[0]
labels = glob.glob("yolo/*.txt")[0]
cfg = glob.glob("yolo/*.cfg")[0]

logger.info("Using {} weights ,{} configs and {} labels.".format(weights, cfg, labels))

class_names = list()
with open(labels, "r") as f:
    class_names = [cname.strip() for cname in f.readlines()]

COLORS = np.random.randint(0, 255, size=(len(class_names), 3), dtype="uint8")

net = cv2.dnn.readNetFromDarknet(cfg, weights)
net.setPreferableBackend(cv2.dnn.DNN_BACKEND_CUDA)
net.setPreferableTarget(cv2.dnn.DNN_TARGET_CUDA)

layer = net.getLayerNames()
layer = [layer[i[0] - 1] for i in net.getUnconnectedOutLayers()]
writer = None


def detect(frm, net, ln):
    (H, W) = frm.shape[:2]
    blob = cv2.dnn.blobFromImage(frm, 1 / 255.0, (416, 416), swapRB=True, crop=False)
    net.setInput(blob)
    start_time = time.time()
    layerOutputs = net.forward(ln)
    end_time = time.time()

    boxes = []
    classIds = []
    confidences = []
    for output in layerOutputs:
        for detection in output:
            scores = detection[5:]
            classID = np.argmax(scores)
            confidence = scores[classID]

            if confidence > CONFIDENCE_THRESHOLD:
                box = detection[0:4] * np.array([W, H, W, H])
                (centerX, centerY, width, height) = box.astype("int")
                x = int(centerX - (width / 2))
                y = int(centerY - (height / 2))

                boxes.append([x, y, int(width), int(height)])
                classIds.append(classID)
                confidences.append(float(confidence))
                
    idxs = cv2.dnn.NMSBoxes(boxes, confidences, CONFIDENCE_THRESHOLD, NMS_THRESHOLD)

    if len(idxs) > 0:
        for i in idxs.flatten():
            (x, y) = (boxes[i][0], boxes[i][1])
            (w, h) = (boxes[i][2], boxes[i][3])

            color = [int(c) for c in COLORS[classIds[i]]]
            cv2.rectangle(frm, (x, y), (x + w, y + h), color, 2)
            text = "{}: {:.4f}".format(class_names[classIds[i]], confidences[i])
            
            # HERE IM PRINTING THE RESULTS (WORKS IN TERMNAL)
            print("found")
            print(confidences[i])
            print(class_names[classIds[i]])

            cv2.putText(
                frm, text, (x, y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 2
            )
            
            fps_label = "FPS: %.2f" % (1 / (end_time - start_time))
            cv2.putText(
                frm, fps_label, (0, 25), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 2
            )


while cv2.waitKey(1) < 1:
    (grabbed, frame) = vc.read()
    if not grabbed:
        break
    frame = cv2.resize(frame, (args.height, args.width))
    detect(frame, net, layer)

    if writer is not None:
        writer.write(frame)

Then in my PHP script

$command = escapeshellcmd('python3 yolo_video.py -i video.mp4 -o video_out.avi');
$output = shell_exec($command);
echo $output;

How can I get the results from the python script to echo out in PHP? Im not getting any errors and the script is completing.

2 Answers

cv2.waitKey is not working well in general, PHP or jupyter notebook depending on your machine.

I tried this on my machine and it solved the problem :

while(vc.isOpened()):

if frame is None:
        break

or

if not grabbed:
        break

The script will stop when the video is finished.

After rechecking your code, I believe the main reason is waitKey(1):

while cv2.waitKey(1) < 1:
    (grabbed, frame) = vc.read()
    if not grabbed:
        break
    frame = cv2.resize(frame, (args.height, args.width))
    detect(frame, net, layer)

    if writer is not None:
        writer.write(frame)

Not sure how it is suppose to wait for interaction from the user as it is called from a another script. But cv.waitKey(1) is suppose to return the key pressed after 1 ms (technically more) of waiting from the user. So your condition is probably while None < 1. Check more on a similar issue, but in essence the read returns (return value, image) where return value is was it successful.

Another usual culprit in this kind of scenarios is argparse but would check first maybe to reformulate to loop over frames of the video itself in the form of checking the output of read. This can be worked around with a config file.

Try replacing the condition to something like:

while True:

Checked around, and indeed there is a better condition while(vc.isOpened()):. Would suggest you read the question that explains it.

Related