I have an image that I've quantized like so:
And I would like to get rid of pixels that aren't much like their neighbors (basically do a low-pass filter). The goal would be to get rid of isolated pixels of a particular value or narrow strips of such pixels.
I've tried doing a Gaussian blur and then re-quantizing, but it still leaves behind narrow strips, e.g.
One idea I had was for each pixel to compute the most common pixel among it's neighbors in region
this mostly works
But my naive implementation takes O(num_pixels*radius**2)
def knn(img,radius=4):
imgOut=img.copy()
for i in trange(img.size[0]):
for j in range(img.size[1]):
counts={}
for k in range(-radius,radius+1):
for l in range(-radius,radius+1):
if i+k>=0 and i+k<img.size[0] and j+l>=0 and j+l<img.size[1]:
v=img.getpixel((i+k,j+l))
counts[v]=counts.get(v,0)+1
l=list(counts.items())
l.sort(key=lambda x:x[1])
imgOut.putpixel((i,j),l[-1][0])
return imgOut
And it doesn't even get rid of all the isolated pixels, since small regions shrink, creating more isolated pixels.
Is there a faster/better way to achieve the same effect?
I feel like there ought to be some kind of discrete Fourier transform + low pass method, but I'm not sure what it would be.
=============
Update:
scipy.ndimage.median_filter
Is almost what I want, but it still leaves behind narrow edges (like guassian blur did). This is because if you have (for example) an area that looks like [0,0,0,1,2,2,2] the median will be 1, leaving behind a narrow strip.
I don't think this is the most elegant way, but I get around this by doing multiple passes and randomizing the labels at each pass
def smooth_image(img,r=2,k=16):
a= np.asarray(img5)
aa=np.copy(a)
for i in trange(k):
v=np.random.permutation(NUM_COLORS)
ip=np.argsort(v)
#shuffle labels
aa=v[aa]
aa=scipy.signal.medfilt2d(aa,2*r+1)
#unshuffle
aa=ip[aa]
bmp = Image.fromarray(aa.astype(np.uint8))
bmp.putpalette(img5.palette)
bmp = Image.fromarray(aa.astype(np.uint8))
bmp.putpalette(img.palette)
return bmp
which produces the follow result