Golang : GUI with Qt and OpenCV to capture image from camera




After the tutorial on how to display image with Qt and Golang. I need to build a Graphical User Interface(GUI) application with OpenCV and Qt to capture images from a camera. It is kinda similar to Apple's PhotoBooth application, but a less sophisticated version.

The Golang's OpenCV binding has a highgui package that allows GUI application creation, but lacking of buttons to customise further action - such as to trigger a save to file action or select different filtering algorithms. Sure, I can always build a web-based interface for OpenCV such as this surveillance tutorial, but I'm itching to learn some coding with Qt GUI framework.

The code that follows is my own attempt to create a GUI application to bring up the camera feed into a GUI application with OpenCV, have a slider to control the filter, a button to take a snapshot and finally a button to exit the GUI application.

Qt Golang OpenCV GUI application

NOTES:

By all means, I'm not an expert in creating Qt GUI(Graphical User Interface) application, so pardon me if you spot some silly methods in the code. The recommended way of interfacing with a camera using Qt is documented here(C++ source code) - http://doc.qt.io/qt-5/qtmultimedia-multimediawidgets-camera-example.html. For this example and the sake of simplicity, we will use OpenCV to interface with the camera and a goroutine to update the images.

!! Please follow up the setup instruction at https://github.com/therecipe/qt to setup Qt. !!

Here you go!


 package main

 import (
  "bytes"
  "fmt"
  "github.com/lazywei/go-opencv/opencv"
  "github.com/therecipe/qt/core"
  "github.com/therecipe/qt/gui"
  "github.com/therecipe/qt/widgets"
  "image/jpeg"
  "os"
  "strings"
 )

 var (
  positionsSlider  *widgets.QSlider
  sliderPos int
  displayArea *widgets.QWidget
  scene *widgets.QGraphicsScene
  view *widgets.QGraphicsView
  item *widgets.QGraphicsPixmapItem
  mainApp *widgets.QApplication
  snapshotFileName string
  webCamera = new(opencv.Capture)
  qImg = new(gui.QImage)
  cameraFeedLabel  *widgets.QLabel
  opt = jpeg.Options{90} // for the best color, use png instead of jpeg
  stopCamera = false // to prevent segmentation fault
  snapPhoto = false
  width, height int
 )

 // credits to https://github.com/lazywei/go-opencv/blob/master/samples/webcam.go
 func applyFilters(img *opencv.IplImage) opencv.IplImage {
  w := img.Width()
  h := img.Height()

  // Create the output image
  cedge := opencv.CreateImage(w, h, opencv.IPL_DEPTH_8U, 3)
  defer cedge.Release()

  // Convert to grayscale
  gray := opencv.CreateImage(w, h, opencv.IPL_DEPTH_8U, 1)
  edge := opencv.CreateImage(w, h, opencv.IPL_DEPTH_8U, 1)
  defer gray.Release()
  defer edge.Release()

  opencv.CvtColor(img, gray, opencv.CV_BGR2GRAY)

  opencv.Smooth(gray, edge, opencv.CV_BLUR, 3, 3, 0, 0)
  opencv.Not(gray, edge)

  // Run the edge detector on grayscale
  opencv.Canny(gray, edge, float64(sliderPos), float64(sliderPos*3), 3)

  opencv.Zero(cedge)
  // copy edge points
  opencv.Copy(img, cedge, edge)

  return *cedge
 }

 func processFrameAndUpdate() {

  for {

 if webCamera.GrabFrame() && !stopCamera {
 IplImgFrame := webCamera.RetrieveFrame(1)
 if IplImgFrame != nil {
 // Have to make the image frame
 // smaller, so that it does not blow up the GUI application.
 IplImgFrame = opencv.Resize(IplImgFrame, 0, height-200, opencv.CV_INTER_LINEAR)

 // apply filters
 *IplImgFrame = applyFilters(IplImgFrame)
 //applyFilters(IplImgFrame)

 // snap photo
 if snapPhoto {
 fmt.Println("Snapshot save to : ", snapshotFileName)
 // params - to set jpeg quality
 // but unable to pass params []int slice to opencv...

 opencv.SaveImage(snapshotFileName, IplImgFrame, opencv.CV_IMWRITE_JPEG_QUALITY)

 snapPhoto = false // reset to false again
 }

 // slower than the openCV's showImage()
 // simply because we need to convert to QImage

 goImgFrame := IplImgFrame.ToImage()
 frameBuffer := new(bytes.Buffer)
 err := jpeg.Encode(frameBuffer, goImgFrame, &opt)

 if err != nil {
 panic(err)
 }

 qImg := gui.QImage_FromData2(frameBuffer.String(), "")

 cameraFeedLabel.SetPixmap(gui.QPixmap_FromImage(qImg, core.Qt__AutoColor))

 }
 }

  }

 }

 func setPosition(position int) {
  sliderPos = position
 }

 func mainGUI() *widgets.QWidget {
  scene = widgets.NewQGraphicsScene(nil)
  view = widgets.NewQGraphicsView(nil)
  displayArea = widgets.NewQWidget(nil, 0)
  cameraFeedLabel = widgets.NewQLabel(nil, core.Qt__Widget)

  // use the initial frame to populate the layout
  if webCamera.GrabFrame() {
 IplImgFrame := webCamera.RetrieveFrame(1)
 if IplImgFrame != nil {

 // Have to make the image frame
 // smaller, so that it does not blow up the GUI application.
 IplImgFrame = opencv.Resize(IplImgFrame, 0, height-200, opencv.CV_INTER_LINEAR)

 // convert IplImage(Intel Image Processing Library)
 // to QImage via image.Image->[]byte->QPixmap->QLabel
 // I'm pretty sure there are more efficient method out there
 // but I'm just too lazy to find out the number of channels, blah blah...
 // and also at the time of writing ... go-opencv does not have CV_BGR2RGB :(

 // such as this C++ code from
 // https://decibel.ni.com/content/blogs/kl3m3n/2015/06/09/opencv-and-qt-based-gui-hough-circle-detection-example
 // cv::cvtColor(original,original,CV_BGR2RGB);
 // QImage qimgOriginal((uchar*) original.data,original.cols,original.rows,original.step,QImage::Format_RGB888); // for color images
 // QImage qimgProcessed((uchar*) processed.data,processed.cols,processed.rows,processed.step,QImage::Format_Indexed8); // for grayscale images
 // or
 // QImage qt_img = ( QImage ( cv_img->dataIm, cv_img->width, cv_img->height, cv_img->QImage::Format_RGB888 ) ).rgbSwapped();

 goImgFrame := IplImgFrame.ToImage()

 // and then convert to []byte
 // with the help of jpeg.Encode() function

 frameBuffer := new(bytes.Buffer)

 err := jpeg.Encode(frameBuffer, goImgFrame, &opt)

 if err != nil {
 panic(err)
 }

 qImg := gui.QImage_FromData2(frameBuffer.String(), "")
 cameraFeedLabel.SetPixmap(gui.QPixmap_FromImage(qImg, core.Qt__AutoColor))
 scene.AddWidget(cameraFeedLabel, core.Qt__Widget)

 // The proper way to do a Qt GUI application for camera is to use
 // QtMultimedia widgets, but for simplicity sake,
 // let's do the "traditional" openCV way for this example.

 }
  }

  view.SetScene(scene)

  // create a slider
  positionsSlider = widgets.NewQSlider2(core.Qt__Horizontal, nil)
  positionsSlider.SetRange(0, 100)
  positionsSlider.SetTickInterval(10)
  positionsSlider.SetValue(50)
  positionsSlider.ShowMaximized()

  positionsSlider.ConnectSliderMoved(setPosition)

  //create a button and connect the clicked signal
  var snapButton = widgets.NewQPushButton2("Take a snapshot", nil)
  snapButton.ConnectClicked(func(flag bool) {

 snapPhoto = true

  })

  //create a button and connect the clicked signal
  var quitButton = widgets.NewQPushButton2("Quit", nil)
  quitButton.ConnectClicked(func(flag bool) {

 stopCamera = true
 webCamera.Release() // don't forget to release !!
 mainApp.Quit()
  })

  var layout = widgets.NewQVBoxLayout()

  layout.AddWidget(view, 0, core.Qt__AlignCenter)
  layout.AddWidget(positionsSlider, 0, core.Qt__AlignCenter)

  layout.AddWidget(snapButton, 0, core.Qt__AlignCenter)
  layout.AddWidget(quitButton, 0, core.Qt__AlignRight)

  displayArea.SetLayout(layout)

  return displayArea
 }

 func main() {

  if len(os.Args) != 2 {
 fmt.Printf("Usage : %s <save snapshot filename>\n", os.Args[0])
 os.Exit(0)
  }

  snapshotFileName = os.Args[1]

  if !strings.HasSuffix(snapshotFileName, ".jpg") {
 snapshotFileName += ".jpg"
  }

  fmt.Println("Will be saving snapshot image to : ", snapshotFileName)

  // warming up our camera
  webCamera = opencv.NewCameraCapture(0)

  if webCamera == nil {
 panic("Unable to open camera")
  }

  // get some data from camera
  width = int(webCamera.GetProperty(opencv.CV_CAP_PROP_FRAME_WIDTH))
  height = int(webCamera.GetProperty(opencv.CV_CAP_PROP_FRAME_HEIGHT))

  fmt.Println("Camera width : ", width)
  fmt.Println("Camera height : ", height)

  mainApp = widgets.NewQApplication(len(os.Args), os.Args)

  mainGUI().SetWindowTitle("Golang Qt OpenCV GUI application example")
  mainGUI().SetWindowState(core.Qt__WindowMaximized) // maximized on start
  mainGUI().Show()

  go processFrameAndUpdate() // goroutine to update feed from camera

  widgets.QApplication_Exec()

 }

Further reading ---> http://blog.qt.io/blog/2015/03/20/introducing-video-filters-in-qt-multimedia/

References:

https://github.com/lazywei/go-opencv/blob/master/samples/webcam.go

http://stackoverflow.com/questions/11543298/qt-opencv-displaying-images-on-qlabel

https://github.com/therecipe/qt/blob/master/internal/examples/widgets/videoplayer/videoplayer.go

https://decibel.ni.com/content/blogs/kl3m3n/2015/06/09/opencv-and-qt-based-gui-hough-circle-detection-example

https://github.com/therecipe/qt

https://www.socketloop.com/tutorials/golang-edge-detection-with-sobel-method

  See also : Golang : Surveillance with web camera and OpenCV





By Adam Ng

IF you gain some knowledge or the information here solved your programming problem. Please consider donating to the less fortunate or some charities that you like. Apart from donation, planting trees, volunteering or reducing your carbon footprint will be great too.


Advertisement