I worked last year with OpenCV and Python. Today I wanted to try OpenCV using Golang with the GOCV package. I just wanted a simple Python example () to evalute but in Golang. I used even the same parameters (except the hiThresh and finalThreshold, i used the default values). Somehow I cannot get it working with GOCV, he only finds one centered result.
Here is my code:
package main
import (
"encoding/json"
"fmt"
"image"
"image/color"
"gocv.io/x/gocv"
)
func main() {
// define default hog descriptor
hog := gocv.NewHOGDescriptor()
defer hog.Close()
hog.SetSVMDetector(gocv.HOGDefaultPeopleDetector())
// color for the rect when faces detected
blue := color.RGBA{0, 0, 255, 0}
// read image
img := gocv.IMRead("images/person_010.bmp", 0)
//resize image
fact := float64(400) / float64(img.Cols())
newY := float64(img.Rows()) * fact
gocv.Resize(img, img, image.Point{X: 400, Y: int(newY)}, 0, 0, 1)
// detect people in image
rects := hog.DetectMultiScaleWithParams(img, 0, image.Point{X: 8, Y: 8}, image.Point{X: 16, Y: 16}, 1.05, 2, false)
// print found points
printStruct(rects)
// draw a rectangle around each face on the original image,
// along with text identifing as "Human"
for _, r := range rects {
gocv.Rectangle(img, r, blue, 3)
size := gocv.GetTextSize("Human", gocv.FontHersheyPlain, 1.2, 2)
pt := image.Pt(r.Min.X+(r.Min.X/2)-(size.X/2), r.Min.Y-2)
gocv.PutText(img, "Human", pt, gocv.FontHersheyPlain, 1.2, blue, 2)
}
if ok := gocv.IMWrite("loool.jpg", img); !ok {
fmt.Println("Error")
}
}
func printStruct(i interface{}) {
b, err := json.Marshal(i)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(b))
}
Here is the input image:
And here is the result:
Actually, I've just run the code you posted with the image you provided—and I've got another resulting image:
I'm running:
gocv version: 0.10.0
opencv lib version: 3.4.1
Related
I am using GoCV (Open CV 4 bindings for go) and I want to crop an image represented as a cuda.GpuMat given an image.Rectangle.
With a regular gocv.Mat this operation is simple enough:
func Crop(src *gocv.Mat, rect image.Rectangle) *gocv.Mat {
res := src.Region(rect)
return &res
}
However, I do not see a similar Region method on the cuda.GpuMat in GoCv (some bindings are not implemented yet), nor do I see a region method in the C++ source/docs: https://docs.opencv.org/master/d0/d60/classcv_1_1cuda_1_1GpuMat.html.
I have managed to effectively crop using cuda.Remap as follows:
func Crop(src *cuda.GpuMat, rect image.Rectangle) *cuda.GpuMat {
rectWidth := rect.Dx()
rectHeight := rect.Dy()
dst := cuda.NewGpuMat()
map1 := gocv.NewMatWithSize(rectHeight, rectWidth, gocv.MatTypeCV32F)
defer map1.Close()
map2 := gocv.NewMatWithSize(rectHeight, rectWidth, gocv.MatTypeCV32F)
defer map2.Close()
offsetX := rect.Min.X
offsetY := rect.Min.Y
for x := 0; x < map1.Cols(); x++ {
for y := 0; y < map2.Rows(); y++ {
map1.SetFloatAt(x, y, float32(y+offsetY))
map2.SetFloatAt(x, y, float32(x+offsetX))
}
}
gmap1, gmap2 := cuda.NewGpuMat(), cuda.NewGpuMat()
defer gmap1.Close()
defer gmap2.Close()
gmap1.Upload(map1)
gmap2.Upload(map2)
cuda.Remap(*src, &dst, &gmap1, &gmap2, cuda.InterpolationDefault, cuda.BorderConstant, color.RGBA{0, 0, 0, 0})
return &dst
}
But after running some benchmarks this implementation is 1-2 orders of magnitude slower than gocv.Mat#Region.
Given that there is no Region method on cuda.GpuMat in GoCV or OpenCV4, what would be the most optimal equivalent operation?
I'm not too concerned if the GoCV bindings do not exist. I'm more interested in the OpenCV4 equivalent to Region for a GpuMat.
UPDATE
I have found another method of cropping a cuda.GpuMat, but it is also about an order of magnitude slower than gocv.Mat#Region.
func Crop2(src *cuda.GpuMat, rect image.Rectangle) *cuda.GpuMat {
rectWidth := rect.Dx()
rectHeight := rect.Dy()
dst := cuda.NewGpuMat()
sz := image.Point{
X: rectWidth,
Y: rectHeight,
}
cuda.Rotate(*src, &dst, sz, 0, -float64(rect.Min.X), -float64(rect.Min.Y), cuda.InterpolationDefault)
return &dst
}
When using gocv package it is possible, for example, to perform template matching of a pattern within an image. The package also provide the MinMaxLoc function to retrieve locations of minimums and maximums within the matrix.
However, in below python example, the writer uses numpy.Where to threshold the matrix and get locations of multiple maximums. The python zip function is used to glue values together so they are like a slice [][2]int, the inner slice being xs and ys of the matches found.
The syntax loc[::-1] reverses the array.
The star operator in zip(*loc..) is being used to unpack the slices given to zip.
https://docs.opencv.org/master/d4/dc6/tutorial_py_template_matching.html
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img_rgb = cv.imread('mario.png')
img_gray = cv.cvtColor(img_rgb, cv.COLOR_BGR2GRAY)
template = cv.imread('mario_coin.png',0)
w, h = template.shape[::-1]
res = cv.matchTemplate(img_gray,template,cv.TM_CCOEFF_NORMED)
threshold = 0.8
loc = np.where( res >= threshold)
for pt in zip(*loc[::-1]):
cv.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (0,0,255), 2)
cv.imwrite('res.png',img_rgb)
How do I implement the same np.where algorithm in Go to get the multiple locations after the threshold is applied?
OpenCV has a built-in (semi-)equivalent function to np.where(), which is findNonZero(). As implied by the name, it finds the non-zero elements in an image, which is what np.where() does when called with a single argument, as the numpy docs state.
And this is available in the golang bindings as well. From the gocv docs on FindNonZero:
func FindNonZero(src Mat, idx *Mat)
FindNonZero returns the list of locations of non-zero pixels.
For further details, please see: https://docs.opencv.org/master/d2/de8/group__core__array.html#gaed7df59a3539b4cc0fe5c9c8d7586190
Note: np.where() returns indexes in array order, that is, (row, col) or (i, j) which is opposite to typical image indexing (x, y). That is why loc is reversed in Python. When using findNonZero() you won't need to do that, since OpenCV always uses (x, y) for points.
For anyone coming across this I hope a full example keeps you from spending days hitting your head against the wall and reading the same google results over and over until something clicks.
package main
import (
"fmt"
"image"
"image/color"
"os"
"gocv.io/x/gocv"
)
func OpenImage(path string) (image.Image, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
img, _, err := image.Decode(f)
return img, err
}
func main() {
src := gocv.IMRead("haystack.png", gocv.IMReadGrayScale)
tgt := gocv.IMRead("needle.png", gocv.IMReadGrayScale)
if src.Empty() {
fmt.Printf("failed to read image")
os.Exit(1)
}
if tgt.Empty() {
fmt.Printf("failed to read image")
os.Exit(1)
}
// Get image size
tgtImg, _ := tgt.ToImage()
iX, iY := tgtImg.Bounds().Size().X, tgtImg.Bounds().Size().Y
// Perform a match template operation
res := gocv.NewMat()
gocv.MatchTemplate(src, tgt, &res, gocv.TmSqdiffNormed, gocv.NewMat())
// Set a thresh hold. Using the `gocv.TmSqdiffNormed` confidence levels are
// reversed. Meaning the lowest value is actually the greatest confidence.
// So here I perform an Inverse Binary Threshold setting all values
// above 0.16 to 1.
thresh := gocv.NewMat()
gocv.Threshold(res, &thresh, 0.16, 1.0, gocv.ThresholdBinaryInv)
// Filter out all the non-zero values.
gocv.FindNonZero(thresh, &res)
// FindNonZero returns a list or vector of locations in the form of a gocv.Mat when using gocv.
// There may be a better way to do this, but I iterate through each found location getting the int vector in value
// at each row. I have to convert the returned int32 values into ints. Then draw a rectangle around each point.
//
// The result of get res.GetVeciAt(i, 0) is just a slice of x, y integers so each value can be accessed by
// using slice/array syntax.
for i := 0; i < res.Rows(); i++ {
x, y := res.GetVeciAt(i, 0)[0], res.GetVeciAt(i, 0)[1]
xi, yi := int(x), int(y)
gocv.Rectangle(&src, image.Rect(xi, yi, xi+iX, yi+iY), color.RGBA{0, 0, 0, 1}, 2)
}
w := gocv.NewWindow("Test")
w.IMShow(src)
if w.WaitKey(0) > 1 {
os.Exit(0)
}
}
I started playing with gocv. I'm trying to figure out a simple thing: how to cut out an object from an image which has a background of certain colour. In this case the object is pizza and background colour is blue.
I'm using InRange function (inRange in OpenCV) to define the upper and lower threshold for blue colour to create a mask and then CopyToWithMask function (copyTo in OpenCV) to apply the mask on the original image. I expect the result to be the blue background with the pizza cut out of it.
The code is very simple:
package main
import (
"fmt"
"os"
"gocv.io/x/gocv"
)
func main() {
imgPath := "pizza.png"
// read in an image from filesystem
img := gocv.IMRead(imgPath, gocv.IMReadColor)
if img.Empty() {
fmt.Printf("Could not read image %s\n", imgPath)
os.Exit(1)
}
// Create a copy of an image
hsvImg := img.Clone()
// Convert BGR to HSV image
gocv.CvtColor(img, hsvImg, gocv.ColorBGRToHSV)
lowerBound := gocv.NewMatFromScalar(gocv.NewScalar(110.0, 100.0, 100.0, 0.0), gocv.MatTypeCV8U)
upperBound := gocv.NewMatFromScalar(gocv.NewScalar(130.0, 255.0, 255.0, 0.0), gocv.MatTypeCV8U)
// Blue mask
mask := gocv.NewMat()
gocv.InRange(hsvImg, lowerBound, upperBound, mask)
// maskedImg: output array that has the same size and type as the input arrays.
maskedImg := gocv.NewMatWithSize(hsvImg.Rows(), hsvImg.Cols(), gocv.MatTypeCV8U)
hsvImg.CopyToWithMask(maskedImg, mask)
// save the masked image
newImg := gocv.NewMat()
// Convert back to BGR before saving
gocv.CvtColor(maskedImg, newImg, gocv.ColorHSVToBGR)
gocv.IMWrite("no_pizza.jpeg", newImg)
}
However the resulting image is basically almost completely black except for a slight hint of a pizza edge:
As for the chosen upper and lower bound of blue colours, I followed the guide mentioned in the official documentation:
blue = np.uint8([[[255, 0, 0]]])
hsv_blue = cv2.cvtColor(blue, cv2.COLOR_BGR2HSV)
print(hsv_blue)
[[[120 255 255]]]
Now you take [H-10, 100,100] and [H+10, 255, 255] as lower bound and
upper bound respectively.
I'm sure I'm missing something fundamental, but can't figure out what it is.
So I spent quite some time on this to figure out what I'm missing and finally found the answer to my question in case anyone is interested. It's now clearer to me now why this question hasn't been answered as the solution to it is rather crazy due to gocv API.
Here is the code that I had to write to get the result I'm after:
package main
import (
"fmt"
"os"
"path/filepath"
"gocv.io/x/gocv"
)
func main() {
// read image
pizzaPath := filepath.Join("pizza.png")
pizza := gocv.IMRead(pizzaPath, gocv.IMReadColor)
if pizza.Empty() {
fmt.Printf("Failed to read image: %s\n", pizzaPath)
os.Exit(1)
}
// Convert BGR to HSV image (dont modify the original)
hsvPizza := gocv.NewMat()
gocv.CvtColor(pizza, &hsvPizza, gocv.ColorBGRToHSV)
pizzaChannels, pizzaRows, pizzaCols := hsvPizza.Channels(), hsvPizza.Rows(), hsvPizza.Cols()
// define HSV color upper and lower bound ranges
lower := gocv.NewMatFromScalar(gocv.NewScalar(110.0, 50.0, 50.0, 0.0), gocv.MatTypeCV8UC3)
upper := gocv.NewMatFromScalar(gocv.NewScalar(130.0, 255.0, 255.0, 0.0), gocv.MatTypeCV8UC3)
// split HSV lower bounds into H, S, V channels
lowerChans := gocv.Split(lower)
lowerMask := gocv.NewMatWithSize(pizzaRows, pizzaCols, gocv.MatTypeCV8UC3)
lowerMaskChans := gocv.Split(lowerMask)
// split HSV lower bounds into H, S, V channels
upperChans := gocv.Split(upper)
upperMask := gocv.NewMatWithSize(pizzaRows, pizzaCols, gocv.MatTypeCV8UC3)
upperMaskChans := gocv.Split(upperMask)
// copy HSV values to upper and lower masks
for c := 0; c < pizzaChannels; c++ {
for i := 0; i < pizzaRows; i++ {
for j := 0; j < pizzaCols; j++ {
lowerMaskChans[c].SetUCharAt(i, j, lowerChans[c].GetUCharAt(0, 0))
upperMaskChans[c].SetUCharAt(i, j, upperChans[c].GetUCharAt(0, 0))
}
}
}
gocv.Merge(lowerMaskChans, &lowerMask)
gocv.Merge(upperMaskChans, &upperMask)
// global mask
mask := gocv.NewMat()
gocv.InRange(hsvPizza, lowerMask, upperMask, &mask)
// cut out pizza mask
pizzaMask := gocv.NewMat()
gocv.Merge([]gocv.Mat{mask, mask, mask}, &pizzaMask)
// cut out the pizza and convert back to BGR
gocv.BitwiseAnd(hsvPizza, pizzaMask, &hsvPizza)
gocv.CvtColor(hsvPizza, &hsvPizza, gocv.ColorHSVToBGR)
// write image to filesystem
outPizza := "no_pizza.jpeg"
if ok := gocv.IMWrite(outPizza, hsvPizza); !ok {
fmt.Printf("Failed to write image: %s\n", outPizza)
os.Exit(1)
}
// write pizza mask to filesystem
outPizzaMask := "no_pizza_mask.jpeg"
if ok := gocv.IMWrite(outPizzaMask, mask); !ok {
fmt.Printf("Failed to write image: %s\n", outPizza)
os.Exit(1)
}
}
This code produces the result I was after:
I'm also going to add another picture that shows the im
Now, let's get to code. gocv API function InRange() does not accept Scalar like OpenCV does so you have to do all that crazy image channel splitting and merging dance since you need to pass in Mats as lower and upper bounds to InRange(); these Mat masks have to have the exact number of channels as the image on which you run InRange().
This brings up another important point: when allocating the Scalars in gocv for this task, I originally used gocv.MatTypeCV8U type which represents single channel color - not enough for HSV image which has three channels -- this is fixed by using gocv.MatTypeCV8UC3 type.
If I it were possible pass in gocv.Scalars into gocv.InRange() a lot of the boiler plate code would disappear; so would all the unnecessary gocv.NewMat() allocations for splitting and reassembling the channels which are required to create lower and upper bounds channels.
inRange with the given range runs perfectly for me. I'm not familiar with Go, but here's my python code:
import numpy as py
import cv2
img = cv2.imread("pizza.png")
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
mask = cv2.inRange(hsv, (110, 100, 100), (130, 255, 255))
inv_mask = cv2.bitwise_not(mask)
pizza = cv2.bitwise_and(img, img, mask=inv_mask)
cv2.imshow("img", img)
cv2.imshow("mask", mask)
cv2.imshow("pizza", pizza)
cv2.imshow("inv mask", inv_mask)
cv2.waitKey()
A few of notes here:
inRange returns the blue background so we need to invert it to reveal the object's mask (if you need the object).
You don't need to apply mask on hsvImg and convert to BGR, you can apply mask directly on the original image (which is BGR already).
Python does not have CopyToWithMask so I use the equivalent bitwise_and. You may check this function in Go, but I suspect there would be no differences.
Here is what I did with Python because I don't know Go...
Let me explain first.
(1) Image has been turned to gray.
(2) Applied Canny Edge
(3 - 4) Created kernel and used it to do Dilate and Close operations
(5) Found contours
(6) Created and applied mask
(7) Cropped and saved the region
Here is the code:
import cv2
import numpy as np
image = cv2.imread("image.png")
copy = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
cv2.imshow('Gray', gray)
cv2.waitKey(0)
edged = cv2.Canny(gray, 10, 250)
cv2.imshow('Edged', edged)
cv2.waitKey(0)
kernel = np.ones((5, 5), np.uint8)
dilation = cv2.dilate(edged, kernel, iterations=1)
cv2.imshow('Dilation', dilation)
cv2.waitKey(0)
closing = cv2.morphologyEx(dilation, cv2.MORPH_CLOSE, kernel)
cv2.imshow('Closing', closing)
cv2.waitKey(0)
# if using OpenCV 4, remove image variable from below
image, cnts, hiers = cv2.findContours(closing, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cont = cv2.drawContours(copy, cnts, -1, (0, 0, 0), 1, cv2.LINE_AA)
cv2.imshow('Contours', cont)
cv2.waitKey(0)
mask = np.zeros(cont.shape[:2], dtype="uint8") * 255
# Draw the contours on the mask
cv2.drawContours(mask, cnts, -1, (255, 255, 255), -1)
# remove the contours from the image and show the resulting images
img = cv2.bitwise_and(cont, cont, mask=mask)
cv2.imshow("Mask", img)
cv2.waitKey(0)
for c in cnts:
x, y, w, h = cv2.boundingRect(c)
if w > 50 and h > 130:
new_img = img[y:y + h, x:x + w]
cv2.imwrite('Cropped.png', new_img)
cv2.imshow("Cropped", new_img)
cv2.waitKey(0)
Hope will help more than one user.
I use Tesseract OCR to to extract meter reading... tesseract needs to recognize right white background and black numbers.. I tried to threshold image
src := cvLoadImage(filename,CV_LOAD_IMAGE_GRAYSCALE);
dst := cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1);
cvThreshold(src, dst, 50, 250, CV_THRESH_BINARY);
but i didn't get the right result.. what should I do?
I use deplhi6 with Delphi-OpenCV
https://github.com/Laex/Delphi-OpenCV
You can treat this image as follows:
for jy:= 0 to bm.Height do
for ix := 0 to bm.Width do
begin
cor:=bm.Canvas.Pixels[ix,jy];
R:=GetRValue(Cor);
G:=GetGValue(Cor);
B:=GetBValue(Cor);
if g>38 then
bm.Canvas.Pixels[ix,jy]:=clWhite
else
bm.Canvas.Pixels[ix,jy]:=clBlack;
end;
As an output I got the following image:
Hope this helps.
I have a problem with opencv, I must detect and tracking grapes with a camera using the program: processing, how do it do? Can I have an exemple? thankyou
This code is an exemple code that detect the face:
import gab.opencv.*;
import processing.video.*;
import java.awt.*;
Capture video;
OpenCV opencv;
void setup() {
size(640, 480);
video = new Capture(this, 640/2, 480/2);
opencv = new OpenCV(this, 640/2, 480/2);
opencv.loadCascade(OpenCV.CASCADE_FRONTALFACE);
video.start();
}
void draw() {
scale(2);
opencv.loadImage(video);
image(video, 0, 0 );
noFill();
stroke(0, 255, 0);
strokeWeight(3);
Rectangle[] faces = opencv.detect();
println(faces.length);
for (int i = 0; i < faces.length; i++) {
println(faces[i].x + "," + faces[i].y);
rect(faces[i].x, faces[i].y, faces[i].width, faces[i].height);
}
}
void captureEvent(Capture c) {
c.read();
}
The code you're using trying to detect faces.
As a basic breakdown you will need to segment the object you're trying to detect (grapes in this case) from the background. I recommend starting simple:
try simply using threshold() and see if the highlights of each grape can be isolated. Hopefully they'll be the brightest spot in the image (if the camera isn't looking directly at a light source)
if method 1 isn't effective, try using colour detection: if you what kind of grapes you want to detect you can select a range of colours to detect and ignore the rest. Run the HSVColorTracking example and have a play with the ranges. Swap the marbles image with an image of grapes and see what you can get.
OpenCV has a function specifically built for detecting circles: HoughCircles. Unfortunately Greg's OpenCV Processing library doesn't wrap this function as he does with HoughLines yet, but there it provides function to convert between OpenCV's Mat and Processing PImage. If you're just getting started with Processing and don't have a experience with plain Java, this may be more convoluted.
Try basic thresholding and HSB range thresholding first. Once you have a good looking binary image (where the background is completely black and the grapes are white) you can findContours, get the centroid of each contour, compute the minEnclosingCircle(), etc.
Another option might be to train a support vector machine to distinguish between two classes: grapes and not grapes. This is a more advanced topic, but luckily Greg Borenstein, author of the OpenCV Processing library wrote a nice article with videos and example code on creating on the topic. Check out PSVM: Support Vector Machines for Processing.
Here's a mashup of the HueRangeSelection and FindContours examples using an google image result:
import gab.opencv.*;
PImage img;
OpenCV opencv;
Histogram histogram;
int lowerb = 50;
int upperb = 100;
ArrayList<Contour> contours;
ArrayList<Contour> polygons;
void setup() {
size(800,400);
img = loadImage("grape-harvest-inside.jpg");
opencv = new OpenCV(this, img);
opencv.useColor(HSB);
}
void draw() {
opencv.loadImage(img);
image(img, 0, 0);
opencv.setGray(opencv.getH().clone());
opencv.inRange(lowerb, upperb);
histogram = opencv.findHistogram(opencv.getH(), 255);
image(opencv.getOutput(), width/2, height/2, width/2,height/2);
noStroke(); fill(0);
histogram.draw(10, height - 230, 400, 200);
noFill(); stroke(0);
line(10, height-30, 410, height-30);
text("Hue", 10, height - (textAscent() + textDescent()));
float lb = map(lowerb, 0, 255, 0, 400);
float ub = map(upperb, 0, 255, 0, 400);
stroke(255, 0, 0); fill(255, 0, 0);
strokeWeight(2);
line(lb + 10, height-30, ub +10, height-30);
ellipse(lb+10, height-30, 3, 3 );
text(lowerb, lb-10, height-15);
ellipse(ub+10, height-30, 3, 3 );
text(upperb, ub+10, height-15);
contours = opencv.findContours();
for (Contour contour : contours) {
stroke(0, 255, 0);
noFill();
contour.draw();
}
}
void mouseMoved() {
if (keyPressed) {
upperb += mouseX - pmouseX;
}
else {
if (upperb < 255 || (mouseX - pmouseX) < 0) {
lowerb += mouseX - pmouseX;
}
if (lowerb > 0 || (mouseX - pmouseX) > 0) {
upperb += mouseX - pmouseX;
}
}
upperb = constrain(upperb, lowerb, 255);
lowerb = constrain(lowerb, 0, upperb-1);
}
Here's a preview of selecting range closer to the grapes colour:
You already notice this is both easy to use, but also not full proof and should get you on the right track to asking yourself the right kind of questions.
For example:
what environments are you supporting ? (indoors/outdoors, natural lighting, artificial lighting, daytime, nighttime, both ? etc.) - light controls what your input images will look like and is therefore crucial
how many different grapes will you support ? (can you get away with a single type (colour range), are there are elements that may trigger a false positive ?)
etc.