Golang : Put UTF8 text on OpenCV video capture image frame




The HERSHEY font use by OpenCV does not support UTF-8 characters. If you use my previous tutorial on how to put text on OpenCV video capture image with PutText() function with Chinese, Russian, Greek, Japanese or Korean characters(for example), it will not be able to display those characters properly. Instead it will show up as ????.


broken utf8 characters on openCV ????


For this tutorial, we will learn how to place UTF-8 characters onto OpenCV video capture. We will use Nigel Tao's example on how to load True Type Font and draw the UTF8 strings on an image and paste the image onto the video capture image frames. Such as :


proper UTF8 characters pasted on OpenCV videocapture image frames


To get the code example below running, first download the True Type Font at http://www.slackware.com/~alien/slackbuilds/wqy-zenhei-font-ttf/build/wqy-zenhei-0.4.23-1.tar.gz, unzip and place the wqy-zenhei.ttf file in the same folder.

Read the notes after the source code too. Especially on the languages covered by the wqy-zenhei.ttf.

Execute go get -u on these packages if you haven't done so.

 "github.com/lazywei/go-opencv/opencv"
 "github.com/mattn/go-gtk/glib"
 "github.com/mattn/go-gtk/gtk"
 "github.com/golang/freetype"
 "github.com/golang/freetype/truetype"

Here you go!


 package main

 import (
  "fmt"
  "image"
  "image/color"
  "image/draw"
  "io/ioutil"
  "log"
  "runtime"
  "strconv"
  "time"

  "github.com/lazywei/go-opencv/opencv"
  "github.com/mattn/go-gtk/glib"
  "github.com/mattn/go-gtk/gtk"

  "github.com/golang/freetype"
  "github.com/golang/freetype/truetype"
 )

 var (
  win = new(opencv.Window)
  webCamera = new(opencv.Capture)
  statusbar = new(gtk.Statusbar)
  snapshotFileName string
  width, height int
  sliderPosX, sliderPosY int
  stopCamera = false // to prevent segmentation fault
  backgroundWidth = 650
  backgroundHeight = 150
  horizontalScale = float32(1.0)
  verticalScale = float32(1.0)
  shear = float32(1.0)
  thickness = 3
  lineType = 8
  textFont = opencv.InitFont(opencv.CV_FONT_HERSHEY_SIMPLEX, horizontalScale, verticalScale, shear, thickness, lineType)
  utf8FontFile = "wqy-zenhei.ttf"
  utf8FontSize = float64(30.0)
  spacing = float64(1.5)
  dpi = float64(72)
  ctx = new(freetype.Context)
  utf8Font = new(truetype.Font)
  IplImgFrame, utf8TextImg *opencv.IplImage
  redColor = opencv.NewScalar(0, 0, 255, 0) // red - (blue, green, red, alpha)
  cyanColor = opencv.NewScalar(255, 255, 0, 0) // cyan - (blue, green, red, alpha)
  red = color.RGBA{255, 0, 0, 255}
  blue = color.RGBA{0, 0, 255, 255}
  white = color.RGBA{255, 255, 255, 255}
  black = color.RGBA{0, 0, 0, 255}
  background *image.RGBA
  // more color at https://github.com/golang/image/blob/master/colornames/table.go
 )

 func opencvImageBGRToBGRA(img *opencv.IplImage) opencv.IplImage {
  // The image frames from camera is in RGB (3 channels )
  // We need to convert the frames to RGBA (4 channels )
  // so that we can perform copy and paste the UTF8 strings
  // into the region of interest.
  // Using the ToImage() function will work, but will cause delay in refresh rate.
  // Use CvtColor() function for the best result

  //fmt.Println("img channels : ", img.Channels())

  w := img.Width()
  h := img.Height()

  // create a IplImage with 4 channels
  tmp := opencv.CreateImage(w, h, opencv.IPL_DEPTH_8U, 4)

  // upgrade BGR to BGRA ( 3 to 4 channels)
  opencv.CvtColor(img, tmp, opencv.CV_BGR2BGRA)
  //fmt.Println("tmp channels : ", tmp.Channels())
  return *tmp

 }

 func BGRAToBGR(img *opencv.IplImage) opencv.IplImage {

  w := img.Width()
  h := img.Height()

  // create a IplImage with 3 channels
  tmp := opencv.CreateImage(w, h, opencv.IPL_DEPTH_8U, 3)

  // downgrade BGRA to BGR ( 4 to 3 channels)
  opencv.CvtColor(img, tmp, 1)
  // why use integer value of 1?
  // see http://docs.opencv.org/3.1.0/df/d4e/group__imgproc__c.html
  return *tmp

 }

 func processFrameAndUpdate() {

  var UTF8text = []string{
 `©, 世界,Мир,World,세계,Dunia,κόσμος`,
 `全世界 愛Go語, Весь мир любовь golang`,
 `Όλος ο κόσμος αγάπη golang,전세계 Go언어`,
  }

  // Draw the text to the background
  pt := freetype.Pt(10, 10+int(ctx.PointToFixed(utf8FontSize)>>6))

  // not all utf8 fonts are supported by wqy-zenhei.ttf
  // use your own language true type font file if your language cannot be printed

  for _, str := range UTF8text {
 _, err := ctx.DrawString(str, pt)
 if err != nil {
 fmt.Println(err)
 return
 }
 pt.Y += ctx.PointToFixed(utf8FontSize * spacing)
  }

  // convert background from image.Image type to opencv.IplImage
  utf8TextImg = opencv.FromImage(background)

  // after converting to opencv IplImage...the transparency is gone!
  // test := utf8TextImg.ToImage()

  // any way to make the UTF8 string to have transparent background ??
  // maybe http://stackoverflow.com/questions/32350604/create-a-transparent-image-in-opencv

  for {

 if webCamera.GrabFrame() && !stopCamera {
 IplImgFrame = webCamera.RetrieveFrame(1)
 if IplImgFrame != nil {

 // we need to convert IplImgFrame number of
 // channels to 4 instead of 3.
 // otherwise the opencv.Copy() function below will crash...

 *IplImgFrame = opencvImageBGRToBGRA(IplImgFrame)

 // see https://www.socketloop.com/tutorials/golang-get-current-time
 currentTime := time.Now().Local().Format("2006-01-02 15:04:05 +0800")

 // set ROI(Region Of Interest) in IplImageFrame
 rect := opencv.NewRect(sliderPosX, sliderPosY, backgroundWidth, backgroundHeight)
 IplImgFrame.SetROI(rect)

 // copy and paste our UTF8 runes into ROI via Copy
 opencv.Copy(utf8TextImg, IplImgFrame, nil)
 IplImgFrame.ResetROI() // don't forget this!
 opencv.Rectangle(IplImgFrame,
 opencv.Point{sliderPosX + backgroundWidth, sliderPosY},
 opencv.Point{sliderPosX, sliderPosY + backgroundHeight},
 opencv.ScalarAll(0.0), 2, 2, 0)

 //position := opencv.Point{sliderPosX, sliderPosY}
 //textFont.PutText(IplImgFrame, "Hello World!", position, redColor)
 textFont.PutText(IplImgFrame, currentTime, opencv.Point{sliderPosX, sliderPosY + int(verticalScale*200.0)}, cyanColor)

 win.ShowImage(IplImgFrame)

 }
 }

  }

 }

 func main() {

  cores := runtime.NumCPU()

  fmt.Printf("This machine has %d CPU cores. Using all cores. \n", cores)

  // maximize CPU usage for maximum performance
  runtime.GOMAXPROCS(cores)

  // download font from http://www.slackware.com/~alien/slackbuilds/wqy-zenhei-font-ttf/build/wqy-zenhei-0.4.23-1.tar.gz
  // extract wqy-zenhei.ttf to the same folder as this program

  // Read the font data - for this example, we load the Chinese fontfile wqy-zenhei.ttf,
  // but it will display any utf8 fonts such as Russian, Japanese, Korean, etc as well.
  // some utf8 fonts cannot be displayed. You need to use your own language .ttf file
  fontBytes, err := ioutil.ReadFile(utf8FontFile)
  if err != nil {
 log.Println(err)
 return
  }

  utf8Font, err = freetype.ParseFont(fontBytes)
  if err != nil {
 log.Println(err)
 return
  }

  // we use red on white to make our text more visible in the camera video feed
  // if you plan to use the fontForeGroundColor only. Setting background to transparent with image.Transparent
  // will cause it to appear as black on camera feed. Why? because the OpenCV camera video capture images' color model cannot
  // support transparency. See https://blog.golang.org/go-imagedraw-package.

  //fontForeGroundColor, fontBackGroundColor := image.NewUniform(white), image.Transparent

  fontForeGroundColor, fontBackGroundColor := image.NewUniform(red), image.NewUniform(white)

  background = image.NewRGBA(image.Rect(0, 0, backgroundWidth, backgroundHeight))

  draw.Draw(background, background.Bounds(), fontBackGroundColor, image.ZP, draw.Src)

  ctx = freetype.NewContext()
  ctx.SetDPI(dpi) //screen resolution in Dots Per Inch
  ctx.SetFont(utf8Font)
  ctx.SetFontSize(utf8FontSize) //font size in points
  ctx.SetClip(background.Bounds())
  ctx.SetDst(background)
  ctx.SetSrc(fontForeGroundColor)
  // the rest will be inside processFrameAndUpdate() function

  // a new OpenCV window
  win = opencv.NewWindow("Display UTF8 text or runes on Go-OpenCV camera feed")
  defer win.Destroy()

  // activate webCamera
  webCamera = opencv.NewCameraCapture(opencv.CV_CAP_ANY) // autodetect

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

  defer webCamera.Release()

  // 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)

  // open up a new "pure" OpenCV window first
  go processFrameAndUpdate() // goroutine to update feed from camera

  // then our "floating" GTK toolbar
  gtk.Init(nil)
  window := gtk.NewWindow(gtk.WINDOW_TOPLEVEL)

  window.SetPosition(gtk.WIN_POS_CENTER)
  window.SetTitle("Example of writing UTF8 text on Go-OpenCV video capture!")
  window.SetIconName("gtk-dialog-info")
  window.Connect("destroy", func(ctx *glib.CallbackContext) {
 println("got destroy!", ctx.Data().(string))
 gtk.MainQuit()
  }, "Happy coding!")

  vbox := gtk.NewVBox(false, 1)

  //--------------------------------------------------------
  // GtkVPaned
  //--------------------------------------------------------
  vpaned := gtk.NewVPaned()
  vbox.Add(vpaned)

  //--------------------------------------------------------
  // GtkFrame
  //--------------------------------------------------------

  frame1 := gtk.NewFrame("Adjust X & Y co-ordinates to place the text location :")
  framebox1 := gtk.NewVBox(false, 1)
  frame1.Add(framebox1)

  //--------------------------------------------------------
  // GtkScale
  //--------------------------------------------------------
  scaleXHBox := gtk.NewHBox(false, 1)

  scaleX := gtk.NewHScaleWithRange(0, float64(width), 1)
  scaleX.Connect("value-changed", func() {
 //println("scale:", int(scale.GetValue()))
 sliderPosX = int(scaleX.GetValue())
 statusbar.Push(statusbar.GetContextId("go-gtk"), "X : "+strconv.Itoa(sliderPosX)+" Y : "+strconv.Itoa(sliderPosY))
  })
  scaleXHBox.Add(scaleX)
  framebox1.PackStart(scaleXHBox, false, false, 0)

  scaleYHBox := gtk.NewHBox(false, 1)

  scaleY := gtk.NewHScaleWithRange(0, float64(height), 1)
  scaleY.Connect("value-changed", func() {
 //println("scale:", int(scale.GetValue()))
 sliderPosY = int(scaleY.GetValue())
 statusbar.Push(statusbar.GetContextId("go-gtk"), "X : "+strconv.Itoa(sliderPosX)+" Y : "+strconv.Itoa(sliderPosY))
  })
  scaleYHBox.Add(scaleY)
  framebox1.PackStart(scaleYHBox, false, false, 0)

  vpaned.Pack1(frame1, false, false)

  //--------------------------------------------------------
  // GtkHBox
  //--------------------------------------------------------
  buttons := gtk.NewHBox(false, 1)

  //--------------------------------------------------------
  // GtkButton
  //--------------------------------------------------------

  quitButton := gtk.NewButtonWithLabel("Quit")
  quitButton.Clicked(func() {
 stopCamera = true
 webCamera.Release() // don't forget to release !!
 gtk.MainQuit()
  })

  buttons.Add(quitButton)
  framebox1.PackStart(buttons, false, false, 0)

  //--------------------------------------------------------
  // GtkVSeparator
  //--------------------------------------------------------
  vsep := gtk.NewVSeparator()
  framebox1.PackStart(vsep, false, false, 0)

  statusbar = gtk.NewStatusbar()
  //context_id := statusbar.GetContextId("go-gtk")

  //--------------------------------------------------------
  // GtkStatusbar
  //--------------------------------------------------------
  framebox1.PackStart(statusbar, false, false, 0)

  //--------------------------------------------------------
  // Event
  //--------------------------------------------------------
  window.Add(vbox)
  window.SetSizeRequest(600, 128)
  window.ShowAll()

  gtk.Main()

 }

NOTES:

If the link for wqy-zenhei.ttf is not available, you can download it here.

Is there another way to use PutText() with UTF-8 characters? Yeah, see http://blog.csdn.net/fengbingchun/article/details/8029337 ( in Chinese language and C++ ) by [email protected] - the original developer of Go-OpenCV package.

One problem that I still could not solve is how to make the UTF-8 strings to have a transparent background. At this moment, it is good enough for me because I will use the UTF-8 characters as a label on top of a rectangle. Maybe I'll solve this next in future.

Do not scroll the copy and paste image beyond the border of the video capture. Otherwise, the program will crash because of the opencv.Copy() function! :P

Extracted from wqy-zenhei's README on language coverage.

 ----------------------------------------------------------

  III. Language Coverage

 The following table is based on the locale data provided by fontconfig
 (generated by langcover.pl from Dejavu Project
 http://dejavu.sourceforge.net/wiki/index.php/Font_utilities).

 ZenHei 
 aa Afar 100% (62/62) 
 ab Abkhazia 75% (68/90) 
 af Afrikaans 100% (69/69) 
 am Amharic (0/264) 
 ar Arabic (0/125) 
 as (0/89) 
 ast Asturian 100% (72/72) 
 ava Avaric 100% (67/67) 
 ay Aymara 100% (60/60) 
 az Azerbaijani 85% (127/148) 
 az-ir  Azerbaijani in Iran (0/130) 
 ba Bashkir 78% (64/82) 
 bam Bambara 90% (54/60) 
 be Byelorussian 100% (68/68) 
 bg Bulgarian 100% (60/60) 
 bh Bihari (Devanagari script) (0/68) 
 bho Bhojpuri (Devanagari script) (0/68) 
 bi Bislama 100% (58/58) 
 bin Edo or Bini 92% (72/78) 
 bn Bengali (0/89) 
 bo Tibetan (0/95) 
 br Breton 100% (64/64) 
 bs Bosnian 85% (53/62) 
 bua Buriat (Buryat) 94% (66/70) 
 ca Catalan 100% (74/74) 
 ce Chechen 100% (67/67) 
 ch Chamorro 100% (58/58) 
 chm Mari (Lower Cheremis / Upper Cheremis) 86% (66/76) 
 chr Cherokee (0/85) 
 co Corsican 100% (85/85) 
 cs Czech 80% (66/82) 
 cu Old Church Slavonic 71% (74/103) 
 cv Chuvash 89% (66/74) 
 cy Welsh 87% (68/78) 
 da Danish 100% (70/70) 
 de German 100% (60/60) 
 dz Dzongkha (0/95) 
 el Greek 100% (70/70) 
 en English 100% (73/73) 
 eo Esperanto 81% (52/64) 
 es Spanish 100% (67/67) 
 et Estonian 93% (60/64) 
 eu Basque 100% (56/56) 
 fa Persian (0/129) 
 fi Finnish 93% (59/63) 
 fj Fijian 100% (52/52) 
 fo Faroese 100% (68/68) 
 fr French 100% (85/85) 
 ful Fulah (Fula) 87% (54/62) 
 fur Friulian 100% (66/66) 
 fy Frisian 100% (75/75) 
 ga Irish 77% (62/80) 
 gd Scots Gaelic 100% (70/70) 
 gez Ethiopic (Geez) (0/218) 
 gl Galician 100% (66/66) 
 gn Guarani 94% (66/70) 
 gu Gujarati (0/78) 
 gv Manx Gaelic 100% (54/54) 
 ha Hausa 86% (52/60) 
 haw Hawaiian 92% (58/63) 
 he Hebrew (0/27) 
 hi Hindi (Devanagari script) (0/68) 
 ho Hiri Motu 100% (52/52) 
 hr Croatian 85% (53/62) 
 hu Hungarian 94% (66/70) 
 hy Armenian (0/77) 
 ia Interlingua 100% (52/52) 
 ibo Igbo 89% (52/58) 
 id Indonesian 100% (54/54) 
 ie Interlingue 100% (52/52) 
 ik Inupiaq (Inupiak, Eskimo) 100% (68/68) 
 io Ido 100% (52/52) 
 is Icelandic 100% (70/70) 
 it Italian 100% (73/73) 
 iu Inuktitut (0/161) 
 ja Japanese 100% (6538/6538)  
 ka Georgian (0/33) 
 kaa Kara-Kalpak (Karakalpak) 84% (66/78) 
 ki Kikuyu 92% (52/56) 
 kk Kazakh 84% (65/77) 
 kl Greenlandic 95% (77/81) 
 km Khmer (0/70) 
 kn Kannada (0/80) 
 ko Korean 100% (2443/2443)  
 kok Kokani (Devanagari script) (0/68) 
 ks Kashmiri (Devanagari script) (0/68) 
 ku Kurdish 90% (58/64) 
 ku-ir  Kurdish in Iran (0/32) 
 kum Kumyk 100% (66/66) 
 kv Komi (Komi-Permyak/Komi-Siryan) 97% (68/70) 
 kw Cornish 89% (57/64) 
 ky Kirgiz 94% (66/70) 
 la Latin 83% (57/68) 
 lb Luxembourgish (Letzeburgesch) 100% (75/75) 
 lez Lezghian (Lezgian) 100% (67/67) 
 ln Lingala 90% (73/81) 
 lo Lao (0/65) 
 lt Lithuanian 75% (53/70) 
 lv Latvian 73% (57/78) 
 mg Malagasy 100% (56/56) 
 mh Marshallese 88% (55/62) 
 mi Maori 89% (57/64) 
 mk Macedonian 90% (38/42) 
 ml Malayalam (0/78) 
 mn Mongolian (0/130) 
 mo Moldavian 95% (122/128) 
 mr Marathi (Devanagari script) (0/68) 
 mt Maltese 91% (66/72) 
 my Burmese (Myanmar) (0/48) 
 nb Norwegian Bokmal 100% (70/70) 
 nds Low Saxon 100% (59/59) 
 ne Nepali (Devanagari script) (0/68) 
 nl Dutch 100% (83/83) 
 nn Norwegian Nynorsk 100% (76/76) 
 no Norwegian (Bokmal) 100% (70/70) 
 ny Chichewa 100% (54/54) 
 oc Occitan 100% (70/70) 
 om Oromo or Galla 100% (52/52) 
 or Oriya (0/79) 
 os Ossetic 100% (66/66) 
 pa Punjabi (Gurumukhi script) (0/63) 
 pl Polish 81% (57/70) 
 ps-af  Pashto in Afghanistan (0/49) 
 ps-pk  Pashto in Pakistan (0/49) 
 pt Portuguese 100% (83/83) 
 rm Rhaeto-Romance (Romansch) 100% (66/66) 
 ro Romanian 90% (56/62) 
 ru Russian 100% (66/66) 
 sa Sanskrit (Devanagari script) (0/68) 
 sah Yakut 86% (66/76) 
 sco Scots 92% (52/56) 
 se North Sami 89% (59/66) 
 sel Selkup (Ostyak-Samoyed) 100% (66/66) 
 sh Serbo-Croatian 100% (76/76) 
 si Sinhala (Sinhalese) (0/77) 
 sk Slovak 80% (69/86) 
 sl Slovenian 85% (53/62) 
 sm Samoan 100% (53/53) 
 sma South Sami 100% (60/60) 
 smj Lule Sami 100% (60/60) 
 smn Inari Sami 89% (61/68) 
 sms Skolt Sami 78% (63/80) 
 so Somali 100% (52/52) 
 sq Albanian 100% (56/56) 
 sr Serbian 100% (76/76) 
 sv Swedish 100% (68/68) 
 sw Swahili 100% (52/52) 
 syr Syriac (0/45) 
 ta Tamil (0/48) 
 te Telugu (0/80) 
 tg Tajik 84% (66/78) 
 th Thai (0/87) 
 ti-er  Eritrean Tigrinya (0/256) 
 ti-et  Ethiopian Tigrinya (0/282) 
 tig Tigre (0/221) 
 tk Turkmen 89% (66/74) 
 tl Tagalog (0/19) 
 tn Tswana 100% (56/56) 
 to Tonga 100% (53/53) 
 tr Turkish 92% (65/70) 
 ts Tsonga 100% (52/52) 
 tt Tatar 86% (66/76) 
 tw Twi 79% (58/73) 
 tyv Tuvinian 94% (66/70) 
 ug Uighur (0/125) 
 uk Ukrainian 97% (70/72) 
 ur Urdu (0/145) 
 uz Uzbek 88% (60/68) 
 ven Venda 83% (52/62) 
 vi Vietnamese 43% (85/194) 
 vo Volapuk 100% (54/54) 
 vot Votic 93% (58/62) 
 wa Walloon 100% (70/70) 
 wen Sorbian languages (lower and upper) 76% (58/76) 
 wo Wolof 100% (66/66) 
 xh Xhosa 100% (52/52) 
 yap Yapese 100% (58/58) 
 yi Yiddish (0/27) 
 yo Yoruba 77% (92/119) 
 zh-936 GBK Chinese national standard 100% (21921/21920)
 zh-cn  Chinese (simplified) 100% (6765/6765)  
 zh-hk  Chinese Hong Kong Supplementary Character Set 100% (2213/2213)  
 zh-mo  Chinese in Macau 100% (2213/2213)  
 zh-sg  Chinese in Singapore 100% (6765/6765)  
 zh-tw  Chinese (traditional) 100% (13063/13063)
 zu Zulu 100% (52/52) 
 ----------------------------------------------------------

Happy coding!

References:

http://docs.opencv.org/3.1.0/df/d4e/groupimgprocc.html (for the enumeration integers, something that go-opencv doesn't cover )

https://godoc.org/golang.org/x/image/colornames

https://www.socketloop.com/tutorials/golang-how-to-print-rune-unicode-utf-8-and-non-ascii-cjk-chinese-japanese-korean-characters

https://github.com/golang/freetype/blob/master/example/freetype/main.go

https://godoc.org/github.com/golang/freetype

https://www.socketloop.com/references/golang-image-paletted-set-and-setcolorindex-functions-example

http://blog.csdn.net/fengbingchun/article/details/8029337

http://stackoverflow.com/questions/10991523/opencv-draw-an-image-over-another-image

  See also : Golang : Add text to image and get OpenCV's X, Y co-ordinates example





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