Skip to main content
Tweeted twitter.com/StackCodeReview/status/1421168732072730627
Spelling fixes
Source Link
Toby Speight
  • 88.7k
  • 14
  • 104
  • 327

This is how the scripscript works step-by-step:

This is how the scrip works step-by-step:

This is how the script works step-by-step:

Source Link

Rainbow Trails Video Effect

This is an effect to augment juggling videos:

output

The goal of this effect is to add a rainbow trail to a video using a tracked set of points.

Link to source video

Link to source data

Link to complete Python script

Link to complete video

This is how the scrip works step-by-step:

Look at the relevant tracking points:

import numpy as np
import cv2
import pandas as pd
import numpy.polynomial.polynomial as poly
import math

# Read Source Data
cap = cv2.VideoCapture('/home/stephen/Desktop/ss5_id_412.MP4')
df = pd.read_csv('/home/stephen/Desktop/ss5_id_412.csv')
#Write Video Out
fourcc = cv2.VideoWriter_fourcc(*'XVID')
out = cv2.VideoWriter('/home/stephen/Desktop/parabola.avi', fourcc, 120.0, (480,848))

# Define x and y as the first two columns of the spreadsheet
x = df['0']
y = df['1']
# Filter the data to smooth it out
from scipy.signal import savgol_filter
x = savgol_filter(x, 7, 3)
y = savgol_filter(y, 7, 3)

# Start at frame number 0
frameNum = 0
# Define trail length
trail = 30
# Colors of the rainbow
rainbow = [(0,0,255), (0,127,255), (0,255,0), (255,0,0), (95,43,46), (255,0,139)]

def distance(a,b): return(math.sqrt((a[0]-b[0])**2+(a[1]-b[1])**2))
    
# Read a few frames of the video
# Define distance to calculate parabola from (in frames)
interval = 6
for i in range(interval):
    _,_ = cap.read()
    frameNum+= 1

# https://stackoverflow.com/questions/57065080/draw-perpendicular-line-of-fixed-length-at-a-point-of-another-line
def perpendicular(slope, target, dist):
    dy = math.sqrt(3**2/(slope**2+1))*dist
    dx = -slope*dy
    left = target[0] - dx, target[1] - dy
    right = target[0] + dx, target[1] + dy
    return left, right

# Create a list to store past points
history = []
completeHistory = []

### This is the first loop through the video
### In this loop the rainbow-trail data is collected
while True:
    # Read Image
    _, img = cap.read()
    try: _ = img.shape
    except: break

    # Define a small distance
    smallDistance = 0.2

    # Find relevant data
    x_values = x[frameNum-interval:frameNum+interval]
    y_values = y[frameNum-interval:frameNum+interval]

    # Graph x and y values
    for point in zip(x_values, y_values):
        point = tuple(np.array(point,int))
        #cv2.circle(img, point, 1, (255,0,255), 2)

tracking points

Find a parabola that fits those points:

# Fit the parabola
coefs = poly.polyfit(x_values,y_values,2)
# Draw the parabola
target = int(x[frameNum]), int(poly.polyval(x[frameNum], coefs))
xRange = np.arange(target[0]-9,target[0]+9,1)
yRange = poly.polyval(xRange, coefs)
rangePoints = zip(xRange, yRange)
for ppp in rangePoints:
    center = tuple(np.array(ppp,int))
    #cv2.circle(img, center, 1, 123, 2)
#print(yRange)

parabola gif

Find the tangent of the parabola at the ball's position:

# https://stackoverflow.com/questions/57065080/draw-perpendicular-line-of-fixed-length-at-a-point-of-another-line
def perpendicular(slope, target, dist):
    dy = math.sqrt(3**2/(slope**2+1))*dist
    dx = -slope*dy
    left = target[0] - dx, target[1] - dy
    right = target[0] + dx, target[1] + dy
    return left, right

# Calculate the points on either end of the tangent line
leftX, rightX = x[frameNum] - smallDistance, x[frameNum] + smallDistance
left = leftX, poly.polyval(leftX, coefs)
right = rightX, poly.polyval(rightX, coefs)
# Calculate the slope of the tangent line
slope = (left[1]-right[1])/(left[0]-right[0])
intercept = target[1] - slope*target[0]
# Draw line
leftPoint = target[0]-10, (target[0]-10)*slope + intercept
rightPoint = target[0]+10, (target[0]+10)*slope + intercept
leftPoint = tuple(np.array(leftPoint, int))
rightPoint= tuple(np.array(rightPoint, int))
cv2.line(img, leftPoint, rightPoint, (123,234,123), 3)

tangent line

Draw the trails:

# List to save data for this frame
frameHistory = []

# Find Perpendicular points
for i in range(len(rainbow)):
    color = rainbow[i]
    left, right = perpendicular(slope, target, i-3)
    left, right = tuple(np.array(left, int)), tuple(np.array(right, int))
    point = left
    #cv2.circle(img, left, 1, color, 1)
    frameHistory.append(point)
history.append(frameHistory)
completeHistory.append(frameHistory)

# Show the history too
a,b,c,d,e,f = zip(*history)
for pointList, color in zip([a,b,c,d,e,f], rainbow):
    for i in range(len(pointList)-1):
        pass
        cv2.line(img, pointList[i], pointList[i+1], color, 2)

# Pop the oldest frame off the history if the history is longer than 0.25 seconds
if len(history)>15:
    history.pop(0)

not smoothed

Smooth the trails (this is the final output):

#Smooth the data
smoothed = []
a,b,c,d,e,f = zip(*completeHistory)
for i in a,b,c,d,e,f:
    x, y = zip(*i)
    x = savgol_filter(x, 27, 3)
    y = savgol_filter(y, 21, 2)
    smoothed.append(list(zip(x,y)))


# Read Source Data
cap = cv2.VideoCapture('/home/stephen/Desktop/ss5_id_412.MP4')
fourcc = cv2.VideoWriter_fourcc(*'XVID')
out = cv2.VideoWriter('/home/stephen/Desktop/parabola.avi', fourcc, 120.0, (480,848))
frameNum = 0
### This is the second loop through the video
### In this loop the smoothed rinbow-trail points are diplayed
while True:
    # Read Image
    _, img = cap.read()
    if frameNum>21:
        for color, line in zip(rainbow, smoothed):
            pointList = line[frameNum-20:frameNum-5]
            for i in range(len(pointList)-1):
                a,b = tuple(np.array(pointList[i], int)), tuple(np.array(pointList[i+1], int))
                cv2.line(img, a,b, color, 2)

smoothed gif slow