I had to scale the video (and the mask) down to 480p as Mathematica couldn't handle the image processing at high resolution. My approach below naively does a segmentation on each frame and this causes poor temporal coherence in the result leaving holes and flickering artifacts. It's also very slow. I am looking at alternative solutions that overcome these problems, but for now here's what I've achieved:
The code below works as follows on each frame independently:
Training:
Convert the training frame to the LAB colour space.
Perform a Watershed transform to finely dice up the frame into small regions.
For each region, get the proportion of the mask in the region. If it exceeds a threshold, then it's in class 1, otherwise class 0
For each region, get some features about the region, e.g mean colour, standard deviation, centroid position, number of pixels.
Form training data from steps 3 and 4 in the form of a list of features -> class and use Classify
to create a ClassifierFunction
Segmentation of non-training frames:
Convert the frame to LAB.
Get a Watershed transform of the frame as in Training step 2.
As in step 4 of the Training phase, get features for each region.
Use the ClassifierFunction
we created earlier to classify regions in the frame as 1 (in the mask) or 0 (outside of the mask)
Replace the regions in the Watershed transform image with the mask values from step 4, and ImageMultiply
with the frame to apply the mask.
getSegmentation[img_] := Image[WatershedComponents[img, Method -> {"MinimumSaliency", 0.3}]]
pixelFeatures[pixels_] :=
Join[Mean[pixels], If[Length@pixels > 1, StandardDeviation[pixels], {0, 0, 0}]]
(* could also use a histogram here however it performs quite badly *)
getFeatures[img_, segmentation_] := ParallelTable[
With[{pos = PixelValuePositions[segmentation, i]},
Join[Mean[pos], {Length@pos},
pixelFeatures@PixelValue[img, pos]]], {i, 1,
Round@Max@segmentation}]
getSegmentLabelling[mask_, segmentation_, threshold_] :=
(* if more than threshold many pixels of each component appear in the mask, label this piece as flag, else background *)
ParallelTable[
If[Mean[PixelValue[Binarize@mask,
PixelValuePositions[segmentation, i]]] > threshold, 1, 0], {i, 1, Round@Max@segmentation}]
createTrainingData[frame_, mask_] :=
Module[{seg = getSegmentation[frame]},
Return[Thread[
Rule[getFeatures[frame, seg], getSegmentLabelling[mask, seg, 0.5]]]]
]
applyClassifier[cf_, frame_] := Module[{seg = getSegmentation[frame]},
(* I would use ColorReplace here but it doesn't work *)
Image[Round[ImageData[seg]] /. MapIndexed[First[#2] -> cf[#1] &, getFeatures[frame, seg]]]]
(* use it *)
vid = Video["flag.mp4"];
firstFrame = VideoFrameList[vid, 1][[1]];
mask = Binarize@ImageResize[Import["firstFrameMask.jpg"], ImageDimensions[firstFrame]];
HighlightImage[firstFrame, mask]
trainingData = createTrainingData[ColorConvert[firstFrame, "LAB"], mask];
classifier = Classify[trainingData];
result = ImageMultiply[#, applyClassifier[classifier, ColorConvert[#, "LAB"]]] & /@
Table[VideoExtractFrames[vid, i], {i, 1, 17, 0.25}]