from abc import ABC, abstractmethod
import cv2 as cv
import numpy as np
import tensorflow as tf
import time
import requests
import os
from multiprocessing import Process, Queue
from pytwovision.image_process.frame_decorator import Frame
from pytwovision.image_process.resize_with_bbox import ResizeWithBBox
from pytwovision.compute.yolov3_calculus import YoloV3Calculus
from pytwovision.utils.draw import draw_bbox
from pytwovision.input_output.camera import Camera
[docs]class DetectionMode(ABC):
"""
The Abstract Class which defines detect method that contains the skeleton of detection algorithm with different methods such as detection on images, detection on video, detection using multiprocessing and real time detection.
"""
[docs] def predict(self, model, image_data):
"""Apply a prediction step.
Args:
model: expects a tensorflow model trained.
image_data: expects an array or tensor with image data.
Returns:
predicted bounding boxes.
"""
pred_bbox = model.predict(image_data)
pred_bbox = [tf.reshape(x, (-1, tf.shape(x)[-1])) for x in pred_bbox]
return tf.concat(pred_bbox, axis=0)
[docs] def postprocess_boxes(self, original_image, pred_bbox, input_size=416, score_threshold=0.3, iou_threshold=0.45, nms_method="nms"):
"""Apply a postprocess and non maximum supression step and resize bboxes.
Args:
original_image: expects a tensorflow model trained.
pred_bbox: a tensor of predicted bounding boxes.
input_size: integer to resize bounding boxes from their resized dimensions to original dimensions (input_size).
score_threshold: if the score of a bounding boxes is less than score_threshold, it will be discard.
iou_threshold: a parameter between (0, 1) which is used for nms algorithm
nms_method: a string that can be 'nms' or 'soft-nms'.
Returns:
Improved bounding boxes
"""
compute = YoloV3Calculus()
bboxes = compute.postprocess_boxes(pred_bbox, original_image, input_size, score_threshold)
return compute.nms(bboxes, iou_threshold, method=nms_method)
[docs] def pre_process(self, image, input_size):
"""Apply resize images step.
Args:
image: expects a an array.
input_size: integer to resize an input image from their original dimensions to an square image.
Returns:
Resized images and bounding boxes.
"""
frame = Frame(np.copy(image))
image_data = ResizeWithBBox(frame).apply([input_size, input_size])
return image_data[np.newaxis, ...].astype(np.float32)
[docs] def draw(self, original_image, bboxes, class_file_name, rectangle_colors, homogeneous_points=None, text_colors=(255,255,0)):
"""Draw bounding boxes on images.
Args:
original_image: an array which correspond with an image
bboxes: their bounding boxes.
class_file_name: a path with a .txt file where the classes are saved.
rectangle_colors: if this parameter is a string empty bounding box colors will be assing by default, however if rectangle_colors is a tuple like: (R, G, B) that will be bounding box colors.
homogeneous_points: an array with dimensions n x 4 where each row is like (X, Y, Z, W). However if is None it won't be drawed.
Returns:
An image with bounding boxes and homogeneous coordinates.
"""
return draw_bbox(original_image, bboxes, class_file_name=class_file_name,
rectangle_colors=rectangle_colors, homogeneous_points=homogeneous_points, text_colors=text_colors)
[docs] @abstractmethod
def detect(self):
"""Apply detection pipeline"""
pass
[docs] @abstractmethod
def show(self):
"""Show actual frame in a window"""
pass
[docs]class DetectRealTime(DetectionMode):
[docs] def detect(self, model, camera: Camera, class_file_name, output_path="", input_size=416,
score_threshold=0.3, iou_threshold=0.45, rectangle_colors='', nms_method="nms", show=True):
"""Apply detection pipeline in realtime, if you want to close the window just press 'q'.
Args:
model: expects a tensorflow model trained.
camera: An instance of camera object.
class_file_name: it's the path of classes .txt file.
output_path: if is an empty string, it won't be saved, but it is a path it save like a video.
input_size: integer to resize bounding boxes from their resized dimensions to original dimensions (input_size).
score_threshold: if the score of a bounding boxes is less than score_threshold, it will be discard.
iou_threshold: a parameter between (0, 1) which is used for nms algorithm.
rectangle_colors: if this parameter is a string empty bounding box colors will be assing by default, however if rectangle_colors is a tuple like: (R, G, B) that will be bounding box colors.
nms_method: a string that can be 'nms' or 'soft-nms'.
show: a boolean to show frame pcessed.
"""
times = []
vid = self.camera_input(camera)
if isinstance(vid, cv.VideoCapture):
out, vid, fps = self.prepare_input(vid, output_path)
while True:
if isinstance(vid, cv.VideoCapture):
ret, img = vid.read()
if not ret: break
else:
img = self.camera_input(camera)
try:
original_frame = cv.cvtColor(img, cv.COLOR_BGR2RGB)
original_frame = cv.cvtColor(original_frame, cv.COLOR_BGR2RGB)
except:
break
image_data = self.pre_process(original_frame, input_size)
t1 = time.time()
pred_bbox = self.predict(model, image_data)
t2 = time.time()
bboxes = self.postprocess_boxes(original_frame, pred_bbox, input_size,
score_threshold, iou_threshold, nms_method)
times.append(t2-t1)
times = times[-20:]
ms = sum(times)/len(times)*1000
fps = 1000 / ms
print("Time: {:.2f}ms, {:.1f} FPS".format(ms, fps))
frame = self.draw(original_frame, bboxes, class_file_name, rectangle_colors)
cv.putText(frame, "Time: {:.1f}FPS".format(fps), (0, 30),
cv.FONT_HERSHEY_COMPLEX_SMALL, 1, (0, 0, 255), 2)
if output_path != '': out.write(frame)
if show:
self.show(frame, camera)
if cv.waitKey(25) & 0xFF == ord("q"):
cv.destroyAllWindows()
break
if show: cv.destroyAllWindows()
[docs] def show(self, image, camera: Camera):
"""Show an image
Args:
image: an image array.
camera: An instance of camera object.
"""
cv.imshow(camera.id, image)
[docs]class DetectRealTimeMP(DetectionMode):
[docs] def detect(self, model, camera: Camera, class_file_name, output_path="", input_size=416,
score_threshold=0.3, iou_threshold=0.45, rectangle_colors='', nms_method="nms"):
"""Apply detection pipeline using multiprocessing, if you want to close the window just press 'q'.
Args:
model: expects a tensorflow model trained.
camera: An instance of camera object.
class_file_name: it's the path of classes .txt file
output_path: if is an empty string, it won't be saved, but it is a path it save like a video.
input_size: integer to resize bounding boxes from their resized dimensions to original dimensions (input_size).
score_threshold: if the score of a bounding boxes is less than score_threshold, it will be discard.
iou_threshold: a parameter between (0, 1) which is used for nms algorithm
rectangle_colors: if this parameter is a string empty bounding box colors will be assing by default, however if rectangle_colors is a tuple like: (R, G, B) that will be bounding box colors.
nms_method: a string that can be 'nms' or 'soft-nms'.
"""
vid = self.camera_input(camera)
if isinstance(vid, cv.VideoCapture):
out, vid, fps = self.prepare_input(vid, output_path)
original_frames = Queue()
frames_data = Queue()
predicted_data = Queue()
processed_frames = Queue()
processing_times = Queue()
final_frames = Queue()
p1, p2, p3 = self.multi_process_initialization(model, original_frames, frames_data, predicted_data, processed_frames, processing_times, final_frames, class_file_name, input_size, score_threshold, iou_threshold, rectangle_colors, nms_method)
p1.start()
p2.start()
p3.start()
while True:
if isinstance(vid, cv.VideoCapture):
ret, img = vid.read()
if not ret: break
else:
img = self.camera_input(camera)
original_image = cv.cvtColor(img, cv.COLOR_BGR2RGB)
original_image = cv.cvtColor(original_image, cv.COLOR_BGR2RGB)
original_frames.put(original_image)
image_data = self.pre_process(original_image, input_size)
frames_data.put(image_data)
while True:
if original_frames.qsize() == 0 and frames_data.qsize() == 0 and predicted_data.qsize() == 0 and processed_frames.qsize() == 0 and processing_times.qsize() == 0 and final_frames.qsize() == 0:
p1.terminate()
p2.terminate()
p3.terminate()
break
elif final_frames.qsize()>0:
image = final_frames.get()
if output_path != '': out.write(image)
cv.destroyAllWindows()
[docs] def predict_bbox_mp(self, model, frames_data, predicted_data, processing_times):
""" predict bounding boxes using multiprocessing. It needs to be initialized.
Args:
model: expects a tensorflow model trained.
frames_data: a queue from multiprocessing package, that corresponds with frames.
predicted_data: a queue from multiprocessing package, that corresponds with predictions.
processing_times: a queue from multiprocessing package, that corresponds with times.
"""
gpus = tf.config.experimental.list_physical_devices('GPU')
if len(gpus) > 0:
try: tf.config.experimental.set_memory_growth(gpus[0], True)
except RuntimeError: print("RuntimeError in tf.config.experimental.list_physical_devices('GPU')")
times = []
while True:
if frames_data.qsize()>0:
image_data = frames_data.get()
t1 = time.time()
processing_times.put(time.time())
pred_bbox = self.predict(model, image_data)
predicted_data.put(pred_bbox)
[docs] def postprocess_mp(self, predicted_data, original_frames, processed_frames, processing_times, input_size, class_file_name, score_threshold, iou_threshold, rectangle_colors, nms_method):
""" Improve bounding boxes using multiprocessing. It needs to be initialized.
Args:
predicted_data: a queue from multiprocessing package, that corresponds with predictions.
original_frames: a queue from multiprocessing package, that corresponds with frames.
processed_frames: a queue from multiprocessing package, that corresponds with processed frames.
processing times: a queue from multiprocessing package, that corresponds with times.
input_size: integer to resize bounding boxes from their resized dimensions to original dimensions (input_size).
class_file_name: it's the path of classes .txt file
score_threshold: if the score of a bounding boxes is less than score_threshold, it will be discard.
iou_threshold: a parameter between (0, 1) which is used for nms algorithm
rectangle_colors: if this parameter is a string empty bounding box colors will be assing by default, however if rectangle_colors is a tuple like: (R, G, B) that will be bounding box colors.
nms_method: a string that can be 'nms' or 'soft-nms'.
"""
times = []
while True:
if predicted_data.qsize()>0:
pred_bbox = predicted_data.get()
while original_frames.qsize() > 1:
original_image = original_frames.get()
bboxes = self.postprocess_boxes(original_image, pred_bbox, input_size, score_threshold, iou_threshold, nms_method)
image = self.draw(original_image, bboxes, class_file_name, rectangle_colors)
times.append(time.time()-processing_times.get())
times = times[-20:]
ms = sum(times)/len(times)*1000
fps = 1000 / ms
image = cv.putText(image, "Time: {:.1f}FPS".format(fps), (0, 30), cv.FONT_HERSHEY_COMPLEX_SMALL, 1, (0, 0, 255), 2)
#print("Time: {:.2f}ms, Final FPS: {:.1f}".format(ms, fps))
processed_frames.put(image)
[docs] def show(self, processed_frames, final_frames):
""" Show preocessed input.
Args:
processed_frames: a queue from multiprocessing package, that corresponds with processed frames.
final_frames: a queue from multiprocessing package, that corresponds with final frames.
"""
while True:
if processed_frames.qsize()>0:
image = processed_frames.get()
final_frames.put(image)
cv.imshow('Detection output', image)
if cv.waitKey(25) & 0xFF == ord("q"):
cv.destroyAllWindows()
break
[docs] def multi_process_initialization(self, model, original_frames, frames_data, predicted_data, processed_frames, processing_times, final_frames, class_file_name, input_size=416, score_threshold=0.3, iou_threshold=0.45, rectangle_colors='', nms_method="nms"):
"""Apply an initialite multi processing prediction, postprocessing and show steps.
Args:
model: expects a tensorflow model trained.
original_frames: a queue from multiprocessing package, that corresponds with frames.
frames_data: a queue from multiprocessing package, that corresponds with frames.
predicted_data: a queue from multiprocessing package, that corresponds with predictions.
processed_frames: a queue from multiprocessing package, that corresponds with processed frames.
processing times: a queue from multiprocessing package, that corresponds with times.
final_frames: a queue from multiprocessing package, that corresponds with final frames.
class_file_name: it's the path of classes .txt file
input_size: integer to resize bounding boxes from their resized dimensions to original dimensions (input_size).
score_threshold: if the score of a bounding boxes is less than score_threshold, it will be discard.
iou_threshold: a parameter between (0, 1) which is used for nms algorithm
rectangle_colors: if this parameter is a string empty bounding box colors will be assing by default, however if rectangle_colors is a tuple like: (R, G, B) that will be bounding box colors.
nms_method: a string that can be 'nms' or 'soft-nms'.
Returns:
an Process tuple (prediction, postprocessing, show)
"""
p1 = Process(target=self.predict_bbox_mp, args=(model, frames_data, predicted_data, processing_times))
p2 = Process(target=self.postprocess_mp, args=(predicted_data, original_frames, processed_frames, processing_times, input_size, class_file_name, score_threshold, iou_threshold, rectangle_colors, nms_method))
p3 = Process(target=self.show, args=(processed_frames, final_frames))
return p1, p2, p3
[docs]class DetectVideo(DetectionMode):
[docs] def detect(self, model, input_path, class_file_name, output_path="", input_size=416,
score_threshold=0.3, iou_threshold=0.45, rectangle_colors='', nms_method="nms", show=True):
"""Apply detection pipeline in a saved video, if you want to close the window just press 'q'.
Args:
model: expects a tensorflow model trained.
input_path: a video path.
class_file_name: it's the path of classes .txt file
output_path: if is an empty string, it won't be saved, but it is a path it save like a video.
input_size: integer to resize bounding boxes from their resized dimensions to original dimensions (input_size).
score_threshold: if the score of a bounding boxes is less than score_threshold, it will be discard.
iou_threshold: a parameter between (0, 1) which is used for nms algorithm
rectangle_colors: if this parameter is a string empty bounding box colors will be assing by default, however if rectangle_colors is a tuple like: (R, G, B) that will be bounding box colors.
nms_method: a string that can be 'nms' or 'soft-nms'.
show: a boolean to show frame pcessed
"""
times, times_2 = [], []
out, vid, fps = self.prepare_input(input_path, output_path)
while True:
_, img = vid.read()
try:
original_image = cv.cvtColor(img, cv.COLOR_BGR2RGB)
original_image = cv.cvtColor(original_image, cv.COLOR_BGR2RGB)
except:
break
image_data = self.pre_process(original_image, input_size)
t1 = time.time()
pred_bbox = self.predict(model, image_data)
t2 = time.time()
bboxes = self.postprocess_boxes(original_image, pred_bbox, input_size, score_threshold, iou_threshold, nms_method)
image = self.draw(original_image, bboxes, class_file_name, rectangle_colors)
t3 = time.time()
times.append(t2-t1)
times_2.append(t3-t1)
times = times[-20:]
times_2 = times_2[-20:]
ms = sum(times)/len(times)*1000
fps = 1000 / ms
fps2 = 1000 / (sum(times_2)/len(times_2)*1000)
image = cv.putText(image, "Time: {:.1f}FPS".format(fps), (0, 30), cv.FONT_HERSHEY_COMPLEX_SMALL, 1, (0, 0, 255), 2)
# CreateXMLfile("XML_Detections", str(int(time.time())), original_image, bboxes, read_class_names(CLASSES))
print("Time: {:.2f}ms, Detection FPS: {:.1f}, total FPS: {:.1f}".format(ms, fps, fps2))
if output_path != '': out.write(image)
if show:
self.show(image)
if cv.waitKey(25) & 0xFF == ord("q"):
cv.destroyAllWindows()
break
if show: cv.destroyAllWindows()
[docs] def show(self, image):
"""Show an image.
Arguments:
image: an image array.
"""
cv.imshow('Detection output', image)
[docs]class DetectImage(DetectionMode):
[docs] def detect(self, model, input_path, class_file_name, output_path="", input_size=416,
score_threshold=0.3, iou_threshold=0.45, rectangle_colors='', nms_method="nms", show=True):
"""Apply detection pipeline in an image, if you want to close the window just press 'q'.
Arguments:
model: expects a tensorflow model trained.
input_path: an image path
class_file_name: it's the path of classes .txt file
output_path: if is an empty string, it won't be saved, but it is a path it save like an image.
input_size: integer to resize bounding boxes from their resized dimensions to original dimensions (input_size).
score_threshold: if the score of a bounding boxes is less than score_threshold, it will be discard.
iou_threshold: a parameter between (0, 1) which is used for nms algorithm
rectangle_colors: if this parameter is a string empty bounding box colors will be assing by default, however if rectangle_colors is a tuple like: (R, G, B) that will be bounding box colors.
nms_method: a string that can be 'nms' or 'soft-nms'.
show: a boolean to show frame pcessed.
Returns:
an image with prediction drawed
"""
compatible_outputs = [".bmp", ".dib", ".jpg", ".jpeg", ".jpe", ".png", ".webp"]
if not os.path.splitext(output_path)[1] in compatible_outputs:
raise ValueError("output_path only can be one of this: {}".format(compatible_outputs))
original_image = self.prepare_input(input_path)
image_data = self.pre_process(original_image, input_size)
pred_bbox = self.predict(model, image_data)
bboxes = self.postprocess_boxes(original_image, pred_bbox, input_size, score_threshold, iou_threshold, nms_method)
image = self.draw(original_image, bboxes, class_file_name, rectangle_colors)
if output_path != '': cv.imwrite(output_path, image)
if show: self.show(image, output_path)
return image
[docs] def show(self, image, output_path=''):
"""Get a an image, then save it and finally show it.
Args:
image: an image array.
output_path: if is an empty string, it won't be saved, but it is a path it save like an image.
"""
# Show the image
cv.imshow("predicted image", image)
# Load and hold the image
cv.waitKey(0)
# To close the window after the required kill value was provided
cv.destroyAllWindows()