This demonstration illustrates many different ways to create a chess-like image in Python. Each way has a different concept, but most of them uses the property that each pixel is 0 or 1 depending if the sum of its coordinates is even or odd. The most efficient way is the technique using slices and the most general one is the one that uses indices.
These examples below is a good introduction to program Python efficiently for image processing applications and verify that processing arrays in Python is very efficient.
The image that we want to generate can be describe in the following equation:
$$ f(row,col) = (row+col) \bmod 2 $$
In [1]:
import numpy as np
In [3]:
def chessCstyle(H,W):
f = np.empty((H,W),'uint8')
for row in range(H):
for col in range(W):
f[row,col] = (row+col)%2
return f
print("C Style:\n", chessCstyle(4,8))
This is a general way to create images when we can write the equation of each pixel value based on its coordinates. The function indices is very handy to create arrays of each coordinate make possible the array processing of the whole image without the explicit scanning of the pixels in the image.
In [4]:
def chessIndice(H,W):
row,col = np.indices((H,W), 'uint16')
return (row+col)%2
print("Indices:\n", chessIndice(4,8))
In the previous indices example, the arrays row and col are two-dimensional arrays. It is possible to use the broadcast facility of NumPy. We create a row as one column 2D array and col as a one row 2D array and let the arithmetic operation create the full 2D array using broadcast.
In [5]:
def chessIndiceBroad(H,W):
row = np.arange(H).reshape(H,1).astype('uint16')
col = np.arange(W).reshape(1,W).astype('uint16')
return (row+col)%2
print("IndiceBroad:\n", chessIndiceBroad(4,8))
In [6]:
import numpy as np
def chessIter(H,W):
f = [[ (row+col)%2 for col in range(W) ] for row in range(H) ]
return np.array(f,'uint8')
print("Iter:\n", chessIter(4,8))
In [8]:
def chessSlice(H,W):
f = np.zeros((H,W), 'uint8')
f[0::2,1::2] = 1
f[1::2,0::2] = 1
return f
print("Slice:\n", chessSlice(4,8))
In [11]:
def chessRep(H,W):
p = np.array([[0,1],
[1,0]],'uint8')
fCol = p
# First column
for row in range (1, H//2):
fCol = np.vstack((fCol , p))
f = fCol
# Column replication
for col in range (1, W//2):
f = np.hstack((f, fCol))
return f
print("Replication:\n", chessRep(4,8))
In [13]:
def chessTile(H,W):
p = np.array([[0,1],
[1,0]], 'uint8')
f = np.tile(p, (H//2, W//2))
return f
print("Tiling:\n", chessTile(4,8))
In [15]:
def chessResize(H,W):
# create two lines and then apply resize
row1 = np.arange(W).astype('uint8') % 2
row2 = np.array([row1, 1 - row1])
f = np.resize(row2, (H,W))
return f
print("Resize:\n", chessResize(4,8))
We compare the speed efficiency of the implementation above. We can see that slicing, tiling and resize are the most efficient implementations because it is intrinsic to Numpy. Slicing is very efficient and is very recommending in most image processing manipulation. Coordinate processing and replication have medium efficiency with coordinate processing being a very general way to create images. Lastly list processing and C style programming present the worst efficiency because of its explicit pixel scanning. For comparison, the result of the chess image is always of an array of type uint8, except for the coordinates method where the result is in uint16 as the coordinate values are greater than 255.
In [17]:
H,W = 2000,2000
funlist = (chessResize, chessSlice, chessTile, chessIndiceBroad, chessIndice, chessIter, chessRep, chessCstyle)
fundesc = ("Resize", "Slicing", "Tiling", "CoordBroad" , "Coordinates", "List processing", "Replication", "C style")
for fun,fundesc in zip(funlist,fundesc):
print(fundesc)
%timeit f = fun(H,W)
Numpy is very efficient for array processing and it is very suitable for image processing. The recommended way of programming are: avoid explicit pixel scanning; use slicing as much as possible and use array processing such as in the coordinates example. Pixel scanning programming style is not recommended.