Wednesday, January 6, 2021

Feature Detection Using Python GUI (PyQt) Part 1

This content is powered by Balige PublishingVisit this link (collaboration with Rismon Hasiholan Sianipar)

In this tutorial, you will learn how to use OpenCV, NumPy library and other libraries to perform feature extraction with Python GUI (PyQt). The feature detection techniques used in this chapter are Harris Corner Detection, Shi-Tomasi Corner Detector, Scale-Invariant Feature Transform (SIFT), Speeded-Up Robust Features (SURF), Features from Accelerated Segment Test (FAST), Binary Robust Independent Elementary Features (BRIEF), and Oriented FAST and Rotated BRIEF (ORB).



Tutorial Steps To Detect Image Features Using Harris Corner Detection

In this sub chapter, you will find corners that was done by Chris Harris & Mike Stephens in their paper A Combined Corner and Edge Detector in 1988.  

OpenCV has the function cv.cornerHarris() for this purpose, whose arguments are:


The script below implement feature detection using Harris Corner Detection algorithm:

#harris_corner.py
import numpy as np
import cv2 as cv

filename = 'chessboard.png'
img = cv.imread(filename)

gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
gray = np.float32(gray)

dst = cv.cornerHarris(gray,3,5,0.04)

#dilated for marking the corners
dst = cv.dilate(dst,None)

# Threshold for an optimal value, it may vary depending on the image.
img[dst>0.01*dst.max()]=[0,0,255]

cv.imshow('dst',img)

if cv.waitKey(0) & 0xff == 27:
    cv.destroyAllWindows()

Run the script to see detected features as shown in Figure below.



To find the corners with maximum accuracy. OpenCV comes has a function named cv.cornerSubPix() that refines the corners detected with sub-pixel accuracy. 

Below is an example. As usual, we need to find the Harris corners first. Then we pass the centroids of these corners (There may be a bunch of pixels at a corner, we take their centroid) to refine them. Harris corners are marked in red pixels and refined corners are marked in green pixels. For this function, we have to define the criteria when to stop the iteration. We stop it after a specified number of iterations or a certain accuracy is achieved, whichever occurs first. We also need to define the size of the neighbourhood it searches for corners.

#harris_corner_accurate.py
import numpy as np
import cv2 as cv
filename = 'chessboard.png'
img = cv.imread(filename)
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
# find Harris corners
gray = np.float32(gray)
dst = cv.cornerHarris(gray,5,5,0.04)
dst = cv.dilate(dst,None)
ret, dst = cv.threshold(dst,0.01*dst.max(),255,0)
dst = np.uint8(dst)
# find centroids
ret, labels, stats, centroids = cv.connectedComponentsWithStats(dst)
# define the criteria to stop and refine the corners
criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER,\
100, 0.001)
corners = cv.cornerSubPix(gray,np.float32(centroids),\
(5,5),(-1,-1),criteria)
# Now draw them
res = np.hstack((centroids,corners))
res = np.int0(res)
img[res[:,1],res[:,0]]=[0,0,255]
img[res[:,3],res[:,2]] = [0,255,0]
cv.imshow('dst',img)
if cv.waitKey(0) & 0xff == 27:
cv.destroyAllWindows()


Now, you will design Python GUI to implement Harris Corner Detection with Qt Designer.

Create a new form using Qt Designer with Main Window template. Set its name as feature_detection.ui.

Add two new Label widgets and set their objectName properties as labelImage and labelResult. Then, add two more Label widgets and set their text properties as Original Image and Detected Features.

Add one Push Button widget onto form, set its ObjectName property as pbReadImage, and set its text property as Read Image. The form now looks as shown in Figure below.


Create a new Python script and name it as feature_detection.py. Write the basic content of the script as follows:

#feature_detection.py
import sys
import cv2
import numpy as np
from PyQt5.QtWidgets import*
from PyQt5 import QtGui, QtCore
from PyQt5.uic import loadUi
from matplotlib.backends.backend_qt5agg import (NavigationToolbar2QT as NavigationToolbar)
from PyQt5.QtWidgets import QDialog, QFileDialog
from PyQt5.QtGui import QIcon, QPixmap, QImage
from PyQt5.uic import loadUi
class FormFeatureDetection(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
loadUi("feature_detection.ui",self)
self.setWindowTitle("Feature Detection")
if __name__=="__main__":
app = QApplication(sys.argv)
w = FormFeatureDetection()
w.show()
sys.exit(app.exec_())

Define a new method, read_image(), to open file dialog, read file name, and display it in labelImage widget as follows:

def read_image(self):
self.fname = QFileDialog.getOpenFileName(self, 'Open file',
'd:\\',"Image Files (*.jpg *.gif *.bmp *.png)")
self.pixmap = QPixmap(self.fname[0])
self.labelImage.setPixmap(self.pixmap)
self.labelImage.setScaledContents(True)
self.img = cv2.imread(self.fname[0], cv2.IMREAD_COLOR)

The img variable is then saved in self parameter of the FormFeatureDetection class.

Connect the clicked event of pbReadImage widget to readImage() method and put it inside __init_() method:

self.pbReadImage.clicked.connect(self.read_image)

Run feature_detection.py then click Read Image button to see displayed image in labelImage widget as shown in Figure below.


In feature_detection.ui form, put one Combo Box onto form and its objectName property to cboFeature. Double click on this widget and populate it with seven items as shown in Figure below.


Put one Group Box widget onto form and set its objectName property as gbHarris. Inside the group box, put four Line Edit widgets and set each objectName property to leBlockSize, leKSize, leK, and leThreshold. Set its text property of each to 2, 3, 0.04, and 0.01.

Then, put four Horizontal Slider widgets and set their objectName properties to hsBlockSize, hsKSize, hsK, and hsThreshold. Set minimum property of hsBlockSize and hsKSize widgets to 3. Set maximum property of hsKSize to 31.
Set singleStep property of hsKSize widget to 2. Set value property of hsBlockSize to 2, that of hsKSize to 3, that of hsK to 4, and that of hsThreshold to 1.

Run feature_detection.py then click Read Image button to see displayed image in labelImage widget as shown in Figure below.


In feature_detection.ui form, put one Combo Box onto form and its objectName property to cboFeature. Double click on this widget and populate it with seven items as shown in Figure below.


Save the form as feature_detection.ui. Now, the form looks as shown in Figure below.


Define a new method named initialization() to set state of some widgets as follows:

def initialization(self,state):
    self.cboFeature.setEnabled(state)
    self.gbHarris.setEnabled(state)

Invoke initialization() method from inside __init__() to disable widgets when form starts:

def __init__(self):
    QMainWindow.__init__(self)
    loadUi("feature_detection.ui",self)
    self.setWindowTitle("Feature Detection")
    self.pbReadImage.clicked.connect(self.read_image)
    self.initialization(False)

Modify read_image() to enable cboFeature widget as shown in line 8:

def read_image(self):
    self.fname = QFileDialog.getOpenFileName(self, 'Open file', \
        'd:\\',"Image Files (*.jpg *.gif *.bmp *.png)")
    self.pixmap = QPixmap(self.fname[0])        
    self.labelImage.setPixmap(self.pixmap)
    self.labelImage.setScaledContents(True)
    self.img = cv2.imread(self.fname[0], cv2.IMREAD_COLOR)  
    self.cboFeature.setEnabled(True)

Run feature_detection.py. Make sure some widgets disabled when form first starts as shown in Figure below.

Then open one image, then you will see the image and makes sure cboFeature widget enabled as shown in Figure below.

Define a new method, choose_feature(), to read current text chosen and perform relevant task as follows:

def choose_feature(self):        
    strCB = self.cboFeature.currentText()
        
    if strCB == 'Harris Corner Detection':
        self.gbHarris.setEnabled(True)

Connect currentIndexChanged() of cboFeature widget to choose_feature() and place it inside __init__() method as follows:

self.cboFeature.currentIndexChanged.connect(self.choose_feature)

Define four functions to display each slider handle value in Line Edit widget:

def set_hsBlockSize(self, value):
    self.leBlockSize.setText(str(value))
        
def set_hsKSize(self, value):
    self.leKSize.setText(str(value))
        
def set_hsK(self, value):
    self.leK.setText(str(round((value/100),2)))

def set_hsThreshold(self, value):
    self.leThreshold.setText(str(round((value/100),2)))

Connect valueChanged event of hsBlockSize widget with set_hsBlockSize() method, that of hsKSize widget with set_hsKSize(), that of hsK widget with set_hsK(), and that of hsThreshold with set_hsThreshold(). Put them inside __init__() method:

def __init__(self):
    QMainWindow.__init__(self)
    loadUi("feature_detection.ui",self)
    self.setWindowTitle("Feature Detection")
    self.pbReadImage.clicked.connect(self.read_image)
    self.initialization(False)
    self.cboFeature.currentIndexChanged.connect(self.choose_feature)
    self.hsBlockSize.valueChanged.connect(self.set_hsBlockSize)
    self.hsKSize.valueChanged.connect(self.set_hsKSize)
    self.hsK.valueChanged.connect(self.set_hsK)
    self.hsThreshold.valueChanged.connect(self.set_hsThreshold)

Run feature_detection.py. Open an image, choose Harris Corner Detection from combo box, and drag the four horizontal sliders as you like. You will see the value in line edit widgets change as shown in Figure below.

Define display_image() method to display an image on a Label widget as follows:

def display_image(self, img, label):
    height, width, channel = img.shape
    bytesPerLine = 3 * width  
        
    qImg = QImage(img, width, height, \
                  bytesPerLine, QImage.Format_RGB888)
    pixmap = QPixmap.fromImage(qImg)
    label.setPixmap(pixmap)
    label.setScaledContents(True)

Define harris_detection() method to perform Harris Corner Detection technique as follows:

def harris_detection(self):
    self.test_im = self.img.copy()
    gray = cv2.cvtColor(self.test_im,cv2.COLOR_BGR2GRAY) 
    gray = np.float32(gray)
    blockSize = int(self.leBlockSize.text())
    kSize = int(self.leKSize.text())
    K = float(self.leK.text())
    dst = cv2.cornerHarris(gray,blockSize,kSize,K)

    #dilated for marking the corners
    dst = cv2.dilate(dst,None)

    # Threshold for an optimal value,
    Thresh = float(self.leThreshold.text())
    self.test_im[dst>Thresh*dst.max()]=[0,0,255] 
    cv2.cvtColor(self.test_im, cv2.COLOR_BGR2RGB, self.test_im)
    self.display_image(self.test_im, self.labelResult)

Modify these four methods to invoke harris_detection() as follows:

def set_hsBlockSize(self, value):
    self.leBlockSize.setText(str(value))
    self.harris_detection()
        
def set_hsKSize(self, value):
    self.leKSize.setText(str(value))
    self.harris_detection()
        
def set_hsK(self, value):
    self.leK.setText(str(round((value/100),2)))
    self.harris_detection()

def set_hsThreshold(self, value):
    self.leThreshold.setText(str(round((value/100),2)))
    self.harris_detection()

Modify choose_feature() to invoke harris_detection() method when user choose Harris Corner Detection from combo box:

def choose_feature(self):        
    strCB = self.cboFeature.currentText()
        
    if strCB == 'Harris Corner Detection':
        self.gbHarris.setEnabled(True)
        self.harris_detection()

Run feature_detection.py, open an image, and choose Harris Corner Detection from cboFeature. Choose the value of each parameter and see the result as shown in Figure below.

Below is the full script of feature_detection.py so far:

#feature_detection.py
import sys
import cv2
import numpy as np
from PyQt5.QtWidgets import*
from PyQt5 import QtGui, QtCore
from PyQt5.uic import loadUi
from matplotlib.backends.backend_qt5agg import (NavigationToolbar2QT as NavigationToolbar)
from PyQt5.QtWidgets import QDialog, QFileDialog
from PyQt5.QtGui import QIcon, QPixmap, QImage
from PyQt5.uic import loadUi

class FormFeatureDetection(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)
        loadUi("feature_detection.ui",self)
        self.setWindowTitle("Feature Detection")
        self.pbReadImage.clicked.connect(self.read_image)
        self.initialization(False)
        self.cboFeature.currentIndexChanged.connect(self.choose_feature)
        self.hsBlockSize.valueChanged.connect(self.set_hsBlockSize)
        self.hsKSize.valueChanged.connect(self.set_hsKSize)
        self.hsK.valueChanged.connect(self.set_hsK)
        self.hsThreshold.valueChanged.connect(self.set_hsThreshold)

    def read_image(self):
        self.fname = QFileDialog.getOpenFileName(self, 'Open file', \
            'd:\\',"Image Files (*.jpg *.gif *.bmp *.png)")
        self.pixmap = QPixmap(self.fname[0])        
        self.labelImage.setPixmap(self.pixmap)
        self.labelImage.setScaledContents(True)
        self.img = cv2.imread(self.fname[0], cv2.IMREAD_COLOR)  
        self.cboFeature.setEnabled(True)

    def initialization(self,state):
        self.cboFeature.setEnabled(state)
        self.gbHarris.setEnabled(state)

    def set_hsBlockSize(self, value):
        self.leBlockSize.setText(str(value))
        self.harris_detection()
        
    def set_hsKSize(self, value):
        self.leKSize.setText(str(value))
        self.harris_detection()
        
    def set_hsK(self, value):
        self.leK.setText(str(round((value/100),2)))
        self.harris_detection()

    def set_hsThreshold(self, value):
        self.leThreshold.setText(str(round((value/100),2)))
        self.harris_detection()

    def choose_feature(self):        
        strCB = self.cboFeature.currentText()
        
        if strCB == 'Harris Corner Detection':
            self.gbHarris.setEnabled(True)
            self.harris_detection()

    def harris_detection(self):
        self.test_im = self.img.copy()
        gray = cv2.cvtColor(self.test_im,cv2.COLOR_BGR2GRAY) 
        gray = np.float32(gray)
        blockSize = int(self.leBlockSize.text())
        kSize = int(self.leKSize.text())
        K = float(self.leK.text())
        dst = cv2.cornerHarris(gray,blockSize,kSize,K)

        #dilated for marking the corners
        dst = cv2.dilate(dst,None)

        # Threshold for an optimal value, it may vary depending on the image.
        Thresh = float(self.leThreshold.text())
        self.test_im[dst>Thresh*dst.max()]=[0,0,255] 
        cv2.cvtColor(self.test_im, cv2.COLOR_BGR2RGB, self.test_im)
        self.display_image(self.test_im, self.labelResult)

    def display_image(self, img, label):
        height, width, channel = img.shape
        bytesPerLine = 3 * width  
        
        qImg = QImage(img, width, height, \
                      bytesPerLine, QImage.Format_RGB888)
        pixmap = QPixmap.fromImage(qImg)
        label.setPixmap(pixmap)
        label.setScaledContents(True)
        
if __name__=="__main__":
    app = QApplication(sys.argv)    
    w = FormFeatureDetection()
    w.show()
    sys.exit(app.exec_())

Feature Detection Using Python GUI (PyQt) Part 2



No comments:

Post a Comment