From 02da2d4700f22ded07c09bcb007cc35202d341bb Mon Sep 17 00:00:00 2001 From: David Oram Date: Thu, 10 Sep 2015 16:51:38 +1200 Subject: [PATCH] Added functions: - FindContours - DrawContours - Set - EqualizeHist and associated constants --- images/pic5_contours.png | Bin 0 -> 5180 bytes opencv/cv.go | 6 ++++- opencv/cxcore.go | 17 +++++++++++++ opencv/imgproc.go | 30 +++++++++++++--------- opencv/imgproc_test.go | 53 ++++++++++++++++++++++++++++++++------- 5 files changed, 84 insertions(+), 22 deletions(-) create mode 100644 images/pic5_contours.png diff --git a/images/pic5_contours.png b/images/pic5_contours.png new file mode 100644 index 0000000000000000000000000000000000000000..ba18b187073f8790cd600c1fd239be72bed126fe GIT binary patch literal 5180 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 I^|QYJ0r(G^2><{9 literal 0 HcmV?d00001 diff --git a/opencv/cv.go b/opencv/cv.go index c41ddcc..2e5e689 100644 --- a/opencv/cv.go +++ b/opencv/cv.go @@ -23,7 +23,11 @@ const ( CV_BGR2BGRA = C.CV_BGR2BGRA CV_RGBA2BGRA = C.CV_RGBA2BGRA - CV_BLUR = C.CV_BLUR + CV_BLUR_NO_SCALE = C.CV_BLUR_NO_SCALE + CV_BLUR = C.CV_BLUR + CV_GAUSSIAN = C.CV_GAUSSIAN + CV_MEDIAN = C.CV_MEDIAN + CV_BILATERAL = C.CV_BILATERAL CV_8U = C.CV_8U CV_8S = C.CV_8S diff --git a/opencv/cxcore.go b/opencv/cxcore.go index 610d933..f2c8080 100644 --- a/opencv/cxcore.go +++ b/opencv/cxcore.go @@ -113,6 +113,11 @@ func (img *IplImage) GetROI() Rect { return Rect(r) } +/* Equalizes the histogram of a grayscale image */ +func (img *IplImage) EqualizeHist(dest *IplImage) { + C.cvEqualizeHist(unsafe.Pointer(img), unsafe.Pointer(dest)) +} + /* Reshape changes shape of the image without copying data. A value of `0` means that channels or rows remain unchanged. @@ -142,6 +147,11 @@ func (img *IplImage) Get3D(x, y, z int) Scalar { return Scalar(ret) } +/* Sets every element of an array to a given value. */ +func (img *IplImage) Set(value Scalar) { + C.cvSet(unsafe.Pointer(img), (C.CvScalar)(value), nil) +} + /* Set1D sets a particular element in the image */ func (img *IplImage) Set1D(x int, value Scalar) { C.cvSet1D(unsafe.Pointer(img), C.int(x), (C.CvScalar)(value)) @@ -531,6 +541,13 @@ func Not(src, dst *IplImage) { /****************************************************************************************\ * Dynamic data structures * \****************************************************************************************/ +func (seq *Seq) Release() { + C.cvReleaseMemStorage(&seq.storage) +} + +func (seq *Seq) Total() int { + return (int)(seq.total) +} /****************************************************************************************\ * Drawing * diff --git a/opencv/imgproc.go b/opencv/imgproc.go index 83ae702..5d0676f 100644 --- a/opencv/imgproc.go +++ b/opencv/imgproc.go @@ -54,10 +54,12 @@ func Crop(src *IplImage, x, y, width, height int) *IplImage { } func CreateContourType() *ContourType { - return &ContourType{CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE, Point{0, 0}} + return &ContourType{mode: CV_RETR_EXTERNAL, method: CV_CHAIN_APPROX_SIMPLE, offset: Point{0, 0}} } -func (this *ContourType) FindContours(image *IplImage) []*Contour { +/* Returns a Seq of countours in an image, detected according to the parameters in ContourType. + Caller must Release() the Seq returned */ +func (this *ContourType) FindContours(image *IplImage) *Seq { storage := C.cvCreateMemStorage(0) header_size := (C.size_t)(unsafe.Sizeof(C.CvContour{})) var seq *C.CvSeq @@ -70,14 +72,18 @@ func (this *ContourType) FindContours(image *IplImage) []*Contour { this.method, C.cvPoint(C.int(this.offset.X), C.int(this.offset.Y))) - var contours []*Contour - for i := 0; i < (int)(seq.total); i++ { - contour := (*Contour)((*_Ctype_CvContour)(unsafe.Pointer(C.cvGetSeqElem(seq, C.int(i))))) - contours = append(contours, contour) - } - - storage_c := (*C.CvMemStorage)(storage) - C.cvReleaseMemStorage(&storage_c) - - return contours + return (*Seq)(seq) +} + +//cvDrawContours(CvArr* img, CvSeq* contour, CvScalar externalColor, CvScalar holeColor, int maxLevel, int thickness=1, int lineType=8 +func DrawContours(image *IplImage, contours *Seq, externalColor, holeColor Scalar, maxLevel, thickness, lineType int, offset Point) { + C.cvDrawContours( + unsafe.Pointer(image), + (*C.CvSeq)(contours), + (C.CvScalar)(externalColor), + (C.CvScalar)(holeColor), + C.int(maxLevel), + C.int(thickness), + C.int(lineType), + C.cvPoint(C.int(offset.X), C.int(offset.Y))) } diff --git a/opencv/imgproc_test.go b/opencv/imgproc_test.go index 759dc06..c986aec 100644 --- a/opencv/imgproc_test.go +++ b/opencv/imgproc_test.go @@ -1,10 +1,11 @@ package opencv import ( - "log" "path" "runtime" + "os" "testing" + "syscall" ) func TestResize(t *testing.T) { @@ -59,7 +60,7 @@ func TestCrop(t *testing.T) { func TestFindContours(t *testing.T) { _, currentfile, _, _ := runtime.Caller(0) - filename := path.Join(path.Dir(currentfile), "../images/shapes.png") + filename := path.Join(path.Dir(currentfile), "../images/pic5.png") image := LoadImage(filename) if image == nil { @@ -67,13 +68,47 @@ func TestFindContours(t *testing.T) { } defer image.Release() - grayscale_image := CreateImage(image.Width(), image.Height(), IPL_DEPTH_8U, 1) - CvtColor(image, grayscale_image, CV_BGR2GRAY) - defer grayscale_image.Release() + grayscale := CreateImage(image.Width(), image.Height(), IPL_DEPTH_8U, 1) + CvtColor(image, grayscale, CV_BGR2GRAY) + defer grayscale.Release() - cType := CreateContourType() - contours := cType.FindContours(grayscale_image) - for i, c := range contours { - log.Printf("Contour[%v] = %v", i, c) + + edges := CreateImage(grayscale.Width(), grayscale.Height(), grayscale.Depth(), grayscale.Channels()) + defer edges.Release() + Canny(grayscale, edges, 50, 200, 3) + + contourType := CreateContourType() + seq := contourType.FindContours(edges) + defer seq.Release() + + contours := CreateImage(grayscale.Width(), grayscale.Height(), grayscale.Depth(), grayscale.Channels()) + white := NewScalar(255, 255, 255, 0) + contours.Set(white) + + black := NewScalar(0, 0, 0, 0) + red := NewScalar(0, 255, 0, 0) + + for ; seq != nil; seq = (*Seq)(seq.h_next) { + DrawContours(contours, seq, red, black, 0, 2, 8, Point{0, 0}) } + + filename = path.Join(path.Dir(currentfile), "../images/pic5_contours.png") + // Uncomment this code to create the test image "../images/shapes_contours.png" + // It is part of the repo, and what this test compares against + // + //SaveImage(filename), contours, 0) + + tempfilename := path.Join(os.TempDir(), "pic5_contours.png") + defer syscall.Unlink(tempfilename) + SaveImage(tempfilename, contours, 0) + + // Compare actual image with expected image + same, err := BinaryCompare(filename, tempfilename) + if err != nil { + t.Fatal(err) + } + if !same { + t.Error("Expected contour file != actual contour file") + } + }