Here is my next Numpy practice, I am not sure is it more accurate to call it "Stroke Algorithm" or "Draw edge stuff", xD.
Anyway I did in steps:
calculate each pixel's RGB absolute distance to pixels on its right and down side, the distance is simple measured by absolute different in each color value and sum them up.
if the absolute distance on both directions all larger than some particular value, then it is an edge pixel, and color it black, else white
Suggestions I am looking for
- Any suggestions in Numpy, I have some duplicate code this time, and didn't find good way to remove them.
Full code:
# -*- coding: utf-8 -*-
import numpy as np
from PIL import Image
def stroke(image_path, output, level=80, edge_color=[255,255,255], blackground_color=[0,0,0]):
img = Image.open(image_path)
data = np.asarray(img, dtype="int32")
w, h, k = data.shape
dirright_data = np.concatenate((data[:, 1:], data[:,-1:]), axis=1)
dirdown_data = np.concatenate((data[1:,:], data[-1:,:]), axis=0)
disRight = np.absolute(np.sum(data - dirright_data, axis=2))
disDown = np.absolute(np.sum(data - dirdown_data, axis=2))
level = min(max(1,level), 255)
D_right = np.asarray(disRight<=level, dtype="int32")
D_down = np.asarray(disDown<=level, dtype="int32")
D = (D_right+D_down) > 1
neg_D = (D_right+D_down) <= 1
data[D] = edge_color
data[neg_D] = blackground_color
img = Image.fromarray(np.asarray(np.clip(data, 0, 255), dtype="uint8"), "RGB")
img.save(output)
if __name__ == "__main__":
stroke("images/bob.png", "new_test.jpg")
2 Answers 2
I find your numpy code to be quite rational as far as my skills go, but your script have several things to improve on besides that.
Improvable in current scope :
- Removing duplicate code thanks to a helper function
- Painting the whole background to simplify
- Removing (yet) unecessary line
w, h, k = data.shape
- Naming error, or logic error ? Background and edge color seem swapped. Changing
<= level
to> level
- Some non-compliant and unexplicit variable names
Code showing this
# -*- coding: utf-8 -*-
import numpy as np
from PIL import Image
def get_dist_array(data, shifted_data, level):
distance_map = np.absolute(np.sum(data - shifted_data, axis=2))
return np.asarray(distance_map > level, dtype="int32")
def stroke(image_path, output, level=80, edge_color=[255,255,255], blackground_color=[0,0,0]):
img = Image.open(image_path)
data = np.asarray(img, dtype="int32")
level = min(max(1,level), 255)
edges_right = get_dist_array(data, np.concatenate((data[:, 1:], data[:,-1:]), axis=1), level)
edges_down = get_dist_array(data, np.concatenate((data[1:,:], data[-1:,:]), axis=0), level)
data[:] = blackground_color
edges = (edges_right+edges_down) > 1
data[edges] = edge_color
img = Image.fromarray(np.asarray(np.clip(data, 0, 255), dtype="uint8"), "RGB")
img.save(output)
if __name__ == "__main__":
stroke("images/bob.png", "new_test.jpg")
Other things to consider worth improving :
- Get rid of hardcoded file names. You could use
argparse
here - RGBA support ?
-
\$\begingroup\$ Thanks a lot for your reply,
w, h, k = data.shape
is unecessary right, and<= level
yeah that's a mistake thanks for that.argparse
andRGBA
all good suggestions. hmm I am looking for numpy suggestion, I wonder if these code about distance to right and distance to down side can change color can be replace by some "one line" numpy code \$\endgroup\$Aries_is_there– Aries_is_there2018年11月18日 03:15:48 +00:00Commented Nov 18, 2018 at 3:15
In the future you might want to apply more than one transformation to an image. In order to be able to (efficiently) do this, you should put the image reading and writing in their own functions and just pass a numpy.array
to your function:
import numpy as np
from PIL import Image
def read_image(image_path):
img = Image.open(image_path)
return np.asarray(img, dtype="int32")
def write_image(img, image_path):
img = Image.fromarray(np.asarray(np.clip(img, 0, 255), dtype="uint8"), "RGB")
img.save(image_path)
def stroke(img, level=80, edge_color=[255,255,255], blackground_color=[0,0,0]):
...
if __name__ == "__main__":
img = read_image("images/bob.png")
img = stroke(img)
write_image(img, "new_test.jpg")
One additional point, currently you are using numpy.clip
to ensure that the output is in the right format to be written as an image file. I think this should be the responsibility of the caller and you would want it to explicitly error out if the caller did not take care of it instead of silently clipping. Depending on the use case, e.g. np.interp1d(img, (img.min(), img.max()), (0, 255))
(i.e. rescaling the values to the correct range) might be more correct.