From 5d538b4b5e1db7b70a0d61e3a1302e909cf96d85 Mon Sep 17 00:00:00 2001 From: Thomas Kastner Date: Wed, 20 Aug 2014 11:51:19 +0200 Subject: [PATCH 1/3] added path from go image to opencv image and back. --- opencv/cxcore.go | 53 +++++++++++++++++++++++++++++++++++++++++++ opencv/cxtype.go | 4 ++-- opencv/goimage.go | 57 +++++++++++++++++++++++++++++++++++++++++++++++ opencv/highgui.go | 4 ++-- 4 files changed, 114 insertions(+), 4 deletions(-) create mode 100644 opencv/goimage.go diff --git a/opencv/cxcore.go b/opencv/cxcore.go index 917e676..ca81a59 100644 --- a/opencv/cxcore.go +++ b/opencv/cxcore.go @@ -114,6 +114,39 @@ func (img *IplImage) Reshape(channels, rows, _type int) *Mat { return (*Mat)(n) } +/* Get1D return a specific element from a 1-dimensional matrix. */ +func (img *IplImage) Get1D(x int) Scalar { + ret := C.cvGet1D(unsafe.Pointer(img), C.int(x)) + return Scalar(ret) +} + +/* Get2D return a specific element from a 2-dimensional matrix. */ +func (img *IplImage) Get2D(x, y int) Scalar { + ret := C.cvGet2D(unsafe.Pointer(img), C.int(x), C.int(y)) + return Scalar(ret) +} + +/* Get3D return a specific element from a 3-dimensional matrix. */ +func (img *IplImage) Get3D(x, y, z int) Scalar { + ret := C.cvGet3D(unsafe.Pointer(img), C.int(x), C.int(y), C.int(z)) + return Scalar(ret) +} + +/* 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)) +} + +/* Set2D sets a particular element in the image */ +func (img *IplImage) Set2D(x, y int, value Scalar) { + C.cvSet2D(unsafe.Pointer(img), C.int(x), C.int(y), (C.CvScalar)(value)) +} + +/* Set3D sets a particular element in the image */ +func (img *IplImage) Set3D(x, y, z int, value Scalar) { + C.cvSet3D(unsafe.Pointer(img), C.int(x), C.int(y), C.int(z), (C.CvScalar)(value)) +} + /* GetMat returns the matrix header for an image.*/ func (img *IplImage) GetMat() *Mat { var null C.int @@ -155,6 +188,11 @@ func (mat *Mat) InitHeader(rows, cols, type_ int, data unsafe.Pointer, step int) ) } +/* SetData assigns user data to the matrix header. */ +func (mat *Mat) SetData(data unsafe.Pointer, step int) { + C.cvSetData(unsafe.Pointer(mat), data, C.int(step)) +} + /* Releases CvMat header and deallocates matrix data (reference counting is used for data) */ func (mat *Mat) Release() { @@ -275,6 +313,21 @@ func (m *Mat) Get3D(x, y, z int) Scalar { return Scalar(ret) } +/* Set1D sets a particular element in them matrix */ +func (m *Mat) Set1D(x int, value Scalar) { + C.cvSet1D(unsafe.Pointer(m), C.int(x), (C.CvScalar)(value)) +} + +/* Set2D sets a particular element in them matrix */ +func (m *Mat) Set2D(x, y int, value Scalar) { + C.cvSet2D(unsafe.Pointer(m), C.int(x), C.int(y), (C.CvScalar)(value)) +} + +/* Set3D sets a particular element in them matrix */ +func (m *Mat) Set3D(x, y, z int, value Scalar) { + C.cvSet3D(unsafe.Pointer(m), C.int(x), C.int(y), C.int(z), (C.CvScalar)(value)) +} + /* GetImage returns the image header for the matrix. */ func (m *Mat) GetImage(channels int) *IplImage { tmp := CreateImage(m.Cols(), m.Rows(), m.Type(), channels) diff --git a/opencv/cxtype.go b/opencv/cxtype.go index 8878bb6..ba46295 100644 --- a/opencv/cxtype.go +++ b/opencv/cxtype.go @@ -602,8 +602,8 @@ const ( type Scalar C.CvScalar -func NewScalar(b, g, r int) Scalar { - rv := C.cvScalar(C.double(b), C.double(g), C.double(r), C.double(0)) +func NewScalar(b, g, r, a float64) Scalar { + rv := C.cvScalar(C.double(b), C.double(g), C.double(r), C.double(a)) return (Scalar)(rv) } diff --git a/opencv/goimage.go b/opencv/goimage.go new file mode 100644 index 0000000..9e590a6 --- /dev/null +++ b/opencv/goimage.go @@ -0,0 +1,57 @@ +package opencv + +import ( + "image" + "image/color" + "unsafe" +) + +/* DecodeImageMem decodes an image from an in memory byte buffer. */ +func DecodeImageMem(data []byte) *IplImage { + buf := CreateMatHeader(1, len(data), CV_8U) + buf.SetData(unsafe.Pointer(&data[0]), CV_AUTOSTEP) + + return DecodeImage(buf, CV_LOAD_IMAGE_UNCHANGED) +} + +/* From Image converts a go image.Image to an opencv.IplImage. */ +func FromImage(img image.Image) *IplImage { + b := img.Bounds() + model := color.NRGBAModel + dst := CreateImage( + b.Max.Y-b.Min.Y, + b.Max.X-b.Min.X, + IPL_DEPTH_8U, 4) + + for y := b.Min.Y; y < b.Max.Y; y++ { + for x := b.Min.X; x < b.Max.X; x++ { + px := img.At(x, y) + c := model.Convert(px).(color.NRGBA) + + value := NewScalar(float64(c.B), float64(c.G), float64(c.R), float64(c.A)) + dst.Set2D(x, y, value) + } + } + + return dst +} + +/* ToImage converts a opencv.IplImage to an go image.Image */ +func (img *IplImage) ToImage() image.Image { + out := image.NewNRGBA(image.Rect(0, 0, img.Width(), img.Height())) + if img.Depth() != IPL_DEPTH_8U { + return nil // TODO return error + } + + for y := 0; y < img.Height(); y++ { + for x := 0; x < img.Width(); x++ { + s := img.Get2D(x, y).Val() + b, g, r, a := s[2], s[1], s[0], s[3] + + c := color.NRGBA{uint8(r), uint8(g), uint8(b), uint8(a)} + out.Set(x, y, c) + } + } + + return out +} diff --git a/opencv/highgui.go b/opencv/highgui.go index e2a1490..8583b59 100644 --- a/opencv/highgui.go +++ b/opencv/highgui.go @@ -399,11 +399,11 @@ func SaveImage(filename string, image *IplImage, params int) int { } /* decode image stored in the buffer */ -func DecodeImage(buf unsafe.Pointer, iscolor int) *IplImage { +func DecodeImage(buf *Mat, iscolor int) *IplImage { rv := C.cvDecodeImage((*C.CvMat)(buf), C.int(iscolor)) return (*IplImage)(rv) } -func DecodeImageM(buf unsafe.Pointer, iscolor int) *Mat { +func DecodeImageM(buf *Mat, iscolor int) *Mat { rv := C.cvDecodeImageM((*C.CvMat)(buf), C.int(iscolor)) return (*Mat)(rv) } From d2eca0237c174f08029d557d753537c12f6470fb Mon Sep 17 00:00:00 2001 From: Thomas Kastner Date: Wed, 20 Aug 2014 15:53:27 +0200 Subject: [PATCH 2/3] added path from go image to opencv image and back. --- opencv/cxcore.go | 16 ++++++++++++---- opencv/goimage.go | 8 ++++---- opencv/highgui.go | 4 ++-- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/opencv/cxcore.go b/opencv/cxcore.go index ca81a59..0e5c622 100644 --- a/opencv/cxcore.go +++ b/opencv/cxcore.go @@ -69,6 +69,10 @@ func (img *IplImage) Release() { C.cvReleaseImage(&img_c) } +func (img *IplImage) Zero() { + C.cvSetZero(unsafe.Pointer(img)) +} + /* Creates a copy of IPL image (widthStep may differ) */ func (img *IplImage) Clone() *IplImage { p := C.cvCloneImage((*C.IplImage)(img)) @@ -122,13 +126,13 @@ func (img *IplImage) Get1D(x int) Scalar { /* Get2D return a specific element from a 2-dimensional matrix. */ func (img *IplImage) Get2D(x, y int) Scalar { - ret := C.cvGet2D(unsafe.Pointer(img), C.int(x), C.int(y)) + ret := C.cvGet2D(unsafe.Pointer(img), C.int(y), C.int(x)) return Scalar(ret) } /* Get3D return a specific element from a 3-dimensional matrix. */ func (img *IplImage) Get3D(x, y, z int) Scalar { - ret := C.cvGet3D(unsafe.Pointer(img), C.int(x), C.int(y), C.int(z)) + ret := C.cvGet3D(unsafe.Pointer(img), C.int(z), C.int(y), C.int(x)) return Scalar(ret) } @@ -139,12 +143,12 @@ func (img *IplImage) Set1D(x int, value Scalar) { /* Set2D sets a particular element in the image */ func (img *IplImage) Set2D(x, y int, value Scalar) { - C.cvSet2D(unsafe.Pointer(img), C.int(x), C.int(y), (C.CvScalar)(value)) + C.cvSet2D(unsafe.Pointer(img), C.int(y), C.int(x), (C.CvScalar)(value)) } /* Set3D sets a particular element in the image */ func (img *IplImage) Set3D(x, y, z int, value Scalar) { - C.cvSet3D(unsafe.Pointer(img), C.int(x), C.int(y), C.int(z), (C.CvScalar)(value)) + C.cvSet3D(unsafe.Pointer(img), C.int(z), C.int(y), C.int(x), (C.CvScalar)(value)) } /* GetMat returns the matrix header for an image.*/ @@ -217,6 +221,10 @@ func (mat *Mat) Clone() *Mat { return (*Mat)(mat_new) } +func (mat *Mat) Zero() { + C.cvSetZero(unsafe.Pointer(mat)) +} + /* Reshape changes shape of the matrix without copying data. A value of `0` means that channels or rows remain unchanged. diff --git a/opencv/goimage.go b/opencv/goimage.go index 9e590a6..15804db 100644 --- a/opencv/goimage.go +++ b/opencv/goimage.go @@ -11,22 +11,22 @@ func DecodeImageMem(data []byte) *IplImage { buf := CreateMatHeader(1, len(data), CV_8U) buf.SetData(unsafe.Pointer(&data[0]), CV_AUTOSTEP) - return DecodeImage(buf, CV_LOAD_IMAGE_UNCHANGED) + return DecodeImage(unsafe.Pointer(buf), CV_LOAD_IMAGE_UNCHANGED) } /* From Image converts a go image.Image to an opencv.IplImage. */ func FromImage(img image.Image) *IplImage { b := img.Bounds() - model := color.NRGBAModel + model := color.RGBAModel dst := CreateImage( - b.Max.Y-b.Min.Y, b.Max.X-b.Min.X, + b.Max.Y-b.Min.Y, IPL_DEPTH_8U, 4) for y := b.Min.Y; y < b.Max.Y; y++ { for x := b.Min.X; x < b.Max.X; x++ { px := img.At(x, y) - c := model.Convert(px).(color.NRGBA) + c := model.Convert(px).(color.RGBA) value := NewScalar(float64(c.B), float64(c.G), float64(c.R), float64(c.A)) dst.Set2D(x, y, value) diff --git a/opencv/highgui.go b/opencv/highgui.go index 8583b59..e2a1490 100644 --- a/opencv/highgui.go +++ b/opencv/highgui.go @@ -399,11 +399,11 @@ func SaveImage(filename string, image *IplImage, params int) int { } /* decode image stored in the buffer */ -func DecodeImage(buf *Mat, iscolor int) *IplImage { +func DecodeImage(buf unsafe.Pointer, iscolor int) *IplImage { rv := C.cvDecodeImage((*C.CvMat)(buf), C.int(iscolor)) return (*IplImage)(rv) } -func DecodeImageM(buf *Mat, iscolor int) *Mat { +func DecodeImageM(buf unsafe.Pointer, iscolor int) *Mat { rv := C.cvDecodeImageM((*C.CvMat)(buf), C.int(iscolor)) return (*Mat)(rv) } From a3c5d8e139449ccfe35e39f921cc908f8ec80862 Mon Sep 17 00:00:00 2001 From: Thomas Kastner Date: Thu, 21 Aug 2014 08:40:53 +0200 Subject: [PATCH 3/3] added FromImageUnsafe and tests/benchmarks --- opencv/cv.go | 5 ++- opencv/cxcore.go | 5 +++ opencv/goimage.go | 22 +++++++++- opencv/goimage_test.go | 96 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 125 insertions(+), 3 deletions(-) create mode 100644 opencv/goimage_test.go diff --git a/opencv/cv.go b/opencv/cv.go index ea9eb7d..4d20598 100644 --- a/opencv/cv.go +++ b/opencv/cv.go @@ -18,8 +18,9 @@ func init() { } const ( - CV_BGR2GRAY = C.CV_BGR2GRAY - CV_BGR2BGRA = C.CV_BGR2BGRA + CV_BGR2GRAY = C.CV_BGR2GRAY + CV_BGR2BGRA = C.CV_BGR2BGRA + CV_RGBA2BGRA = C.CV_RGBA2BGRA CV_BLUR = C.CV_BLUR diff --git a/opencv/cxcore.go b/opencv/cxcore.go index 0e5c622..b2b335a 100644 --- a/opencv/cxcore.go +++ b/opencv/cxcore.go @@ -57,6 +57,11 @@ func CreateImage(w, h, depth, channels int) *IplImage { return (*IplImage)(img) } +/* SetData assigns user data to the image header */ +func (img *IplImage) SetData(data unsafe.Pointer, step int) { + C.cvSetData(unsafe.Pointer(img), data, C.int(step)) +} + /* Releases (i.e. deallocates) IPL image header */ func (img *IplImage) ReleaseHeader() { img_c := (*C.IplImage)(img) diff --git a/opencv/goimage.go b/opencv/goimage.go index 15804db..34c74e2 100644 --- a/opencv/goimage.go +++ b/opencv/goimage.go @@ -14,7 +14,7 @@ func DecodeImageMem(data []byte) *IplImage { return DecodeImage(unsafe.Pointer(buf), CV_LOAD_IMAGE_UNCHANGED) } -/* From Image converts a go image.Image to an opencv.IplImage. */ +/* FromImage converts a go image.Image to an opencv.IplImage. */ func FromImage(img image.Image) *IplImage { b := img.Bounds() model := color.RGBAModel @@ -36,6 +36,26 @@ func FromImage(img image.Image) *IplImage { return dst } +/* FromImageUnsafe create an opencv.IplImage that shares the buffer with the +go image.RGBA image. All changes made from opencv might affect go! */ +func FromImageUnsafe(img *image.RGBA) *IplImage { + b := img.Bounds() + buf := CreateImageHeader( + b.Max.X-b.Min.X, + b.Max.Y-b.Min.Y, + IPL_DEPTH_8U, 4) + dst := CreateImage( + b.Max.X-b.Min.X, + b.Max.Y-b.Min.Y, + IPL_DEPTH_8U, 4) + + buf.SetData(unsafe.Pointer(&img.Pix[0]), CV_AUTOSTEP) + CvtColor(buf, dst, CV_RGBA2BGRA) + buf.Release() + + return dst +} + /* ToImage converts a opencv.IplImage to an go image.Image */ func (img *IplImage) ToImage() image.Image { out := image.NewNRGBA(image.Rect(0, 0, img.Width(), img.Height())) diff --git a/opencv/goimage_test.go b/opencv/goimage_test.go new file mode 100644 index 0000000..e0bc3b5 --- /dev/null +++ b/opencv/goimage_test.go @@ -0,0 +1,96 @@ +package opencv + +import ( + "image" + _ "image/jpeg" + _ "image/png" + "os" + "reflect" + "testing" +) + +func TestFromImage(t *testing.T) { + fh, err := os.Open("../images/pic5.png") + if err != nil { + t.Fatal(err) + } + + img, _, err := image.Decode(fh) + if err != nil { + t.Fatal(err) + } + + ocv := FromImage(img) + if ocv == nil { + t.Fatal("failed to convert image") + } + + width := img.Bounds().Max.X - img.Bounds().Min.X + height := img.Bounds().Max.Y - img.Bounds().Min.Y + if ocv.Width() != width || ocv.Height() != height { + t.Fatalf("loaded image has wrong dimensions: got %dx%d, expected %dx%d", ocv.Width(), ocv.Height(), width, height) + } + + ex := [4]float64{98, 139, 26, 255} + px := ocv.Get2D(50, 90).Val() + if !reflect.DeepEqual(px, ex) { + t.Fatalf("wrong color @50,90: got %v, expected %v", px, ex) + } +} + +func TestFromImageUnsafe(t *testing.T) { + fh, err := os.Open("../images/pic5.png") + if err != nil { + t.Fatal(err) + } + + img, _, err := image.Decode(fh) + if err != nil { + t.Fatal(err) + } + + rgba, ok := img.(*image.RGBA) + if !ok { + t.Fatal("image is no RGBA image") + } + + ocv := FromImageUnsafe(rgba) + if ocv == nil { + t.Fatal("failed to convert image") + } + + width := img.Bounds().Max.X - img.Bounds().Min.X + height := img.Bounds().Max.Y - img.Bounds().Min.Y + if ocv.Width() != width || ocv.Height() != height { + t.Fatalf("loaded image has wrong dimensions: got %dx%d, expected %dx%d", ocv.Width(), ocv.Height(), width, height) + } + + ex := [4]float64{98, 139, 26, 255} + px := ocv.Get2D(50, 90).Val() + if !reflect.DeepEqual(px, ex) { + t.Fatalf("wrong color @50,90: got %v, expected %v", px, ex) + } +} + +func BenchmarkFromImage(b *testing.B) { + fh, _ := os.Open("../images/pic5.png") + img, _, _ := image.Decode(fh) + b.ResetTimer() + + for i := 0; i < b.N; i++ { + ocv := FromImage(img) + ocv.Release() + } +} + +func BenchmarkFromImageUnsafe(b *testing.B) { + fh, _ := os.Open("../images/pic5.png") + img, _, _ := image.Decode(fh) + b.ResetTimer() + + for i := 0; i < b.N; i++ { + rgba := img.(*image.RGBA) + ocv := FromImageUnsafe(rgba) + ocv.Release() + } +}