Seedlings in-field orientation detection via elementary PCA

In the aim of counting young seedlings, it can be interesting to find the path followed by the tractor first. One can think that with that information known, it’ll be easier to find the rows from where we can eliminate inter-rows vegetation considered as weeds.

Here we explore a naive solution based on PCA. After all, PCA gives the main directions of a points cloud! Actually, even not perfect this solution still succeeds a bit (and allows one to practice PCA with 2 lines of Python’ code):

Loading an orthorectified RGB image

from IPython.display import Image
from IPython.core.display import HTML as Center

Center(""" <style>
.output_png {
    display: table-cell;indf
    text-align: center;
    vertical-align: middle;
    height:1600;
    width:600;
    
}
</style> """)
import cv2
import imutils
import numpy as np
import matplotlib.pyplot as plt
Image_path='/home/dac/Bureau/COMPTAGE/Images/tilled.tif'
img=cv2.imread(Image_path,-1)
Nrow=img.shape[0]
Ncol=img.shape[1]
Ncol
4066
###########CallBack Function to get MouseClick pixel values##########################
def mouseRGB(event,x,y,flags,param):
    if event == cv2.EVENT_LBUTTONDOWN: 
        colors = image[y,x]
        print("BRG Format: ",colors)
        print("Coordinates of pixel: X: ",x,"Y: ",y)

Calculating the Green Index: 2G - R - B

###########GREEN INDEX############################
Red,Green,Blue,_=cv2.split(img)
Green_Idx=2*Green-Blue-Red
print(Green_Idx.shape[0])

Window_name='Green Index'
cv2.namedWindow(Window_name,cv2.WINDOW_NORMAL)
cv2.imshow(Window_name,Green_Idx)
image=Green_Idx
cv2.setMouseCallback(Window_name,mouseRGB)
k = cv2.waitKey(0)
cv2.destroyAllWindows()
3192

Cleaning the image

#############################MASK###################################
mask=cv2.threshold(Green_Idx,100,255,cv2.THRESH_BINARY)[1]
#####################ERODATE FOR OUTLIERS SUPPRESSION####################################
kernel= np.ones((2,2),np.uint8)
erosion=255-cv2.dilate(mask,kernel,iterations=3)
# The best is still to revert the mask

Finding the contours

################FIND CONTOURS#####################################################
Window_name="Contours"
# contours are not anymore in [row, col] but [col, row] format (geometrical points format)
contours, hierarchy = cv2.findContours(erosion, cv2.RETR_LIST, cv2.CHAIN_APPROX_TC89_L1)
cv2.drawContours(erosion, contours, -1, (255,0,0), 3)

cv2.namedWindow(Window_name,cv2.WINDOW_NORMAL)
cv2.imshow(Window_name,erosion)
k = cv2.waitKey(0)
cv2.destroyAllWindows()

Calculating the contours’ centroids

def f_Centroids(contours,axis=0):
    Centroids=np.zeros((len(contours),2))
    for i,points in enumerate(contours):
        Centroids[i]=np.average(points,axis)
    return Centroids
##############Contours Centroids#####################################
Centroids=f_Centroids(contours)
##########PLOTS##################################
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB),zorder=1)
plt.scatter(Centroids[:,0],Centroids[:,1],c='b',s=3,zorder=2)
plt.show()

png

Towards finding the seedlings’ lines

First, we clip the image into Ndisks disks to avoid principal components to be influenced by the shape of the sampling window.

The helpers functions here:

def Disk_clip(points,center, radius):
    return points[(np.square(points[:,0]-center[0])+np.square(points[:,1]-center[1]) < np.square(radius))]
def draw_vector(v0, v1, idx, ax=None):
    ax = ax or plt.gca()
    arrowprops=dict(arrowstyle='->',
                    linewidth=2,
                    shrinkA=0, shrinkB=0)
    ax.annotate('', v1, v0,arrowprops=arrowprops)
    ax.annotate('ax'+str(idx), v1)
def local_directions(Points):
    #Data is centered by the function already, not scaled though which is convenient here
    pca = PCA(n_components=2)
    pca.fit(Points)
    idx=0
    for length, vector in zip(pca.explained_variance_, pca.components_):
        
        v = vector * np.sqrt(length)
        cloud_pts_dir[idx] += vector 
        #draw_vector(pca.mean_, pca.mean_ + np.array([v[1],v[0]]), idx)
        idx+=1
    

Finally, we average all the principal directions we found.

The main algorithm here:

####################FIND SEEDLINGS LINES FROM PCA#####################
import seaborn as sns; sns.set()
from sklearn.decomposition import PCA

plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB),zorder=1)
plt.scatter(Centroids[:,0],Centroids[:,1],c='b',s=3,zorder=2)
        
Ndisks=1000
Disk_radius=800
Xcenters=np.random.randint(1, Ncol-1, Ndisks)
Ycenters=np.random.randint(1, Nrow -1, Ndisks)
cloud_pts_dir=np.array([[0.0,0.0],[0.0,0.0]])

for i in range(Ndisks):
    local_directions( Disk_clip( Centroids,[ Xcenters[i], Ycenters[i]],Disk_radius))
    
img_center = np.array( [ int(Ncol/2), int(Nrow/2)] ) 
cloud_pts_dir = 1/Ndisks * cloud_pts_dir
cloud_pts_dir/= np.sqrt(np.square(cloud_pts_dir[0]) + np.square(cloud_pts_dir[1]))
draw_vector(img_center, img_center + 500 * np.array([cloud_pts_dir[0,1] , cloud_pts_dir[0,0] ]), 99999)
plt.axis('equal');
plt.show()

The result:

png