1. general ¶
In digital image processing, segmented transformation is to apply different transformation functions to different gray levels, so as to enhance the gray levels of interest and suppress the gray levels of interest
The advantage of piecewise linear function is that it can stretch the gray details of features according to needs. Some important transformations can only be described and realized by piecewise function. The disadvantage is that it is difficult to determine the parameters
Typical images of piecewise linear functions are as follows:
import numpy as np import matplotlib.pyplot as plt def func(value): if value <= 3: return 0 if value <= 6: return 2*(value-3) else: return 6 plt.figure(figsize=(6,4)) x = np.linspace(0, 8, 100) y = np.array([]) for v in x: y = np.append(y,np.linspace(func(v),func(v),1)) l=plt.plot(x,y,'b',label='value') plt.text(3.2, 0.2, r'$x_1,y_1$') plt.text(6, 5.7, r'$x_2,y_2$') plt.legend() plt.show()

The general formula of piecewise linear function is as follows:
By using segment transformation, the gray value of an image in a certain interval can be enlarged, and the image contrast can be enhanced by concentrating the gray value in a certain interval
2. disadvantages of gamma transform ¶
Non segmented transformation (such as gamma transformation) will have an interval with a slope of about 1, which makes the contrast of the image in this interval almost unchanged after gamma change
from PIL import Image import numpy as np import matplotlib.pyplot as plt def gamma_trans(input, gamma=2, eps=0 ): return 255. * (((input + eps)/255.) ** gamma) # Read in original drawing gray_img = np.asarray(Image.open('./image/washed_out_pollen_image.tif').convert('L')) # Create a display body and divide it into four display areas fig = plt.figure() ax1 = fig.add_subplot(221) ax2 = fig.add_subplot(222) ax3 = fig.add_subplot(223) ax4 = fig.add_subplot(224) # Show original ax1.set_title("origin") ax1.imshow(gray_img, cmap='gray', vmin=0, vmax=255) ax1.axes.xaxis.set_visible(False) ax1.axes.yaxis.set_visible(False) # Display the gray distribution histogram of the original image ax2.grid(True, linestyle=':', linewidth=1) ax2.set_title('origin', fontsize=12) ax2.set_xlim(0, 255) # Set x-axis distribution range ax2.set_ylim(0, 0.15) # Set y-axis distribution range ax2.hist(gray_img.flatten(), bins=50,density=True,color='r',edgecolor='k') ax2.axes.xaxis.set_visible(False) ax2.axes.yaxis.set_visible(False) gamma_ = 2 # Execute on original drawing γ Transformation output = gamma_trans(gray_img, gamma_, 0.2) # display γ Transform result image ax3.set_title("result") ax3.imshow(output, cmap='gray',vmin=0,vmax=255) ax3.axes.xaxis.set_visible(False) ax3.axes.yaxis.set_visible(False) # display γ Gray distribution histogram of transformed image ax4.set_xlim(0, 255) # Set x-axis distribution range ax4.set_ylim(0, 0.15) # Set y-axis distribution range ax4.grid(True, linestyle=':', linewidth=1) ax4.set_title('result', fontsize=12) ax4.hist(output.flatten(),bins=50,density=True,color='r',edgecolor='k') ax4.axes.xaxis.set_visible(False) ax4.axes.yaxis.set_visible(False) plt.show()

It can be seen that for an image whose gray value is concentrated in a certain part in the middle, the gamma change can hardly improve the contrast
3. segment transformation ¶
According to the piecewise function formula:
We can write the simplest and most direct piecewise transformation function:
# Three segment contrast stretch transformation, where x1,y1, X2 and Y2 are segment points, and X is the input matrix def three_linear_trans(x, x1,y1, x2,y2): # 1. check parameters to avoid denominator being 0 if x1 == x2 or x2 == 255: print("[INFO] x1=%d,x2=%d ->Calling this function must meet:x1≠x2 And x2≠255" % (x1, x2)) return None # 2. perform piecewise linear transformation out = np.zeros(x.shape) for i in range(x.shape[0]): for j in range(x.shape[1]): if x[i,j] < x1: out[i,j] = y1/x1*x[i,j] elif x1 <= x[i,j] <= x2: out[i,j] = (y2-y1)/(x2-x1)*(x[i,j]-x1)+y1 elif x[i,j] > x2: out[i,j] = (255-y2)/(255-x2)*(x[i,j]-x2)+y2 return out
Segment the image just now:
# Read image in grayscale gray_img = np.asarray(Image.open('./image/washed_out_pollen_image.tif').convert('L')) # Create a display body and divide it into five display areas fig = plt.figure() ax1 = fig.add_subplot(221) ax2 = fig.add_subplot(222) ax3 = fig.add_subplot(223) ax4 = fig.add_subplot(224) # Display the original image and its gray distribution histogram ax1.set_title("origin", fontsize=8) ax1.imshow(gray_img,cmap='gray',vmin=0,vmax=255) ax3.grid(True, linestyle=':', linewidth=1) ax3.set_xlim(0, 255) # Set x-axis distribution range ax3.set_ylim(0, 0.15) # Set y-axis distribution range ax3.hist(gray_img.flatten(), bins=50, density=True, color='r', edgecolor='k') # Perform piecewise linear transformation x1,y1,x2,y2 = 90, 3, 140, 250 out = three_linear_trans(gray_img,x1,y1,x2,y2) # Display transform result image ax2.clear() ax2.set_title("result", fontsize=8) ax2.imshow(out,cmap='gray',vmin=0,vmax=255) # Display gray distribution histogram of transformation results ax4.clear() ax4.grid(True, linestyle=':', linewidth=1) ax4.set_xlim(0, 255) # Set x-axis distribution range ax4.set_ylim(0, 0.15) # Set y-axis distribution range ax4.hist(out.flatten(), bins=50, density=True, color='r', edgecolor='k') plt.show()

It can be seen that the contrast is significantly enhanced, and the color distribution is no longer concentrated in a certain interval in the middle
Of course, this expanded distribution of colors concentrated in a certain interval is also called histogram equalization, which means that the color values of the image are redistributed to make the distribution of color values more uniform
4. algorithm optimization ¶
Here, we make full use of Numpy's broadcast mechanism and parallel computing to optimize the segment transformation just now
The calculation of each section of function is realized through three mask matrices, and the final transformation result is the sum of them:
# Three segment contrast stretch transformation, where x1,y1, X2 and Y2 are segment points, and X is the input matrix def three_linear_trans2(x, x1,y1, x2,y2): # 1. check parameters to avoid denominator being 0 if x1 == x2 or x2 == 255: print("[INFO] x1=%d,x2=%d ->Calling this function must meet:x1≠x2 And x2≠255" % (x1, x2)) return None # 2. perform piecewise linear transformation out = np.zeros(x.shape) m1=(x<x1) m2=(x1<=x)&(x<=x2) m3=(x>x2) out = (y1/x1*x)*m1 + ((y2-y1)/(x2-x1)*(x-x1)+y1)*m2 + ((255-y2)/(255-x2)*(x-x2)+y2)*m3 return out
Execution time comparison:
import time gray_img = np.asarray(Image.open('./image/washed_out_pollen_image.tif').convert('L')) x1,y1,x2,y2 = 90, 3, 140, 250 ticks1 = time.time() out = three_linear_trans(gray_img,x1,y1,x2,y2) print("Elapsed time( s)by:", time.time()-ticks1) ticks2 = time.time() out = three_linear_trans2(gray_img,x1,y1,x2,y2) print("Elapsed time( s)by:", time.time()-ticks2)
Elapsed time( s)by: 7.202014446258545 Elapsed time( s)by: 0.017905712127685547
You can see a significant improvement in execution efficiency