Plot over video in Matlab

Imagine you want to plot something on top of a video/images in Matlab and save it back to video. One example will be a video tracking app. You do the tracking and want to save the full video results for later presentation. If you Google for it, then the most likely the answer will be to use hold:

I = imread('cameraman.tif');
figure, imshow(I, [])
hold on;
plot(118, 61, 'or', 'markersize', 35, 'markerfacecolor', 'r');

Looks good you think. Go ahead and make it in a loop to create video. If you do and you have at least a 5 minutes video, then you'd just freed your afternoon. This is about how long it will take.

For the inpatient

The summary of this post is the reference to one of Matlab's functions. insertShape available from R2014a. Have a look. Below I just explain why you should really use insertShape.

Moving on

Let's measure the time of Matlab's code for a fun. I have a test video, which is 1920x1090 MPEG-4 H264 video. Let's make a new one with a constant circle.

source = VideoReader('test.mp4');
dst = VideoWriter('test-naive', 'MPEG-4');
dst.FrameRate = source.FrameRate;

open(dst)

fprintf('Started on %s\n', datestr(now));
tic;
try
    while hasFrame(source)
        vidFrame = readFrame(source);
        imshow(vidFrame);
        hold on;
        plot(100, 100, 'or', 'MarkerSize', 50, 'MarkerFaceColor', 'r');
        hold off;

        writeVideo(dst, getframe(gcf));
    end
catch ME
    % no error handling, just display the exception
    ME
end
elapsedTime = toc;
fprintf('Finished on %s\n', datestr(now));
fprintf('Tic/toc measure is %f sec\n', elapsedTime);

clear readerObj;
close(dst);

And the results are

Started on 16-Apr-2018 19:42:17
Warning: Image is too big to fit on screen; displaying at 67% 
> In images.internal.initSize (line 71)
  In imshow (line 336)
  In videoNaive (line 18) 
Finished on 16-Apr-2018 19:51:31
Tic/toc measure is 554.421488 sec

I will get to the warning later. Almost 10 minutes! OK. Not much for a free afternoon, but perhaps still quite some time for such a tiny job. I run that test on Windows 10 x64 machine with both integrated and dedicated graphics card (used simultaneously) and in Matlab R2018a. Not that the specs matter that much as I did tests on different machines and the results were similar.

To illustrate how fast can it be, let's use Python to do the same thing. I won't go into details about setting your Python environment up. You will need working openCV and numPy in order to run the example code.

## Visualize tracker
import numpy as np
import cv2
import os
from datetime import datetime

rawVideo = 'test.mp4'
outVideo = 'test_python.avi'
markerSize = 9
frameCounter = -1

cap = cv2.VideoCapture(rawVideo)
fourcc = cv2.VideoWriter_fourcc(*'X264')
out = cv2.VideoWriter(outVideo, fourcc, 25.0, (1920,1080))

if not cap.isOpened():
    print('Cap is not opened')

print("Started at {0}".format(datetime.now().time()))

while (cap.isOpened()):
    ret, frame = cap.read()
    frameCounter += 1
    if ret == False:
        break

    x = int(100)
    y = int(100)
    cv2.circle(frame, (x, y), markerSize, (0, 0, 240), -1)

    out.write(frame)


print("Ended at {0}".format(datetime.now().time()))
cap.release()
out.release()
cv2.destroyAllWindows()
print('done')

And the output is

Started at 20:20.12.771219
Ended at 20:21:43.550657

WHAT?! One and a half minute it is!

By observing the code you can see the obvious difference: Python doesn't display the image to user, whereas Matlab displays the image, plots on top, grabs the frame from what has been displayed, writes the frame. Of course it is inefficient. Matlab's Computer Vision Systems Toolbox has a function to deal with such situations. It's named insertShape and it first appeared in R2014a.

Let's see how it looks with that function.

source = VideoReader('test.mp4');
dst = VideoWriter('test-insertShape', 'MPEG-4');
dst.FrameRate = source.FrameRate;

open(dst)
curAxes = axes;
hold on;
set(curAxes, 'nextplot', 'replacechildren');

fprintf('Started on %s\n', datestr(now));
tic;
try
    while hasFrame(source)
        vidFrame = readFrame(source);
        vidFrame = insertShape(vidFrame, 'FilledCircle', [100 100 50], 'Color', 'red', 'Opacity', 1);
        writeVideo(dst, vidFrame);
    end
catch ME
    % no error handling, just display the exception
    ME
end
elapsedTime = toc;
fprintf('Finished on %s\n', datestr(now));
fprintf('Tic/toc measure is %f sec\n', elapsedTime);

clear readerObj;
close(dst);

And the results are

Started on 16-Apr-2018 20:33:14
Finished on 16-Apr-2018 20:36:12
Tic/toc measure is 177.690809 sec

That's the way to go! 3 minutes, which is still longer than Python version, but obviously much better than the original hold on version.

Bonus. Remember that warning from the first version of Matlab's code? If you watch the video, you will see that it is not your original 1920x1080. In my case, it is 1454x814 with space around the frame (the regular Matlab image saving artifacts). However, code with insertShape produced a video as we would expect it to do.