This script replaces the red hair in image to black color (replace one color to another)
Three major parts in this script
Not just replace color red to black, I hope the output image looks natural, so I will replace the color close to red to black. This "close" is measured by Euclidean distance.
And not just replace by black color, user can modify the red color's
r, g, b
value separately, like add more green.Choose the area to be changed, rather than change the whole picture
Suggestions I am looking for:
- I did some NumPy practice before, so any suggestions about NumPy are welcome.
I think the code to replace target area is not so elegant, I am not quite sure about this part:
(c1, r1), (c2, r2) = area for i in range(r1, r2+1): l, r = i*h+c1, i*h+c2+1 data[l:r,:k][D[l:r]] = modification(data[l:r,:k][D[l:r]])
Full code:
import numpy as np
from PIL import Image
from scipy.spatial.distance import cdist
def replace_corlor(image, original_corlor, modification, area=None, distance=1000, output="new_test.jpg"):
print("[*] START Replace Color")
img = Image.open(image)
data = np.asarray(img, dtype="int32")
w,h,k = data.shape
data = np.reshape(data, (w*h,k))
distMatrix = cdist(data, np.array([original_corlor]))
D = distMatrix<=distance
D = np.reshape(D,w*h)
if area is None:
data[:,:k][D] = modification(data[:,:k][D])
else:
(c1, r1), (c2, r2) = area
for i in range(r1, r2+1):
l, r = i*h+c1, i*h+c2+1
data[l:r,:k][D[l:r]] = modification(data[l:r,:k][D[l:r]])
data = np.reshape(data, (w,h,k))
img = Image.fromarray(np.asarray(np.clip(data, 0, 255), dtype="uint8"), "RGB")
img.save(output)
print("[*] DONE Replace Color")
if __name__ == "__main__":
def modification(color):
return color * [2,0,0]
# return [255,0,0]
replace_corlor("test.jpg",(36,35,30),modification, ((0,0),(400,100)))
1 Answer 1
Nice code.
Let's look at the function signatures.
As gbartonowen noted, two typos on corlor
for color
.
The keyword defaults are lovely,
thank you for appropriately dealing with a magic number, and defaulting the filespec.
Lots of keywords will lead to a somewhat long signature, though, as reported by $ flake8
:
E501 line too long (106 > 79 characters)
Recommend you always run such a linter before sharing code.
And heed the linter's advice.
In the same vein we see things like PEP-8 wants spaces in the w, h, k
assignment,
or (w * h, k)
expression.
And, this being python rather than java,
snake-case identifiers of d
and dist_matrix
would be much more appropriate.
Python convention says the Gentle Reader should expect D
to be a class.
(Yes, I know this clashes with conventions of the mathematical community,
something had to give. Ok, on to more substantive comments.)
An identifier of data
is always on the vague side,
it's a bit like naming your variable the_thing
.
Yes, it's accurate, but often it could be a little more informative.
Consider a rename along these lines:
img = np.asarray(Image.open(image), dtype='int32')
(Hmmm, as I look at that, maybe we'd like identifiers of in_file
and out_file
?)
The cdist()
call, which defaults to Euclidean, is pretty interesting,
since RGB is not a perceptually uniform color scheme.
Consider mapping to Lab* colorspace and using that for comparisons.
It is slightly tricky for a naive caller to correctly pass in area
,
so it warrants mention in the docstring you're going to write,
or at least in a comment.
Similarly for the modification
signature.
Consider raising an error if c1 < c2
or r1 < r2
does not hold.
The l, r
identifiers are well chosen,
and I eventually puzzled out their meaning,
but I wouldn't mind a comment mentioning "left, right",
as I kept thinking in terms of "row".
I also wouldn't mind seeing comments explaining the need for each reshape()
.
If speed is a concern, then figuring out how to get modification()
to broadcast values to a sub-rectangle would be the thing to focus on.
The saturating aspect of [2, 0, 0]
is slightly surprising,
please comment on it, and how it deliberately interacts with clip()
.
Also, the list is not pythonic, this should definitely be the tuple (2, 0, 0)
.
A more descriptive name, perhaps red_modification
, would be appropriate.
Protecting def modification
so import
won't see it
kind of make sense, but is a little weird.
You don't want that example to be part of your public API, that's fine.
But consider using def _modification
in the usual way,
so the __main__
clause is just a one-liner.
Well, OK, two lines, as the magic tuple (36, 35, 30)
needs a name like dark_gray
.
Looks good, ship it!
original_color
value. The larger this distance (and the closer the distance is to thedistance
threshold), the less you modify the color. This way, you will get a smoother transition at the edges of the red region. \$\endgroup\$area
in parameters, if you wanna just change one pixel, for example the first pixel, just set area=((0,0),(1,1)), and about the typo, you are totally right \$\endgroup\$