blob: 121f165ea0bde251d7ef3af28fcf168d9d0b47f7 [file] [log] [blame]
# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Video processing tools to processing high speed camera videos.
This file contains various tools for video processing, starting with reading
video files, image filters and segmentation tools to detect objects in the
video as well as tools for visualizing results of a video analysis process.
"""
import os
from safetynet import List, Tuple, TypecheckMeta, typecheck
import numpy as np
import skimage.color
from .io import LoadImage, SaveImage
from .types import BinaryImage, Image, RGBAImage, RGBImage
@typecheck
def DebugView(title="", cmap_name="gray", **kwargs):
"""Display any number of images using matplotlib.
The images are provided using keyword arguments, with the argument
name being used as the image title.
Blocks until the window is closed.
:param str title: Title of window
:param str cmap_name: Name of matplotlib colormap to use. Defaults to gray.
:type kwargs: List[Image]
"""
import matplotlib.pyplot as plt
cmap = plt.get_cmap(cmap_name)
num_plots = float(len(kwargs))
num_rows = np.ceil(np.sqrt(num_plots))
num_cols = np.ceil(num_plots / num_rows)
figure = plt.figure()
for i, arg in enumerate(kwargs.items()):
image = arg[1]
if image is None:
continue
if image.dtype == np.bool:
image = arg[1].astype(np.float)
plt.subplot(num_rows, num_cols, i)
if image.dtype == np.float:
plt.imshow(arg[1], cmap=cmap, vmin=0, vmax=1)
else:
plt.imshow(arg[1], cmap=cmap)
plt.title(arg[0])
figure.canvas.set_window_title(title)
plt.show()
def ImageMatches(image, expected_image_path, force_update=False, show=False):
if force_update or not os.path.exists(expected_image_path):
SaveImage(expected_image_path, image)
if show:
DebugView(title=os.path.basename(expected_image_path),
created_image=image)
return True
expected_image = LoadImage(expected_image_path, gray=True)
actual_image = skimage.color.rgb2gray(image)
mse = np.mean((expected_image - actual_image) ** 2)
if mse > 0.01:
print "Image not matching expected image: %s" % expected_image_path
if show:
DebugView(actual_image=actual_image, expected_image=expected_image)
return False
return True
class Canvas(object):
"""Helper class for drawing contours, lines and masks and simple graphs"""
__metaclass__ = TypecheckMeta
PLOT_PADDING = 10
"""Padding at top and bottom of plots in pixels."""
# A collection of colors for drawing. Chosen to be clearly visible on black
# and white video streams. Colors are denoted in [blue, green, red, alpha].
WHITE = (1.0, 1.0, 1.0, 1.0)
BLUE = (1.0, 0.0, 0.0, 1.0)
GREEN = (0.0, 0.7, 0.0, 1.0)
RED = (0.0, 0.0, 1.0, 1.0)
YELLOW = (0.7, 0.7, 0.0, 1.0)
BLACK = (0.0, 0.0, 0.0, 1.0)
def __init__(self, buffer):
"""
:type buffer: RGBAImage
"""
self._buffer = buffer
@classmethod
def FromShape(cls, shape):
"""Create canvas from array shape.
:param Tuple[int, int] shape: (width, height) of canvas
:rtype Canvas
"""
return cls(np.zeros((shape[0], shape[1], 4)))
def Transformed(self, transform_function):
"""Return canvas with buffer transformed by function.
:rtype Canvas
"""
return Canvas(transform_function(self._buffer))
def BlendWithImage(self, image):
"""Blend canvas on top of image.
:type image: RGBImage
:rtype RGBImage
"""
weight = np.zeros(image.shape)
for i in (0, 1, 2):
weight[:, :, i] = self._buffer[:, :, 3]
weighted_image = image * (1 - weight)
weighted_buffer = self._buffer[:, :, 0:3] * weight
return weighted_image + weighted_buffer
def PlotProfile(self, color, profile):
"""Plot normalized function with values 0..1
This plots the profile along the image, thus the length of the profile array
should be exactly the same as the width of the canvas.
The y-value is scaled to stretch the full height of the canvas.
:type color: Tuple[float, float, float, float]
:type profile: np.ndarray
"""
assert(len(profile) == self._buffer.shape[1])
profile[profile > 1] = 1
profile[profile < 0] = 0
height = self._buffer.shape[0] - self.PLOT_PADDING * 2
cols = (height - (profile * height)).astype(np.int) + self.PLOT_PADDING
rows = range(len(profile))
self._buffer[cols, rows] = color
def DrawVLine(self, color, x):
"""Draw vertical line at x-location.
:type color: Tuple[float, float, float, float]
:type x: int
"""
for i in (0, 1, 2, 3):
self._buffer[:, x, i] = color[i]
def DrawHLine(self, color, y):
"""Draw horizontal line at x-location.
:type color: Tuple[float, float, float, float]
:type x: int
"""
for i in (0, 1, 2, 3):
self._buffer[y, :, i] = color[i]
def DrawMask(self, color, mask):
"""Fill area of mask with color.
:type color: Tuple[float, float, float, float]
:type mask: BinaryImage
"""
assert(mask.shape[0] == self._buffer.shape[0])
assert(mask.shape[1] == self._buffer.shape[1])
self._buffer[mask] = color
def DrawImage(self, image, alpha=1.0):
assert(image.shape[0] == self._buffer.shape[0])
assert(image.shape[1] == self._buffer.shape[1])
for i in range(3):
self._buffer[:,:,i] = image
self._buffer[:,:,3] = alpha