HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Recolour a shape

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
recolourshapestackoverflow

Problem

I am starting with a starting colour, a target colour and a starting position in an image. I want to change every contiguous pixel if it is close to the starting colour.

Imagine a stop sign. It has white letters on a red background. But the red is faded to a bunch of different reds. It should work that all of the red around the letters is one shape to be filled any colour, each letter is another shape to be coloured, and the holes of the O and P should also be shapes to be coloured. If I picked a midrange red from the stop sign and my starting point is on the outside of the letters, I would want all the outside red to be changed to the target colour.

So I used colormath to do a delta_e on each pixel to see if it is less than the max acceptable delta, and it works. However, it is painfully slow. I was thinking instead of doing a delta_e on each pixel I could do a histogram of all the colours and then have some sort of look up table. However, I am brand spankin new to Python and I was hoping to get some help with how I would do that.

Please help optimize it for speed.

```
from colormath.color_objects import RGBColor
import Image

def floodFill(x,y, d,e,f, g,h,i, image):
toFill = set()
alreadyfilled = set()
toFill.add((x,y))
image = Image.open(image)
xsize, ysize = image.size
print(xsize,ysize)
print(image.getpixel((x,y)))
while (toFill):
(x,y) = toFill.pop()
alreadyfilled.add((x,y))
#print (x,y)
(a,b,c) = image.getpixel((x,y))
if not (FindDeltaColor(a,b,c,d,e,f) < 50):
continue
image.putpixel((x,y), (g,h,i))
if x != 0:
if (x-1,y) not in alreadyfilled:
toFill.add((x-1,y))
if y != 0:
if (x,y-1) not in alreadyfilled:
toFill.add((x,y-1))
if x != (xsize-1):
if (x+1,y) not in alreadyfilled:
toFill.add((x+1,y))
if y != (ysize-1):
if (x,y+1) not in alrea

Solution

numpy and skimage can do this quite fast. Without plotting the intermediate results in there, this runs in about 120ms.

Using some random stopsign image found from google: http://www.ecovelo.info/images/stop-sign.jpg

Using a formula from wikipedia to calculate color difference: http://en.wikipedia.org/wiki/Color_difference

from skimage.data import imread
from skimage.color import rgb2lab
from skimage.morphology import label
import numpy as np
import matplotlib.pyplot as plt

img = imread('stop-sign.jpg')
imgLab = rgb2lab(img)

fig = plt.figure()
fig.add_subplot(231)
plt.imshow(img)
plt.title("original image")

pos = (200, 270)
color = img[pos]
colorLab = rgb2lab(color[None, None, :])

#calculate deltaE for the whole image.
deltaE = np.sqrt(((imgLab - colorLab)**2).sum(axis=2))
fig.add_subplot(232)
plt.imshow(deltaE)
plt.title("Delta E of image")

#create a boolean mask of where deltaE is less than a given threshold
mask = np.where(deltaE  1:
    label_at_pos = label[pos]
    mask[label != label_at_pos] = False
    fig.add_subplot(235)
    plt.imshow(mask.copy())
    plt.title("mask connected with start position")

img[mask] = color
#Too see exactly what got filled, uncomment the next line
#img[~mask] = [0,0,0]

fig.add_subplot(236)
plt.imshow(img)
plt.title("Result")
plt.show()


output:

Code Snippets

from skimage.data import imread
from skimage.color import rgb2lab
from skimage.morphology import label
import numpy as np
import matplotlib.pyplot as plt

img = imread('stop-sign.jpg')
imgLab = rgb2lab(img)

fig = plt.figure()
fig.add_subplot(231)
plt.imshow(img)
plt.title("original image")

pos = (200, 270)
color = img[pos]
colorLab = rgb2lab(color[None, None, :])

#calculate deltaE for the whole image.
deltaE = np.sqrt(((imgLab - colorLab)**2).sum(axis=2))
fig.add_subplot(232)
plt.imshow(deltaE)
plt.title("Delta E of image")

#create a boolean mask of where deltaE is less than a given threshold
mask = np.where(deltaE < 50, True, False)
fig.add_subplot(233)
plt.imshow(mask.copy())
plt.title("mask of Delta E")

#connected labeling calculation, used to find all the pixels in the boolean mask connected to our starting position
label, nlabels = label(mask, 4, False, return_num=True)
fig.add_subplot(234)
plt.imshow(label)
plt.title("labeling")
#If the algorithm finds more than 1 label, we need to modify our mask.
if nlabels > 1:
    label_at_pos = label[pos]
    mask[label != label_at_pos] = False
    fig.add_subplot(235)
    plt.imshow(mask.copy())
    plt.title("mask connected with start position")

img[mask] = color
#Too see exactly what got filled, uncomment the next line
#img[~mask] = [0,0,0]

fig.add_subplot(236)
plt.imshow(img)
plt.title("Result")
plt.show()

Context

StackExchange Code Review Q#39701, answer score: 5

Revisions (0)

No revisions yet.