I've stumbled across this, with my specific situation being anomalous singleton pixels in my constructed classification layer. Based on pointers in the (older) answers and comments here, plus How to apply a Majority filter in GRASS GIS?, here's what worked for me with a combo of GRASS and Raster calculator in QGIS.
Starting point is my (noisy) classification layer (a small subset). The 2nd image highlights the anomalous singletons in black.
A simple run of the QGIS/GDAL sieve
algorithm, with 8-connectedness and threshold of 2, seems promising, but does some strange remappings, e.g. the pixel circled in red. The problem is the GDAL sieve maps removed pixels to the biggest neighbouring polygon, which may not be the most frequent neighbour. Here it's one pixel, but in a land cover/vegetation classification this happens too often.
A majority filter gives a better remapping of the singletons, but unfortunately also remaps pixels that were on actual polygon boundaries, 2 examples again circled in red:
The solution is then to combine the original raster plus the majority-filtered one on the singletons only. Here is the output:
The steps to do this are as follows, the original raster called aci_class_combined
:
Create aci_class_interspersion
using grassr.neighbors method=interspersion, size=3
. This will have value 101 for pixels which are singletons (and down to 1 for pixels all of whose neighbours are the same).
Create aci_class_neighborsmode
using grass r.neighbors method=mode, size=3
. This majority filter smooths out the raster by replacing each cell with the mode of the cells around it.
Finally, combine to get the desired output by creating a raster in the Raster Calculator with expression
("aci_class_interspersion@1"<100)*"aci_class_combined@1"+("aci_class_interspersion@1">=100)*"aci_class_neighborsmode@1"
If using grass natively rather than from withing QGIS, one could use the the r.neighbors method=interspersion
output as a location mask in the method=mode
invocation directly, but from QGIS it would require mucking around with conditionals and no-data values and the above was faster than figuring it out.)
Editing to add: With some further tinkering, the simple majority filter using grass r.neighbors
can be overly aggressive, especially for singletons that end up having multiple neighbouring polygons with 2-3 adjacent pixels. For that step, using the SAGA Raster filter / Majority filter
with a threshold of ~50% (4+ neighbours to change a pixel) is a more conservative option. Still good to incorporate its changes only on singletons as above.