Ask Your Question

Revision history [back]

click to hide/show revision 1
initial version

Here's the code I used to generate the image that I added to the original question. I modified it just a little bit to map radius to value instead of saturation to be in closer correspondence with the optical flow output. Admittedly, HSV wheels generally assign radius to saturation. My goal here is to provide the clearest possible data visualization of optical flow. Of course, most of the responses to my question seem to indicate that no one else considers this to be very important, so whatever. Just trying to help.

from PIL import Image, ImageDraw
import numpy as np
import cv2

wh = 200
wh_2 = wh / 2

# Generate a standard HSV color wheel
hsv_color_wheel = np.zeros((wh, wh, 3))
for y in range(wh):
    yy = y - wh_2
    for x in range(wh):
        xx = x - wh_2
        radius = math.sqrt(xx**2 + yy**2)
        if radius < wh_2:
            angle = math.atan2(xx, yy)
            angle = 360 - (angle * 180 / np.pi)
#             hsv_color_wheel[x, y] = (angle, radius / wh_2, 255) # Radius as saturation, which is a more conventional HSV wheel
            hsv_color_wheel[x, y] = (angle, 1, radius / wh_2 * 255) # Radius as value, which maps optical flow's magnitude

hsv_color_wheel = np.asarray(hsv_color_wheel, dtype=np.float32)
hsv_color_wheel = cv2.cvtColor(hsv_color_wheel, cv2.COLOR_HSV2RGB)

hsv_color_wheel_flat = hsv_color_wheel.reshape(wh * wh, 3)
hsv_color_wheel_flat_tuple = [tuple(v) for v in hsv_color_wheel_flat]

hsv_color_wheel_img = Image.new("RGB", (wh, wh))
hsv_color_wheel_img.putdata(hsv_color_wheel_flat_tuple)

# Generate some shifted boxes to demonstrate optical flow
box_sz = wh / 5
box_sz_2 = 2 * wh / 5
box_sz_3 = 3 * wh / 5
dim = (wh, wh)
img1 = Image.new("L", dim)
img2 = Image.new("L", dim)

shift = 5
draw = ImageDraw.Draw(img1)
draw.ellipse((wh/5+10, wh/5+10, 4*wh/5-10, 4*wh/5-10), 255)
draw.ellipse((wh/5+20, wh/5+20, 4*wh/5-20, 4*wh/5-20), 0)
draw = ImageDraw.Draw(img2)
draw.ellipse((wh/5+10 - shift, wh/5+10 - shift, 4*wh/5-10 + shift, 4*wh/5-10 + shift), 255)
draw.ellipse((wh/5+20 - shift, wh/5+20 - shift, 4*wh/5-20 + shift, 4*wh/5-20 + shift), 0)

img1_arr = np.array(img1)
img2_arr = np.array(img2)

# Run optical flow and visualize two possible ways
for method in [1, 2]:
    flow = cv2.calcOpticalFlowFarneback(img1_arr, img2_arr, None, 0.5, 3, 40, 3, 5, 1.2, 0)

    # Convert from cartesian to polar
    mag, ang = cv2.cartToPolar(flow[..., 0], flow[..., 1])

    # Create an HSV image
    hsv = np.zeros((img1.size[0], img1.size[1], 3))
    hsv[:,:,1] = 1  # Full saturation
    # Set hue from the flow direction
    if method == 1:
        hsv[:,:,0] = ang * (180 / np.pi / 2)
    else:
        hsv[:,:,0] = 360 - (ang * (180 / np.pi))
    # Set value from the flow magnitude
    hsv[:,:,2] = cv2.normalize(mag, None, 0, 255, cv2.NORM_MINMAX)
    # Convert HSV to int32's
    hsv = np.asarray(hsv, dtype=np.float32)
    rgb_flow = cv2.cvtColor(hsv,cv2.COLOR_HSV2RGB)

    # Convert to an image
    rgb_flow_flat = rgb_flow.reshape(rgb_flow.shape[0] * rgb_flow.shape[1], 3)
    rgb_flow_flat_tuple = [tuple(v) for v in rgb_flow_flat]
    flow_img = Image.new("RGB", img1.size)
    flow_img.putdata(rgb_flow_flat_tuple)

    analysis_img = Image.new("RGB", (img1.size[0] * 4, img2.size[1]))
    analysis_img.paste(img1, (0, 0))
    analysis_img.paste(img2, (img1.size[0], 0))
    analysis_img.paste(flow_img, (img1.size[0] * 2, 0))
    analysis_img.paste(hsv_color_wheel_img, (img1.size[0] * 3, 0))

    print("Original visualization method:" if method == 1 else "Proposed visualization method:")
    display(analysis_img)