Video Style Transfer: Coherence vs Quality

The video style transfer project we built at NTNU began with a problem I hadn’t appreciated from looking at still images. A frame could look good on its own and still look wrong when played beside the next one. Getting the painted texture to follow the motion became a large part of the work.
Why it’s harder than it looks
When style transfer processes each frame independently, it can place different textures on the same part of a scene from one frame to the next. Those changes appear as flicker during playback. We needed to keep the frames consistent while preserving both the motion and the chosen style.
The math
A convolutional network provides features for comparing content and style. For a frame \(I_t\), we can use content features \(F_c\) and style features \(F_s\), then add a temporal term that measures changes between frames:
\[L_{total} = \alpha L_c + \beta L_s + \gamma L_{temporal}\]
Here \(L_c\) measures content differences, \(L_s\) measures style differences, and \(L_{temporal}\) measures inconsistency over time. The weights \(\alpha\), \(\beta\), and \(\gamma\) control their relative influence.
Making it smooth over time
A simple temporal loss penalizes differences between consecutive stylized frames \(S(I_{t-1})\) and \(S(I_t)\):
\[L_{temporal} = \sum_{i,j} \| S(I_t) - S(I_{t-1}) \|\]
Comparing frames directly also penalizes real movement, so our version used optical flow to estimate where pixels had moved. We warped the previous stylized frame into the current frame’s coordinates before comparing them:
import torch |
This encourages a point in the scene to keep a consistent appearance as it moves, rather than encouraging every pixel to stay unchanged.
Approaches we tried
Frame-by-frame Gatys
We first applied Gatys-style optimization to each frame independently. The resulting flicker gave us a useful baseline for judging whether later changes improved consistency.
Optical flow constraints (Ruder)
Ruder’s approach uses optical flow to constrain how the stylized image changes between frames. It improved consistency, but estimating the flow added computation and made the implementation more involved.
Feedforward networks with instance normalization
Optimization-based methods repeatedly update an image to minimize the loss. Johnson et al. instead train a feedforward network to produce the stylized image in one pass. We used instance normalization in our version:
class InstanceNorm(nn.Module): |
Instance normalization normalizes each feature channel within an individual image, reducing the effect of differences in contrast. Once trained, the network can apply its learned style to new frames without optimizing each frame from scratch.
Wasserstein distance for style matching
We also used Wasserstein distance to compare feature distributions for style matching. It measures the minimum cost of moving one distribution into another, where the cost depends on how far the mass moves:
\[W(P, Q) = \inf_{\gamma \in \Pi(P, Q)} \mathbb{E}_{(x, y) \sim \gamma} [\| x - y \|]\]
Our implementation applied this comparison to Gram matrices of feature activations. We used it as another way to compare style statistics alongside the usual Frobenius-norm difference between Gram matrices.
What worked best
The best balance in our experiments came from a feedforward network with instance normalization and temporal constraints during training. The network learned from comparisons across frames, while inference still used a single forward pass for each frame.
Each approach had a different cost. Independent frame optimization was easy to use as a baseline but flickered, and optical flow improved consistency at the cost of extra work. The feedforward model was faster after training, although it was tied to the style it had learned.
Our GitHub repository has the approaches in separate modules. Comparing their outputs as videos was essential: judging only individual frames would have missed much of the difference between them.