Postprocessing is the process of turning a B/W image back into the colored one that resembles a scan or a photo of a document. It can be understood as the inverse operation to binarization in a traditional document processing pipeline.
Smashcima scene contains sprites, which although can be any RGBA image, are most likely black-on-alpha images, because the source symbol datasets (such as CVC-MUSCIMA) are binarized. Postprocessing aims to re-introduce color and texture into the synthesized image.
Postprocessing also covers the application of geometric distortion filters to the image to simulate camera-related effects.
Postprocessing is a sub-task of compositing, therefore the actions taken by the postprocessor depend on the current compositing pipeline. To learn more about compositing, see the related documentation page.
The Compositing documentation page contains the description of the DefaultCompositor's compositing pipeline. It consists of the extraction of 3 layers and their subsequent merger. These three layers are given to the Postprocessor before they are merged and then once again after they are merged to apply various filters.
Therefore the Postptocessor interface defines two methods:
class Postprocessor(abc.ABC):
@abc.abstractmethod
def process_extracted_layers(
self,
layers: LayerSet
) -> LayerSet:
"""Processes layers separately right after they are extracted
from the scene."""
raise NotImplementedError
@abc.abstractmethod
def process_final_layer(
self,
final_layer: ImageLayer
) -> ImageLayer:
"""Processes the final composed layer before it
exits the compositor."""
raise NotImplementedErrorThis interface is therefore compatible with any compositor, that first extracts some layers from the scene and then merges them into a final layer.
The first process_extracted_layers method receives a LayerSet. It's a collection of ImageLayers identified by a string label. It can be manipulated like a dict value:
layers: LayerSet
# get a layer
layer: ImageLayer = layers["ink"]
# test presence of a layer
assert "ink" in layers
# set a layer
layers["ink"] = my_new_ink_layerIf the DefaultCompositor is used, the LayerSet will contain the three default layers:
inkstafflinespaper
Once given layers are processed by the postprocessor, a new LayerSet instance should be created and returned from the method:
return LayerSet({
"ink": postprocessed_ink,
"stafflines": postprocessed_stafflines,
"paper": postprocessed_paper
})The second process_final_layer method behaves exactly the same, except that it operates only on the one, final, composed layer.
By default, each Model registers the NullPostprocessor implementation for the Postprocessor interface. This implementation simply does not postprocessing. It returns the exact same layers it gets on input:
class NullPostprocessor(Postprocessor):
"""Applies no postprocessing filters."""
def process_extracted_layers(
self,
layers: LayerSet
) -> LayerSet:
return layers
def process_final_layer(
self,
final_layer: ImageLayer
) -> ImageLayer:
return final_layerIn pracise it's good to apply postprocessing, as it acts as image augmentation and makes the trained recognition model more robust.
A reasonable default implementation for (handwritten) music images is the BaseHandwrittenPostprocessor. It is built using filters from two image augmentation libraries (which come installed with Smashcima):
Most of the used filters come from Augraphy, as it's better tuned to augmenting scans of physical documents. Albumentations, on the other hand, provides more filters, but they are more low-level and many are geared towards general-purpose augmentation of photos or medical scan images.
The postprocessor defines 7 filter stacks that apply randomized filters:
f_stafflinesStafflines (dilation, texture, color)f_inkstyleScribbles (scribbles and text added on top of the document)f_bleed_throughBleed through (back-side music notation bleeding through)f_ink_colorInk colorf_scribblesInk texturef_foldingFolding (crumpled paper effect)f_cameraCamera effects (rotation, padding, shadow, blur)
These stacks can be toggled in the gradio demo to see their effects.
For details, please refer to the source code.
If you use the BaseHandwrittenPostprocessor or you build your own using these libraries, don't forget to cite their respective papers: