The Dandelion (Taraxacum Officinalis) and OpenCV

The dandelion’s pallid tube
Astonishes the grass,
And winter instantly becomes
An Infinite Alas —

The tube uplifts a signal Bud
And then a shouting Flower, —
The Proclamation of the Suns
That septulture is o’er.

– Emily Dickinson

The yellow flowers and the delicate and beautiful florescence of Dandelion catch the attention of both romantic and curious souls. The aerial consistency of the fine silk decorated seeds that glance to the sunlight as crystalline material became the favourite subject of inspired photographer and the toy of amused children. Beside the grace of its forms, other interesting and curious secret is hidden in its phloem fluids. In fact, if you cut one of the stems of the plant a milky sticky liquid will flow out of the wound resection. This latex is going to polymerize at 30-35 oC in few minutes in a yellow-brown quite solid mass. Around the year 1982, I have annotated this observation but I could not find in my later notes further follow-ups study on the topics. It was a casual observation but I didn’t know at that time that this latex is indeed very useful. A variety of the Taraxacum (Taraxacum koksaghyz, Russian Dandelion) was used in Russian and American to produce a replacement of the natural rubber from Brazil during the WWII that was in shortage because of the war. Now days, many researches are in progress to exploit the lattice of Taraxacum and also Taraxacum brevicorniculatumas a convenient replacement of the rubber plant lattice. A recent study has shown the presence of rubber particles in the lattice of these plant in 32% proportion composed prevalently by poly(cis-1,4-isoprene) at >95% of purity (www.biomedcentral.com/1471-2091/11/11). The brownish lattice condensate that, as I reported in my note, forms after exposing for several minutes the latex to the air, is caused by the presence of the polyphenol oxidase (PPO) enzyme that produces the fast coagulation of the latex by catalysing the oxidation of polyphenols. Genetic engineer the plant, it is possible to reduce the amount of PPO in the latex making economically competitive the use of this resource for the production of latex.

My fascination for this common weed is not going to end with the useful properties of its latex. Dandelion plant cached again my attention very recently. This time my interest was directed to analyse the mechanics of the inflorescence: the formation of seeds and their dispersion. Indeed, the reproduction and proliferation processes are very complex and not completely understood even for the simpler weed model as the Arabidopsis thaliana. However, the observation of these phenomena from the amateur point of view is a delicious opportunity to learn interesting aspect of physics photography and computer science.

The Taraxacum officinale is a common species in all Europe. The pant can have stems up to 40 cm high. The yellow flowers are called florets and are hold by a cup-like bract called calyculus composed of 12 to 18 green lanceolate shaped segments with dark grey-purple apices acuminate. The number of florets can vary from 40 to more than 100. The fruits, called cypselae are oblanceoloid in shape and 2 to 3 mm long with slender beaks. They are attached to white silky pappi, which form the ~6 mm wide parachutes. (Figure 2).

You can collect the flower by cutting on the stem 15-20 cm below them and then insert the cut stem in a test tube with water. Normally after few hours, the sepals close the corona of florets. Later, the florets start to wither and dry out and after a couple of days the bundle of the parched petals fall down leaving a white wisp of thin white hair of the silky pappi enclosed in the sepal tuft (see Figure 3). This process can be filmed in time-lapse with little effort using the consumer technological resources available in each house:  mobile phone. In my case I have used the camera of an iPhone 4s using the software for time-lapse Lapse-it. The same program is also available for Android devices. The set up for a mini photo studio is simple, you just need a LED light to keep uniformly illuminated the subject and a white background behind the test tube with the flower.

Analysis of the Dandelion inflorescence opening

The analysis of a time-lapse video of a Dandelion inflorescence was a valid motivation to learn how to use the library Python-OpenCV for video analysis. I have recorded the opening of a Dandelion collected in the garden using a iPhone4 equipped with an app for time-lapse (Lapse-it). The video recording was a first test and it was made without much care on the lighting and background. It turns out to be a nice and interesting video tough with a not uniform background and a variable illumination. Still, I decided to perform a video analysis of the inflorescence opening to explore the capability of the OpenCV image processing and eventually to better understand the dynamics of the process. The results of this analysis is reported in the video below. There are still bugs in code that need to be removed. For example, the clock as it does not match the reported time in seconds. Working is in progress tough very slowly as the dandelion’s movements !.

Implementation of the Dandelion Video analysis using Python and OpenCV

OpenCV stands for Open Source Computer Vision Library. It is an open source computer vision and machine learning software library. It is distributed under a BSD-license that allow to utilize and modify the code. The library has a huge collection of optimized algorithms that can be used to identify objects, track moving objects, extract 3D models of objects, produce 3D point clouds from stereo cameras, stitch images together to produce a high resolution image of an entire scene, and for many more. The library has a very easy to use Python interface and supports for all commonly used OS. The best use with python is in combination with Jupyter.

The task required the following steps:

  • Separate the subject from the messy background of my workshop.
  • Adjust the video to align the stem of the dandelion with the vertical ass of the video.
  • Add visual aids to follow the movement of the opening corolla.
  • Extract features from the subject that allow to track its dynamics.
  • Plot the numerical description of this feature.
  • Add time indicators.

The code that I have implemented is in the appendix. I am sure that it can be written in more professional way and suggestions to improve it are always welcome.

Acknowledgments

In this project, I have adapted many examples posted in the python OpenCV tutorials. However, I could not solve some task without the help of many bloggers that generously share their knowledge and experience on python and OpenCV. I would like to thank in particularly the users of stackoverflow.com for sharing their very useful posts.

APPENDIX

In this appendix, you can find the python source code as implemented in Jupyter notebook. The code is provided as some reader can find it useful for experimenting.

%matplotlib inline

import numpy as np
import cv2
from matplotlib import pyplot as plt

The following cell contains the definition of functions used to draw the elements on the mask overalying the image.

  • Draw_Clock: draw the clock on the right side of the screen
  • Draw_Line: draw to dotted cross line on the dandelion
  • Draw_half_circle_no_round: draw half a portion of a circle. It is used to create the mask for the analysis of the dandelion opening.
  • Plot_Axes:
  • Logistic: define a logistic function to overla with the calculated experimental data
# Colors (B, G, R)
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)


def Draw_Clock():

#
# Draw clock
#

    cx=200   # x pos. of clock center
    cy=200   # y pos. of clock center
    crad=150 # clock radius
    cv2.circle(dst, (cx,cy),crad, (255,255,255),1 ) 
    cv2.circle(dst, (cx,cy),10, (255,255,255),2 ) 

    for t in range(-180,180,6):
        a=t*np.pi/180
        x=int(crad*np.cos(a))+cx
        y=int(crad*np.sin(a))+cy

        if t%30==0:
            x1=int((crad-15)*np.cos(a))+cx
            y1=int((crad-15)*np.sin(a))+cy
            tick=2
        else:
            x1=int((crad-10)*np.cos(a))+cx       
            y1=int((crad-10)*np.sin(a))+cy
            tick=1
        cv2.line(dst,(x,y),(x1,y1),(255,255,0),tick)    

        # Draw clock handle

        x=int((crad-30)*np.cos(angr))+cx
        y=int((crad-30)*np.sin(angr))+cy        
        cv2.line(dst,(cx,cy),(x,y),(255,255,0),3)
        font = cv2.FONT_HERSHEY_SIMPLEX
        text="Hours"
        cv2.putText(dst, text, (25,50),font, 1,(255,255,255))        

def drawline(img,pt1,pt2,color,thickness=1,style='dotted',gap=20):

    dist =((pt1[0]-pt2[0])**2+(pt1[1]-pt2[1])**2)**.5
    pts= []
    for i in  np.arange(0,dist,gap):
        r=i/dist
        x=int((pt1[0]*(1-r)+pt2[0]*r)+.5)
        y=int((pt1[1]*(1-r)+pt2[1]*r)+.5)
        p = (x,y)
        pts.append(p)

    if style=='dotted':
        for p in pts:
            cv2.circle(img,p,thickness,color,-1)
    else:
        s=pts[0]
        e=pts[0]
        i=0
        for p in pts:
            s=e
            e=p
            if i%2==1:
                cv2.line(img,s,e,color,thickness)
            i+=1

def draw_half_circle_no_round(image):
#
# This function was taken from stackoverflow
#
    height, width = image.shape[0:2]
    # Ellipse parameters
    radius = 350
    center =(xc,370)
    axes = (radius, radius)
    angle = 0
    startAngle = 180
    endAngle = 450
    # When thickness == -1 -> Fill shape
    thickness = -1

    # Draw black half circle
    cv2.ellipse(image, center, axes, angle, startAngle, endAngle, BLACK, thickness)

    axes = (radius - 10, radius - 10)
    # Draw a bit smaller white half circle
    cv2.ellipse(image, center, axes, angle, startAngle, endAngle, WHITE, thickness)

def plotaxes(image):
#
# Plot the axes of the graphs 
#
    cv2.rectangle(image,(20,20),(380,700),(255,255,255),2)
    cv2.line(dst,(37,650),(360,650),(0,255,255),2)
    cv2.line(dst,(37,650),(37,400),(0,255,255),2)
    cv2.line(dst,(37,500),(360,500),(0,255,255),2)
    cv2.rectangle(image,(37,400),(360,650),(0,255,255),1)
    for t in range(0,330):
        if t%10==0:    
            cv2.line(image,(37+t,650),(37+t,660),(0,255,255),1)  
            cv2.line(image,(37+t,500),(37+t,510),(0,255,255),1) 
        if t%20==0:
            cv2.line(image,(37+t,650),(37+t,660),(0,255,255),2)
    for t in range(0,260):
        if t%10==0:   
            cv2.line(image,(27,650-t),(37,650-t),(0,255,255),1)  
        if t%20==0:  
            cv2.line(image,(27,650-t),(37,650-t),(0,255,255),2)  

def calchROI(ref):
#            
# extract a ROI from the image containing a portion of the flower stem
#
        stem=trf[600:650,700:900]

# calculate the row-averaged histogram of the ROI along the its columns 

        profile = np.zeros(200)
        maxy =-1
        maxx= 0
        inx=int(cc/dx)     
        for i in range(0,200):
            # Average the profile for the green channel                              
            profile[i]=sum(stem[0:50,i,2])/50.
            # Calculate the maximum value of the profile for the green channel                
            if maxy<profile[i]:
                maxx = i
                maxy=profile[i]
#
# subtract the reference value to the calculate one
#
        prof[inx]=int(maxx-32)/dy1    

#
# Calculate the average of the ROI histogram of the visible image
#

        avhist[inx]=(int((sum(hist_mask[200:255])/681923.)/dy))
#
# Use as vale from the first frame as reference value
#
        if cc==0:
            ref=avhist[inx]
#
# Subtract the reference value to the calculated average
#
        avhist[inx]-=ref   

def plotHist():        
        for x in range(1,inx):
            cv2.circle(dst, (37+x,650-avhist[x]),2, (0,0,255),-1 )
            cv2.circle(dst, (37+x,650-int(ydata[x])),1, (255,0,0),-1 )
            cv2.circle(dst, (37+x,500-prof[x]),2, (255,255,0),-1 )

def protractor():
#
# Draw the protractor scale
#
    for t in range(-180,180):
        a=t*np.pi/180
        x=int(300*np.cos(a))+xc
        y=int(300*np.sin(a))+370

        if t%10==0:
            x1=int(335*np.cos(a))+xc
            y1=int(335*np.sin(a))+370
            tick=2
        else:
            x1=int(325*np.cos(a))+xc        
            y1=int(325*np.sin(a))+370
            tick=1
        cv2.line(dst,(x,y),(x1,y1),(0,0,0),tick)
        cv2.circle(dst, (xc,370),300, (0,0,255),1 ) 
        cv2.circle(dst, (xc,370),325, (255,255,255),50 ) 
        drawline(dst,(xc-300,370),(xc+300,370),(0,0,255),2,'dotted',10)
        drawline(dst,(xc,70),(xc,670),(255,0,0),2,'dotted',10)            

def  printsecs():
    font = cv2.FONT_HERSHEY_SIMPLEX
    text="Secs: "+str(cc*cns)
    cv2.putText(dst, text, (400,50),font, 1,(255,255,255))

def func(x, a, b, c):
    return a/(1+np.exp(-b*(x-c)))    


Define the geometric parameters for the object in the mask. The variable are described in the code. In [3]:

#
# Variable assignments 
#


nf=3446      # number of frames

shift=39/nf  #shift 
cc=0
count=0
ref=0

# Plot scaling 
dx=nf/323.
dy=0.2/240

dy1=40./100.
fr=184./(2*nf)
ns=304*60
cns=ns/nf

#
# Circle mask 
#

xc=440+350
yc=370
radius=350

#
# Initialize the two array to store the profiles 
#
avhist = np.zeros(400,np.uint8)
prof=np.zeros(400,np.uint8)

#
# Calculate the logistic function to overlap with 
# the profile of the color distribution 
#

xdata = np.linspace(0, 400, 400)
ydata = func(xdata, 135.7, 0.037, 156.8)
rows=0
cols=0
inx=0

# Open the video file 

cap = cv2.VideoCapture('IMG_2116.m4v')

ret, frame = cap.read()

#
# create the mask for the video
#

# Create a circular mask with center the subject.

# Extract from the frame using only the first 1200 pixel rows
part = frame[:,:1200]
rows,cols,cl =  part.shape
img_mask = np.zeros((rows,cols,3), np.uint8)
cv2.circle(img_mask, (xc,yc),radius, (255,255,255),-1) 
#
# Create mask for histogram
#
mask = np.zeros((rows,cols,3), np.uint8)
draw_half_circle_no_round(mask)

#
# Loop on the frames
#

while(cap.isOpened()):
    ret, frame = cap.read()
    if ret==True:
        if cc%2 ==0:

# Reshape the frame 

            part = frame[:,:1200]
#
# Rotate the frame using an affine trasfromation to better align the 
# step of the flower with the y axis
#
            M = np.float32([[1,0,230],[0,1,0]])
            trf = cv2.warpAffine(part,M,(cols,rows))

#            M = cv2.getRotationMatrix2D((cols/2,rows/2),-7,1)
#            trf = cv2.warpAffine(part,M,(cols,rows))
#
# Apply masks
#
            dst=img_mask&trf
            anaimg=mask&trf
            hist_mask = cv2.calcHist([anaimg],[0],None,[256],[0,256]) 

#
# Calculate and plot ROI histograms
#
            calchROI(ref)  
            plotHist()

#
# Draw the protractor scale
#
            protractor()
#
# draw clock
#
            ang=(fr*cc)-90
            angr=ang*np.pi/180
            drawclock()   
#
# Write the lapsed seconds
#
            printsecs()
#
# plot axes
#
            plotaxes(dst)

        cc+=1
        cv2.imshow('frame',dst)
#
# Save the single frames
#
        name = "FRAMES/frame%d.jpg"%count
        count+=1
        cv2.imwrite(name, dst) 
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
    else:
        break

cap.release()
cv2.destroyAllWindows()

About Danilo Roccatano

I have a Doctorate in chemistry at the University of Roma “La Sapienza”. I led educational and research activities at different universities in Italy, The Netherlands, Germany and now in the UK. I am fascinated by the study of nature with theoretical models and computational. For years, my scientific research is focused on the study of molecular systems of biological interest using the technique of Molecular Dynamics simulation. I have developed a server (the link is in one of my post) for statistical analysis at the amino acid level of the effect of random mutations induced by random mutagenesis methods. I am also very active in the didactic activity in physical chemistry, computational chemistry, and molecular modeling. I have several other interests and hobbies as video/photography, robotics, computer vision, electronics, programming, microscopy, entomology, recreational mathematics and computational linguistics.
This entry was posted in Leonardo's Corner, Programming, Science Topics, What is new. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.