Skip to content

Flow

Flow

Flow(parent, filename, color, has_audio)

Class container for the sparse and dense optical flow processes.

Initializes the Flow class.

Parameters:

Name Type Description Default
parent MgVideo

the parent MgVideo.

required
filename str

Path to the input video file. Passed by parent MgVideo.

required
color bool

Set class methods in color or grayscale mode. Passed by parent MgVideo.

required
has_audio bool

Indicates whether source video file has an audio track. Passed by parent MgVideo.

required
Source code in musicalgestures/_flow.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def __init__(self, parent, filename, color, has_audio):
    """
    Initializes the Flow class.

    Args:
        parent (MgVideo): the parent MgVideo.
        filename (str): Path to the input video file. Passed by parent MgVideo.
        color (bool): Set class methods in color or grayscale mode. Passed by parent MgVideo.
        has_audio (bool): Indicates whether source video file has an audio track. Passed by parent MgVideo.
    """
    self.parent = weakref.ref(parent)
    self.filename = filename
    self.color = color
    self.has_audio = has_audio

dense

dense(filename=None, pyr_scale=0.5, levels=3, winsize=15, iterations=3, poly_n=5, poly_sigma=1.2, flags=0, velocity=False, distance=None, timestep=1, move_step=1, angle_of_view=0, scaledown=1, skip_empty=False, use_gpu=False, convert=True, target_name=None, overwrite=True)

Renders a dense optical flow video of the input video file using cv2.calcOpticalFlowFarneback(). The description of the matching parameters are taken from the cv2 documentation.

Parameters:

Name Type Description Default
filename str

Path to the input video file. If None the video file of the MgVideo is used. Defaults to None.

None
pyr_scale float

Specifies the image scale (<1) to build pyramids for each image. pyr_scale=0.5 means a classical pyramid, where each next layer is twice smaller than the previous one. Defaults to 0.5.

0.5
levels int

The number of pyramid layers including the initial image. levels=1 means that no extra layers are created and only the original images are used. Defaults to 3.

3
winsize int

The averaging window size. Larger values increase the algorithm robustness to image noise and give more chances for fast motion detection, but yield more blurred motion field. Defaults to 15.

15
iterations int

The number of iterations the algorithm does at each pyramid level. Defaults to 3.

3
poly_n int

The size of the pixel neighborhood used to find polynomial expansion in each pixel. Larger values mean that the image will be approximated with smoother surfaces, yielding more robust algorithm and more blurred motion field, typically poly_n =5 or 7. Defaults to 5.

5
poly_sigma float

The standard deviation of the Gaussian that is used to smooth derivatives used as a basis for the polynomial expansion. For poly_n=5, you can set poly_sigma=1.1, for poly_n=7, a good value would be poly_sigma=1.5. Defaults to 1.2.

1.2
flags int

Operation flags that can be a combination of the following: - OPTFLOW_USE_INITIAL_FLOW uses the input flow as an initial flow approximation. - OPTFLOW_FARNEBACK_GAUSSIAN uses the Gaussian \f$\texttt{winsize}\times\texttt{winsize}\f$ filter instead of a box filter of the same size for optical flow estimation. Usually, this option gives z more accurate flow than with a box filter, at the cost of lower speed. Normally, winsize for a Gaussian window should be set to a larger value to achieve the same level of robustness. Defaults to 0.

0
velocity bool

Whether to compute optical flow velocity or not. Defaults to False.

False
distance int

Distance in meters to image (focal length) for returning flow in meters per second. Defaults to None.

None
timestep int

Time step in seconds for returning flow in meters per second. Defaults to 1.

1
move_step int

step size in pixels for sampling the flow image. Defaults to 1.

1
angle_of_view int

angle of view of camera, for reporting flow in meters per second. Defaults to 0.

0
scaledown int

factor to scaledown frame size of the video. Defaults to 1.

1
skip_empty bool

If True, repeats previous frame in the output when encounters an empty frame. Defaults to False.

False
use_gpu bool

Whether to attempt GPU (CUDA) acceleration using cv2.cuda.FarnebackOpticalFlow. When True, falls back to CPU automatically if CUDA is unavailable or the required OpenCV CUDA modules are not installed. When False, CPU processing is used unconditionally. Defaults to False.

False
target_name str

Target output name for the video. Defaults to None (which assumes that the input filename with the suffix "_flow_dense" should be used).

None
overwrite bool

Whether to allow overwriting existing files or to automatically increment target filenames to avoid overwriting. Defaults to True.

True

Returns:

Name Type Description
MgVideo 'musicalgestures.MgVideo'

A new MgVideo pointing to the output video file.

Source code in musicalgestures/_flow.py
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
def dense(
        self,
        filename: str | None = None,
        pyr_scale: float = 0.5,
        levels: int = 3,
        winsize: int = 15,
        iterations: int = 3,
        poly_n: int = 5,
        poly_sigma: float = 1.2,
        flags: int = 0,
        velocity: bool = False,
        distance: int | None = None,
        timestep: int = 1,
        move_step: int = 1,
        angle_of_view: int = 0,
        scaledown: int = 1,
        skip_empty: bool = False,
        use_gpu: bool = False,
        convert: bool = True,
        target_name: str | None = None,
        overwrite: bool = True) -> "musicalgestures.MgVideo":
    """
    Renders a dense optical flow video of the input video file using `cv2.calcOpticalFlowFarneback()`. The description of the matching parameters are taken from the cv2 documentation.

    Args:
        filename (str, optional): Path to the input video file. If None the video file of the MgVideo is used. Defaults to None.
        pyr_scale (float, optional): Specifies the image scale (<1) to build pyramids for each image. `pyr_scale=0.5` means a classical pyramid, where each next layer is twice smaller than the previous one. Defaults to 0.5.
        levels (int, optional): The number of pyramid layers including the initial image. `levels=1` means that no extra layers are created and only the original images are used. Defaults to 3.
        winsize (int, optional): The averaging window size. Larger values increase the algorithm robustness to image noise and give more chances for fast motion detection, but yield more blurred motion field. Defaults to 15.
        iterations (int, optional): The number of iterations the algorithm does at each pyramid level. Defaults to 3.
        poly_n (int, optional): The size of the pixel neighborhood used to find polynomial expansion in each pixel. Larger values mean that the image will be approximated with smoother surfaces, yielding more robust algorithm and more blurred motion field, typically poly_n =5 or 7. Defaults to 5.
        poly_sigma (float, optional): The standard deviation of the Gaussian that is used to smooth derivatives used as a basis for the polynomial expansion. For `poly_n=5`, you can set `poly_sigma=1.1`, for `poly_n=7`, a good value would be `poly_sigma=1.5`. Defaults to 1.2.
        flags (int, optional): Operation flags that can be a combination of the following: - **OPTFLOW_USE_INITIAL_FLOW** uses the input flow as an initial flow approximation. - **OPTFLOW_FARNEBACK_GAUSSIAN** uses the Gaussian \\f$\\texttt{winsize}\\times\\texttt{winsize}\\f$ filter instead of a box filter of the same size for optical flow estimation. Usually, this option gives z more accurate flow than with a box filter, at the cost of lower speed. Normally, `winsize` for a Gaussian window should be set to a larger value to achieve the same level of robustness. Defaults to 0.
        velocity (bool, optional): Whether to compute optical flow velocity or not. Defaults to False.
        distance (int, optional): Distance in meters to image (focal length) for returning flow in meters per second. Defaults to None.
        timestep (int, optional): Time step in seconds for returning flow in meters per second. Defaults to 1.
        move_step (int, optional): step size in pixels for sampling the flow image. Defaults to 1.
        angle_of_view (int, optional): angle of view of camera, for reporting flow in meters per second. Defaults to 0.
        scaledown (int, optional): factor to scaledown frame size of the video. Defaults to 1.
        skip_empty (bool, optional): If True, repeats previous frame in the output when encounters an empty frame. Defaults to False.
        use_gpu (bool, optional): Whether to attempt GPU (CUDA) acceleration using `cv2.cuda.FarnebackOpticalFlow`. When `True`, falls back to CPU automatically if CUDA is unavailable or the required OpenCV CUDA modules are not installed. When `False`, CPU processing is used unconditionally. Defaults to False.
        target_name (str, optional): Target output name for the video. Defaults to None (which assumes that the input filename with the suffix "_flow_dense" should be used).
        overwrite (bool, optional): Whether to allow overwriting existing files or to automatically increment target filenames to avoid overwriting. Defaults to True.

    Returns:
        MgVideo: A new MgVideo pointing to the output video file.
    """

    if filename is None:
        filename = self.filename

    of, fex = os.path.splitext(filename)

    # Convert to avi if the input is not avi - necesarry for cv2 compatibility on all platforms
    if convert and fex != '.avi':
        # first check if there already is a converted version, if not create one and register it to the parent self
        if "as_avi" not in self.parent().__dict__.keys():
            file_as_avi = convert_to_avi(of + fex, overwrite=overwrite)
            # register it as the avi version for the file
            self.parent().as_avi = musicalgestures.MgVideo(file_as_avi)
        # point of and fex to the avi version
        of, fex = self.parent().as_avi.of, self.parent().as_avi.fex
        filename = self.parent().as_avi.filename

    vidcap = cv2.VideoCapture(filename)

    fps = int(vidcap.get(cv2.CAP_PROP_FPS))
    width = int(vidcap.get(cv2.CAP_PROP_FRAME_WIDTH))
    height = int(vidcap.get(cv2.CAP_PROP_FRAME_HEIGHT))
    length = int(vidcap.get(cv2.CAP_PROP_FRAME_COUNT))

    size = (int(width/scaledown), int(height/scaledown))

    # Determine whether to use GPU-accelerated Farneback optical flow
    _use_gpu = False
    farneback_gpu = None
    if use_gpu:
        if not hasattr(cv2, 'cuda') or not hasattr(cv2.cuda, 'FarnebackOpticalFlow'):
            print('cv2.cuda.FarnebackOpticalFlow is unavailable (requires opencv-contrib built with CUDA). Switching to CPU for dense optical flow.')
        elif get_cuda_device_count() <= 0:
            print('OpenCV CUDA backend is unavailable. Switching to CPU for dense optical flow.\n  ' + cuda_unavailable_reason())
        else:
            _use_gpu = True
            farneback_gpu = cv2.cuda.FarnebackOpticalFlow.create(
                numLevels=levels,
                pyrScale=pyr_scale,
                fastPyramids=False,
                winSize=winsize,
                numIters=iterations,
                polyN=poly_n,
                polySigma=poly_sigma,
                flags=flags,
            )

    if velocity:
        pb = MgProgressbar(total=length, prefix='Rendering dense optical flow velocity:')

    else:
        pb = MgProgressbar(total=length, prefix='Rendering dense optical flow video:')

        target_name = resolve_filename(of, '_flow_dense' + fex, target_name, overwrite)

        cmd = ['ffmpeg', '-y', '-s', '{}x{}'.format(width, height),
               '-r', str(fps), '-f', 'rawvideo', '-pix_fmt', 'bgr24', '-vcodec', 'rawvideo',
               '-i', '-', '-vcodec', 'mjpeg', '-q:v', '3', target_name]
        out = ffmpeg_cmd(cmd, total_time=length, pipe='write')

    ret, frame1 = vidcap.read()
    prev_frame = cv2.cvtColor(cv2.resize(frame1, size), cv2.COLOR_BGR2GRAY)

    if _use_gpu:
        gpu_prev_frame = cv2.cuda_GpuMat()
        gpu_next_frame = cv2.cuda_GpuMat()
        gpu_prev_frame.upload(prev_frame)

    prev_rgb = None
    hsv = np.zeros_like(frame1)
    hsv[..., 1] = 255

    ii = 0
    # Create two lists for storing optical flow velocity values
    xvel, yvel = [], []

    while(vidcap.isOpened()):
        ret, frame2 = vidcap.read()
        xsum, ysum = 0, 0

        if ret == True:
            next_frame = cv2.cvtColor(cv2.resize(frame2, size), cv2.COLOR_BGR2GRAY)

            if _use_gpu:
                gpu_next_frame.upload(next_frame)
                gpu_flow_result = farneback_gpu.calc(gpu_prev_frame, gpu_next_frame, None)
                flow = gpu_flow_result.download()
                # Swap references so gpu_next_frame becomes gpu_prev_frame for the
                # next iteration without allocating a new GpuMat object each frame
                gpu_prev_frame, gpu_next_frame = gpu_next_frame, gpu_prev_frame
            else:
                flow = cv2.calcOpticalFlowFarneback(prev_frame, next_frame, None, pyr_scale, levels, winsize, iterations, poly_n, poly_sigma, flags)

            if velocity:
                # Cumulative sum of optical flow vectors        
                for y in range(0, flow.shape[0]):
                    for x in range(0, flow.shape[1]):
                        fx, fy = flow[y, x]
                        xsum += fx
                        ysum += fy

                # Compute average velocity of pixels by dividing the cumulative sum of optical flow vectors by timesteps        
                xvel.append(self.get_velocity(flow, xsum, flow.shape[1], distance, timestep, move_step, angle_of_view))
                yvel.append(self.get_velocity(flow, ysum, flow.shape[0], distance, timestep, move_step, angle_of_view))

            else:
                mag, ang = cv2.cartToPolar(flow[..., 0], flow[..., 1])
                hsv[..., 0] = ang*180/np.pi/2
                hsv[..., 2] = cv2.normalize(mag, None, 0, 255, cv2.NORM_MINMAX)
                rgb = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)

                if skip_empty:
                    if np.sum(rgb) > 0:
                        out.stdin.write(rgb.astype(np.uint8))
                    else:
                        if ii == 0:
                            out.stdin.write(rgb.astype(np.uint8))
                        else:
                            out.stdin.write(prev_rgb.astype(np.uint8))
                else:
                    out.stdin.write(rgb.astype(np.uint8))

                if skip_empty:
                    if np.sum(rgb) > 0 or ii == 0:
                        prev_rgb = rgb
                else:
                    prev_rgb = rgb

            prev_frame = next_frame

        else:
            pb.progress(length)
            break

        pb.progress(ii)
        ii += 1

    if velocity:

        fig, ax = plt.subplots(figsize=(12, 4), dpi=300)

        # make sure background is white
        fig.patch.set_facecolor('white')
        fig.patch.set_alpha(1)

        # add title
        title = 'Dense optical flow velocity: ' + os.path.basename(of + fex)
        fig.suptitle(title, fontsize=16)

        time = np.linspace(0, len(xvel)/fps, len(xvel))
        acceleration = self.get_acceleration(xvel, fps)
        from scipy.stats import entropy   # lazy import: keeps scipy.stats out of startup
        acceleration_entropy = entropy(acceleration)

        ax.plot(time, xvel, label=f'Average acceleration: {round(np.mean(acceleration),3)} m/s\nEntropy of acceleration: {round(acceleration_entropy,3)}')
        ax.set_xlabel('Time [Seconds]')
        ax.set_ylabel('Velocity [Meters]')
        ax.margins(x=0)
        ax.legend(handlelength=0, handletextpad=0, fancybox=True)

        fig.tight_layout()

        target_name = resolve_filename(of, '_velocity.png', target_name, overwrite)

        plt.savefig(target_name, format='png', transparent=False)
        plt.close()

        # create MgFigure
        data = {
            "FPS": fps,
            "path": of,
            "xvel": xvel,
            "yvel": yvel,
        }

        mgf = MgFigure(
            figure=fig,
            figure_type='video.velocity',
            data=data,
            layers=None,
            image=target_name)

        return mgf

    else:
        out.stdin.close()
        out.wait()
        destination_video = target_name

        if self.has_audio:
            source_audio = extract_wav(of + fex)
            embed_audio_in_video(source_audio, destination_video)
            os.remove(source_audio)

        # save result at flow_dense_video at parent MgVideo
        self.parent().flow_dense_video = musicalgestures.MgVideo(
            destination_video, color=self.color, returned_by_process=True)

        return self.parent().flow_dense_video

sparse

sparse(filename=None, corner_max_corners=100, corner_quality_level=0.3, corner_min_distance=7, corner_block_size=7, of_win_size=(15, 15), of_max_level=2, of_criteria=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03), use_gpu=False, convert=True, target_name=None, overwrite=True)

Renders a sparse optical flow video of the input video file using cv2.calcOpticalFlowPyrLK(). cv2.goodFeaturesToTrack() is used for the corner estimation. The description of the matching parameters are taken from the cv2 documentation.

Parameters:

Name Type Description Default
filename str

Path to the input video file. If None, the video file of the MgVideo is used. Defaults to None.

None
corner_max_corners int

Maximum number of corners to return. If there are more corners than are found, the strongest of them is returned. maxCorners <= 0 implies that no limit on the maximum is set and all detected corners are returned. Defaults to 100.

100
corner_quality_level float

Parameter characterizing the minimal accepted quality of image corners. The parameter value is multiplied by the best corner quality measure, which is the minimal eigenvalue (see cornerMinEigenVal in cv2 docs) or the Harris function response (see cornerHarris in cv2 docs). The corners with the quality measure less than the product are rejected. For example, if the best corner has the quality measure = 1500, and the qualityLevel=0.01, then all the corners with the quality measure less than 15 are rejected. Defaults to 0.3.

0.3
corner_min_distance int

Minimum possible Euclidean distance between the returned corners. Defaults to 7.

7
corner_block_size int

Size of an average block for computing a derivative covariation matrix over each pixel neighborhood. See cornerEigenValsAndVecs in cv2 docs. Defaults to 7.

7
of_win_size tuple

Size of the search window at each pyramid level. Defaults to (15, 15).

(15, 15)
of_max_level int

0-based maximal pyramid level number. If set to 0, pyramids are not used (single level), if set to 1, two levels are used, and so on. If pyramids are passed to input then the algorithm will use as many levels as pyramids have but no more than maxLevel. Defaults to 2.

2
of_criteria tuple

Specifies the termination criteria of the iterative search algorithm (after the specified maximum number of iterations criteria.maxCount or when the search window moves by less than criteria.epsilon). Defaults to (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03).

(TERM_CRITERIA_EPS | TERM_CRITERIA_COUNT, 10, 0.03)
use_gpu bool

Whether to attempt GPU (CUDA) acceleration using cv2.cuda.SparsePyrLKOpticalFlow. When True, falls back to CPU automatically if CUDA is unavailable or the required OpenCV CUDA modules are not installed. When False, CPU processing is used unconditionally. Defaults to False.

False
target_name str

Target output name for the video. Defaults to None (which assumes that the input filename with the suffix "_flow_sparse" should be used).

None
overwrite bool

Whether to allow overwriting existing files or to automatically increment target filenames to avoid overwriting. Defaults to True.

True

Returns:

Name Type Description
MgVideo 'musicalgestures.MgVideo'

A new MgVideo pointing to the output video file.

Source code in musicalgestures/_flow.py
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
def sparse(
        self,
        filename: str | None = None,
        corner_max_corners: int = 100,
        corner_quality_level: float = 0.3,
        corner_min_distance: int = 7,
        corner_block_size: int = 7,
        of_win_size: tuple = (15, 15),
        of_max_level: int = 2,
        of_criteria: tuple = (cv2.TERM_CRITERIA_EPS |
                     cv2.TERM_CRITERIA_COUNT, 10, 0.03),
        use_gpu: bool = False,
        convert: bool = True,
        target_name: str | None = None,
        overwrite: bool = True) -> "musicalgestures.MgVideo":
    """
    Renders a sparse optical flow video of the input video file using `cv2.calcOpticalFlowPyrLK()`. `cv2.goodFeaturesToTrack()` is used for the corner estimation. The description of the matching parameters are taken from the cv2 documentation.

    Args:
        filename (str, optional): Path to the input video file. If None, the video file of the MgVideo is used. Defaults to None.
        corner_max_corners (int, optional): Maximum number of corners to return. If there are more corners than are found, the strongest of them is returned. `maxCorners <= 0` implies that no limit on the maximum is set and all detected corners are returned. Defaults to 100.
        corner_quality_level (float, optional): Parameter characterizing the minimal accepted quality of image corners. The parameter value is multiplied by the best corner quality measure, which is the minimal eigenvalue (see cornerMinEigenVal in cv2 docs) or the Harris function response (see cornerHarris in cv2 docs). The corners with the quality measure less than the product are rejected. For example, if the best corner has the quality measure = 1500, and the qualityLevel=0.01, then all the corners with the quality measure less than 15 are rejected. Defaults to 0.3.
        corner_min_distance (int, optional): Minimum possible Euclidean distance between the returned corners. Defaults to 7.
        corner_block_size (int, optional): Size of an average block for computing a derivative covariation matrix over each pixel neighborhood. See cornerEigenValsAndVecs in cv2 docs. Defaults to 7.
        of_win_size (tuple, optional): Size of the search window at each pyramid level. Defaults to (15, 15).
        of_max_level (int, optional): 0-based maximal pyramid level number. If set to 0, pyramids are not used (single level), if set to 1, two levels are used, and so on. If pyramids are passed to input then the algorithm will use as many levels as pyramids have but no more than `maxLevel`. Defaults to 2.
        of_criteria (tuple, optional): Specifies the termination criteria of the iterative search algorithm (after the specified maximum number of iterations criteria.maxCount or when the search window moves by less than criteria.epsilon). Defaults to (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03).
        use_gpu (bool, optional): Whether to attempt GPU (CUDA) acceleration using `cv2.cuda.SparsePyrLKOpticalFlow`. When `True`, falls back to CPU automatically if CUDA is unavailable or the required OpenCV CUDA modules are not installed. When `False`, CPU processing is used unconditionally. Defaults to False.
        target_name (str, optional): Target output name for the video. Defaults to None (which assumes that the input filename with the suffix "_flow_sparse" should be used).
        overwrite (bool, optional): Whether to allow overwriting existing files or to automatically increment target filenames to avoid overwriting. Defaults to True.

    Returns:
        MgVideo: A new MgVideo pointing to the output video file.
    """

    if filename is None:
        filename = self.filename

    of, fex = os.path.splitext(filename)

    # Convert to avi if the input is not avi - necesarry for cv2 compatibility on all platforms
    if convert and fex != '.avi':
        # first check if there already is a converted version, if not create one and register it to the parent self
        if "as_avi" not in self.parent().__dict__.keys():
            file_as_avi = convert_to_avi(of + fex, overwrite=overwrite)
            # register it as the avi version for the file
            self.parent().as_avi = musicalgestures.MgVideo(file_as_avi)
        # point of and fex to the avi version
        of, fex = self.parent().as_avi.of, self.parent().as_avi.fex
        filename = self.parent().as_avi.filename

    vidcap = cv2.VideoCapture(filename)
    ret, frame = vidcap.read()

    fps = int(vidcap.get(cv2.CAP_PROP_FPS))
    width = int(vidcap.get(cv2.CAP_PROP_FRAME_WIDTH))
    height = int(vidcap.get(cv2.CAP_PROP_FRAME_HEIGHT))
    length = int(vidcap.get(cv2.CAP_PROP_FRAME_COUNT))

    # Determine whether to use GPU-accelerated sparse optical flow
    _use_gpu = False
    lk_gpu = None
    if use_gpu:
        if not hasattr(cv2, 'cuda') or not hasattr(cv2.cuda, 'SparsePyrLKOpticalFlow'):
            print('cv2.cuda.SparsePyrLKOpticalFlow is unavailable (requires opencv-contrib built with CUDA). Switching to CPU for sparse optical flow.')
        elif get_cuda_device_count() <= 0:
            print('OpenCV CUDA backend is unavailable. Switching to CPU for sparse optical flow.\n  ' + cuda_unavailable_reason())
        else:
            _use_gpu = True
            iters = of_criteria[1] if len(of_criteria) > 1 else 10
            lk_gpu = cv2.cuda.SparsePyrLKOpticalFlow.create(
                winSize=of_win_size,
                maxLevel=of_max_level,
                iters=iters,
            )

    pb = MgProgressbar(
        total=length, prefix='Rendering sparse optical flow video:')

    target_name = resolve_filename(of, '_flow_sparse' + fex, target_name, overwrite)

    cmd = ['ffmpeg', '-y', '-s', '{}x{}'.format(width, height),
           '-r', str(fps), '-f', 'rawvideo', '-pix_fmt', 'bgr24', '-vcodec', 'rawvideo',
           '-i', '-', '-vcodec', 'mjpeg', '-q:v', '3', target_name]
    out = ffmpeg_cmd(cmd, total_time=length, pipe='write')

    # params for ShiTomasi corner detection
    feature_params = dict(maxCorners=corner_max_corners,
                          qualityLevel=corner_quality_level,
                          minDistance=corner_min_distance,
                          blockSize=corner_block_size)

    # Parameters for lucas kanade optical flow
    lk_params = dict(winSize=of_win_size,
                     maxLevel=of_max_level,
                     criteria=of_criteria)

    # Create some random colors
    color = np.random.randint(0, 255, (100, 3))

    # Take first frame and find corners in it
    ret, old_frame = vidcap.read()
    old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)
    p0 = cv2.goodFeaturesToTrack(old_gray, mask=None, **feature_params)

    if _use_gpu:
        gpu_old_gray = cv2.cuda_GpuMat()
        gpu_frame_gray = cv2.cuda_GpuMat()
        gpu_old_gray.upload(old_gray)
        gpu_p0 = cv2.cuda_GpuMat()
        # CUDA SparsePyrLKOpticalFlow needs the points as a 1xN CV_32FC2 row vector
        gpu_p0.upload(p0.reshape(1, -1, 2))

    # Create a mask image for drawing purposes
    mask = np.zeros_like(old_frame)

    ii = 0

    while(vidcap.isOpened()):
        ret, frame = vidcap.read()
        if ret == True:
            frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

            # calculate optical flow
            if _use_gpu:
                gpu_frame_gray.upload(frame_gray)
                gpu_p1, gpu_st, _gpu_err = lk_gpu.calc(gpu_old_gray, gpu_frame_gray, gpu_p0, None)
                # GPU returns 1xN; flatten to (N,2)/(N,) for uniform selection
                p1 = gpu_p1.download().reshape(-1, 2)
                st = gpu_st.download().reshape(-1)
                # Swap references so current frame becomes old frame for next
                # iteration, avoiding new GpuMat allocation each frame
                gpu_old_gray, gpu_frame_gray = gpu_frame_gray, gpu_old_gray
                good_new = p1[st == 1]
                good_old = p0.reshape(-1, 2)[st == 1]
            else:
                p1, st, err = cv2.calcOpticalFlowPyrLK(
                    old_gray, frame_gray, p0, None, **lk_params)
                # Select good points
                good_new = p1[st == 1]
                good_old = p0[st == 1]

            # draw the tracks
            for i, (new, old) in enumerate(zip(good_new, good_old)):
                a, b = new.ravel()
                c, d = old.ravel()
                mask = cv2.line(mask, (int(a), int(b)),
                                (int(c), int(d)), color[i].tolist(), 2)

                if self.color == False:
                    frame = cv2.cvtColor(frame_gray, cv2.COLOR_GRAY2BGR)

                frame = cv2.circle(
                    frame, (int(a), int(b)), 5, color[i].tolist(), -1)

            img = cv2.add(frame, mask)

            out.stdin.write(img.astype(np.uint8))

            # Now update the previous frame and previous points
            old_gray = frame_gray.copy()
            p0 = good_new.reshape(-1, 1, 2)
            if _use_gpu:
                gpu_p0.upload(good_new.reshape(1, -1, 2))

        else:
            pb.progress(length)
            break

        pb.progress(ii)
        ii += 1

    out.stdin.close()
    out.wait()

    destination_video = target_name

    if self.has_audio:
        source_audio = extract_wav(of + fex)
        embed_audio_in_video(source_audio, destination_video)
        os.remove(source_audio)

    # save result at flow_sparse_video at parent MgVideo
    self.parent().flow_sparse_video = musicalgestures.MgVideo(
        destination_video, color=self.color, returned_by_process=True)

    return self.parent().flow_sparse_video