diff --git a/.travis.yml b/.travis.yml index c1b5f877..eac6dc5b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -41,7 +41,7 @@ before_cache: script: - export CGO_CPPFLAGS="-I${HOME}/usr/include" - - export CGO_LDFLAGS="-L${HOME}/usr/lib -lopencv_core -lopencv_videoio -lopencv_face -lopencv_imgproc -lopencv_highgui -lopencv_imgcodecs -lopencv_objdetect -lopencv_features2d -lopencv_video -lopencv_dnn -lopencv_xfeatures2d -lopencv_plot -lopencv_tracking" + - export CGO_LDFLAGS="-L${HOME}/usr/lib -lopencv_core -lopencv_videoio -lopencv_face -lopencv_imgproc -lopencv_highgui -lopencv_imgcodecs -lopencv_objdetect -lopencv_features2d -lopencv_video -lopencv_dnn -lopencv_xfeatures2d -lopencv_plot -lopencv_tracking -lopencv_img_hash" - export GOCV_CAFFE_TEST_FILES="${HOME}/testdata" - export GOCV_TENSORFLOW_TEST_FILES="${HOME}/testdata" - echo "Ensuring code is well formatted"; ! gofmt -s -d . | read diff --git a/appveyor.yml b/appveyor.yml index f59eb9f5..d4becbc1 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -26,7 +26,7 @@ install: - cd c:\gopath\src\gocv.io\x\gocv - go get -d . - set CGO_CPPFLAGS=-IC:\opencv\build\install\include - - set CGO_LDFLAGS=-LC:\opencv\build\install\x64\mingw\lib -lopencv_core341 -lopencv_face341 -lopencv_videoio341 -lopencv_imgproc341 -lopencv_highgui341 -lopencv_imgcodecs341 -lopencv_objdetect341 -lopencv_features2d341 -lopencv_video341 -lopencv_dnn341 -lopencv_xfeatures2d341 -lopencv_plot341 -lopencv_tracking341 + - set CGO_LDFLAGS=-LC:\opencv\build\install\x64\mingw\lib -lopencv_core341 -lopencv_face341 -lopencv_videoio341 -lopencv_imgproc341 -lopencv_highgui341 -lopencv_imgcodecs341 -lopencv_objdetect341 -lopencv_features2d341 -lopencv_video341 -lopencv_dnn341 -lopencv_xfeatures2d341 -lopencv_plot341 -lopencv_tracking341 -lopencv_img_hash341 - set GOCV_CAFFE_TEST_FILES=C:\opencv\testdata - set GOCV_TENSORFLOW_TEST_FILES=C:\opencv\testdata - go env diff --git a/contrib/img_hash.cpp b/contrib/img_hash.cpp new file mode 100644 index 00000000..95345885 --- /dev/null +++ b/contrib/img_hash.cpp @@ -0,0 +1,43 @@ +#include "img_hash.h" + +void pHashCompute(Mat inputArr, Mat outputArr) { + cv::img_hash::pHash(*inputArr, *outputArr); +} +double pHashCompare(Mat a, Mat b) { + return cv::img_hash::PHash::create()->compare(*a, *b); +} + +void averageHashCompute(Mat inputArr, Mat outputArr) { + cv::img_hash::averageHash(*inputArr, *outputArr); +} +double averageHashCompare(Mat a, Mat b) { + return cv::img_hash::AverageHash::create()->compare(*a, *b); +} + +void blockMeanHashCompute(Mat inputArr, Mat outputArr, int mode) { + cv::img_hash::blockMeanHash(*inputArr, *outputArr, mode); +} +double blockMeanHashCompare(Mat a, Mat b, int mode) { + return cv::img_hash::BlockMeanHash::create(mode)->compare(*a, *b); +} + +void colorMomentHashCompute(Mat inputArr, Mat outputArr) { + cv::img_hash::colorMomentHash(*inputArr, *outputArr); +} +double colorMomentHashCompare(Mat a, Mat b) { + return cv::img_hash::ColorMomentHash::create()->compare(*a, *b); +} + +void marrHildrethHashCompute(Mat inputArr, Mat outputArr, float alpha, float scale) { + cv::img_hash::marrHildrethHash(*inputArr, *outputArr, alpha, scale); +} +double marrHildrethHashCompare(Mat a, Mat b, float alpha, float scale) { + return cv::img_hash::MarrHildrethHash::create(alpha, scale)->compare(*a, *b); +} + +void radialVarianceHashCompute(Mat inputArr, Mat outputArr, double sigma, int numOfAngleLine) { + cv::img_hash::radialVarianceHash(*inputArr, *outputArr, sigma, numOfAngleLine); +} +double radialVarianceHashCompare(Mat a, Mat b, double sigma, int numOfAngleLine) { + return cv::img_hash::RadialVarianceHash::create(sigma, numOfAngleLine)->compare(*a, *b); +} diff --git a/contrib/img_hash.go b/contrib/img_hash.go new file mode 100644 index 00000000..f3e44429 --- /dev/null +++ b/contrib/img_hash.go @@ -0,0 +1,108 @@ +package contrib + +//#include +//#include "img_hash.h" +import "C" + +import ( + "gocv.io/x/gocv" +) + +type ImgHashBase interface { + Compare(a, b gocv.Mat) float64 + Compute(inputArr gocv.Mat, outputArr *gocv.Mat) +} + +type PHash struct{} + +func (hash PHash) Compute(input gocv.Mat, output *gocv.Mat) { + C.pHashCompute(C.Mat(input.Ptr()), C.Mat(output.Ptr())) +} + +func (hash PHash) Compare(a, b gocv.Mat) float64 { + return float64(C.pHashCompare(C.Mat(a.Ptr()), C.Mat(b.Ptr()))) +} + +type AverageHash struct{} + +func (hash AverageHash) Compute(input gocv.Mat, output *gocv.Mat) { + C.averageHashCompute(C.Mat(input.Ptr()), C.Mat(output.Ptr())) +} + +func (hash AverageHash) Compare(a, b gocv.Mat) float64 { + return float64(C.averageHashCompare(C.Mat(a.Ptr()), C.Mat(b.Ptr()))) +} + +type BlockMeanHash struct { + Mode BlockMeanHashMode +} + +type BlockMeanHashMode int + +const ( + BlockMeanHashMode0 BlockMeanHashMode = iota + BlockMeanHashMode1 + BlockMeanHashModeDefault = BlockMeanHashMode0 +) + +func (hash BlockMeanHash) Compute(input gocv.Mat, output *gocv.Mat) { + C.blockMeanHashCompute(C.Mat(input.Ptr()), C.Mat(output.Ptr()), C.int(hash.Mode)) +} + +func (hash BlockMeanHash) Compare(a, b gocv.Mat) float64 { + return float64(C.blockMeanHashCompare(C.Mat(a.Ptr()), C.Mat(b.Ptr()), C.int(hash.Mode))) +} + +// TODO: BlockMeanHash.GetMean isn't implemented, because it requires state from the last +// call to Compute, and there's no easy way to keep it. + +type ColorMomentHash struct{} + +func (hash ColorMomentHash) Compute(input gocv.Mat, output *gocv.Mat) { + C.colorMomentHashCompute(C.Mat(input.Ptr()), C.Mat(output.Ptr())) +} + +func (hash ColorMomentHash) Compare(a, b gocv.Mat) float64 { + return float64(C.colorMomentHashCompare(C.Mat(a.Ptr()), C.Mat(b.Ptr()))) +} + +type MarrHildrethHash struct { + Alpha float32 + Scale float32 +} + +func NewMarrHildrethHash() MarrHildrethHash { + return MarrHildrethHash{2.0, 1.0} +} + +func (hash MarrHildrethHash) Compute(input gocv.Mat, output *gocv.Mat) { + C.marrHildrethHashCompute(C.Mat(input.Ptr()), C.Mat(output.Ptr()), + C.float(hash.Alpha), C.float(hash.Scale)) +} + +func (hash MarrHildrethHash) Compare(a, b gocv.Mat) float64 { + return float64(C.marrHildrethHashCompare(C.Mat(a.Ptr()), C.Mat(b.Ptr()), + C.float(hash.Alpha), C.float(hash.Scale))) +} + +type RadialVarianceHash struct { + Sigma float64 + NumOfAngleLine int +} + +func NewRadialVarianceHash() RadialVarianceHash { + return RadialVarianceHash{1, 180} +} + +func (hash RadialVarianceHash) Compute(input gocv.Mat, output *gocv.Mat) { + C.radialVarianceHashCompute(C.Mat(input.Ptr()), C.Mat(output.Ptr()), + C.double(hash.Sigma), C.int(hash.NumOfAngleLine)) +} + +func (hash RadialVarianceHash) Compare(a, b gocv.Mat) float64 { + return float64(C.radialVarianceHashCompare(C.Mat(a.Ptr()), C.Mat(b.Ptr()), + C.double(hash.Sigma), C.int(hash.NumOfAngleLine))) +} + +// TODO: RadialVariance getFeatures, getHash, getPixPerLine, getProjection are not +// implemented here, because they're stateful. diff --git a/contrib/img_hash.h b/contrib/img_hash.h new file mode 100644 index 00000000..28588a74 --- /dev/null +++ b/contrib/img_hash.h @@ -0,0 +1,29 @@ +#ifndef _OPENCV3_IMG_HASH_H_ +#define _OPENCV3_IMG_HASH_H_ + +#ifdef __cplusplus +#include +#include +extern "C" { +#endif + +#include "../core.h" + +void pHashCompute(Mat inputArr, Mat outputArr); +double pHashCompare(Mat a, Mat b); +void averageHashCompute(Mat inputArr, Mat outputArr); +double averageHashCompare(Mat a, Mat b); +void blockMeanHashCompute(Mat inputArr, Mat outputArr, int mode); +double blockMeanHashCompare(Mat a, Mat b, int mode); +void colorMomentHashCompute(Mat inputArr, Mat outputArr); +double colorMomentHashCompare(Mat a, Mat b); +void marrHildrethHashCompute(Mat inputArr, Mat outputArr, float alpha, float scale); +double marrHildrethHashCompare(Mat a, Mat b, float alpha, float scale); +void radialVarianceHashCompute(Mat inputArr, Mat outputArr, double sigma, int numOfAngleLine); +double radialVarianceHashCompare(Mat a, Mat b, double sigma, int numOfAngleLine); + +#ifdef __cplusplus +} +#endif + +#endif //_OPENCV3_IMG_HASH_H_ diff --git a/contrib/img_hash_test.go b/contrib/img_hash_test.go new file mode 100644 index 00000000..c8c29709 --- /dev/null +++ b/contrib/img_hash_test.go @@ -0,0 +1,122 @@ +package contrib + +import ( + "errors" + "testing" + + "gocv.io/x/gocv" +) + +const ( + testImage = "../images/space_shuttle.jpg" + testImage2 = "../images/toy.jpg" +) + +func compute(path string, hash ImgHashBase) (*gocv.Mat, error) { + img := gocv.IMRead(path, gocv.IMReadColor) + if img.Empty() { + return nil, errors.New("Invalid input") + } + defer img.Close() + + dst := gocv.NewMat() + hash.Compute(img, &dst) + if dst.Empty() { + dst.Close() + return nil, errors.New("Empty output") + } + + return &dst, nil +} + +func testHash(t *testing.T, hash ImgHashBase) { + result, err := compute(testImage, hash) + if err != nil { + t.Error(err) + } + defer result.Close() + + t.Logf("%T: %x", hash, result.ToBytes()) + + // Load second image and make sure it doesn't compare as identical + result2, err := compute(testImage2, hash) + if err != nil { + t.Error(err) + } + defer result2.Close() + + similar := hash.Compare(*result, *result2) + t.Logf("%T: similarity %g", hash, similar) + // The range and meaning of this value varies between algorithms, and + // there doesn't seem to be a well defined set of default thresholds, so + // "anything but zero" is the minimum smoke test. + if similar == 0 { + t.Error("Image similarity is zero?") + } +} + +func TestHashes(t *testing.T) { + t.Run("PHash", func(t *testing.T) { testHash(t, PHash{}) }) + t.Run("AverageHash", func(t *testing.T) { testHash(t, AverageHash{}) }) + t.Run("BlockMeanHash", func(t *testing.T) { testHash(t, BlockMeanHash{}) }) + t.Run("ColorMomentHash", func(t *testing.T) { testHash(t, ColorMomentHash{}) }) + t.Run("MarrHidlrethHash", func(t *testing.T) { testHash(t, NewMarrHildrethHash()) }) + t.Run("RadialVarianceHash", func(t *testing.T) { testHash(t, NewRadialVarianceHash()) }) +} + +func BenchmarkCompute(b *testing.B) { + img := gocv.IMRead(testImage, gocv.IMReadColor) + if img.Empty() { + b.Error("Invalid input") + } + defer img.Close() + b.ResetTimer() + + compute := func(b *testing.B, hash ImgHashBase) { + for i := 0; i < b.N; i++ { + dst := gocv.NewMat() + hash.Compute(img, &dst) + if dst.Empty() { + b.Error("Empty output") + dst.Close() + return + } + dst.Close() + } + } + + b.Run("PHash", func(b *testing.B) { compute(b, PHash{}) }) + b.Run("AverageHash", func(b *testing.B) { compute(b, AverageHash{}) }) + b.Run("BlockMeanHash", func(b *testing.B) { compute(b, BlockMeanHash{}) }) + b.Run("ColorMomentHash", func(b *testing.B) { compute(b, ColorMomentHash{}) }) + b.Run("MarrHidlrethHash", func(b *testing.B) { compute(b, NewMarrHildrethHash()) }) + b.Run("RadialVarianceHash", func(b *testing.B) { compute(b, NewRadialVarianceHash()) }) +} + +func BenchmarkCompare(b *testing.B) { + compare := func(b *testing.B, hash ImgHashBase) { + result1, err := compute(testImage, hash) + if err != nil { + b.Error(err) + } + defer result1.Close() + + result2, err := compute(testImage2, hash) + if err != nil { + b.Error(err) + } + defer result2.Close() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + hash.Compare(*result1, *result2) + } + } + + b.Run("PHash", func(b *testing.B) { compare(b, PHash{}) }) + b.Run("AverageHash", func(b *testing.B) { compare(b, AverageHash{}) }) + b.Run("BlockMeanHash", func(b *testing.B) { compare(b, BlockMeanHash{}) }) + b.Run("ColorMomentHash", func(b *testing.B) { compare(b, ColorMomentHash{}) }) + b.Run("MarrHidlrethHash", func(b *testing.B) { compare(b, NewMarrHildrethHash()) }) + b.Run("RadialVarianceHash", func(b *testing.B) { compare(b, NewRadialVarianceHash()) }) +} diff --git a/env.cmd b/env.cmd index e4f797c6..2c911cd3 100644 --- a/env.cmd +++ b/env.cmd @@ -2,7 +2,7 @@ IF EXIST C:\opencv\build\install\include\ ( ECHO Configuring GoCV env for OpenCV. set CGO_CPPFLAGS=-IC:\opencv\build\install\include - set CGO_LDFLAGS=-LC:\opencv\build\install\x64\mingw\lib -lopencv_core341 -lopencv_face341 -lopencv_videoio341 -lopencv_imgproc341 -lopencv_highgui341 -lopencv_imgcodecs341 -lopencv_objdetect341 -lopencv_features2d341 -lopencv_video341 -lopencv_dnn341 -lopencv_xfeatures2d341 -lopencv_plot341 -lopencv_tracking341 + set CGO_LDFLAGS=-LC:\opencv\build\install\x64\mingw\lib -lopencv_core341 -lopencv_face341 -lopencv_videoio341 -lopencv_imgproc341 -lopencv_highgui341 -lopencv_imgcodecs341 -lopencv_objdetect341 -lopencv_features2d341 -lopencv_video341 -lopencv_dnn341 -lopencv_xfeatures2d341 -lopencv_plot341 -lopencv_tracking341 -lopencv_img_hash341 ) ELSE ( ECHO ERROR: Unable to locate OpenCV for GoCV configuration. ) diff --git a/env.sh b/env.sh index 9b2ac2e4..0ce26f0f 100644 --- a/env.sh +++ b/env.sh @@ -5,12 +5,12 @@ if [[ "$uname_val" == "Darwin" ]]; then echo "Brew install detected" export CGO_CPPFLAGS="-I$CVPATH/include -I$CVPATH/include/opencv2" export CGO_CXXFLAGS="--std=c++1z -stdlib=libc++" - export CGO_LDFLAGS="-L$CVPATH/lib -lopencv_core -lopencv_face -lopencv_videoio -lopencv_imgproc -lopencv_highgui -lopencv_imgcodecs -lopencv_objdetect -lopencv_features2d -lopencv_video -lopencv_dnn -lopencv_xfeatures2d -lopencv_plot -lopencv_tracking" + export CGO_LDFLAGS="-L$CVPATH/lib -lopencv_core -lopencv_face -lopencv_videoio -lopencv_imgproc -lopencv_highgui -lopencv_imgcodecs -lopencv_objdetect -lopencv_features2d -lopencv_video -lopencv_dnn -lopencv_xfeatures2d -lopencv_plot -lopencv_tracking -lopencv_img_hash" else echo "Non-Brew install detected" export CGO_CPPFLAGS="-I/usr/local/include" export CGO_CXXFLAGS="--std=c++1z -stdlib=libc++" - export CGO_LDFLAGS="-L/usr/local/lib -lopencv_core -lopencv_face -lopencv_videoio -lopencv_imgproc -lopencv_highgui -lopencv_imgcodecs -lopencv_objdetect -lopencv_features2d -lopencv_video -lopencv_dnn -lopencv_xfeatures2d -lopencv_plot -lopencv_tracking" + export CGO_LDFLAGS="-L/usr/local/lib -lopencv_core -lopencv_face -lopencv_videoio -lopencv_imgproc -lopencv_highgui -lopencv_imgcodecs -lopencv_objdetect -lopencv_features2d -lopencv_video -lopencv_dnn -lopencv_xfeatures2d -lopencv_plot -lopencv_tracking -lopencv_img_hash" fi echo "Environment variables configured for OSX" @@ -18,13 +18,13 @@ elif [[ "$uname_val" == "Linux" ]]; then if [[ -f /etc/pacman.conf ]]; then export CGO_CPPFLAGS="-I/usr/include" export CGO_CXXFLAGS="--std=c++1z" - export CGO_LDFLAGS="-L/lib64 -lopencv_core -lopencv_face -lopencv_videoio -lopencv_imgproc -lopencv_highgui -lopencv_imgcodecs -lopencv_objdetect -lopencv_features2d -lopencv_video -lopencv_dnn -lopencv_xfeatures2d -lopencv_plot -lopencv_tracking" + export CGO_LDFLAGS="-L/lib64 -lopencv_core -lopencv_face -lopencv_videoio -lopencv_imgproc -lopencv_highgui -lopencv_imgcodecs -lopencv_objdetect -lopencv_features2d -lopencv_video -lopencv_dnn -lopencv_xfeatures2d -lopencv_plot -lopencv_tracking -lopencv_img_hash" else export PKG_CONFIG_PATH="/usr/local/lib64/pkgconfig" export LD_LIBRARY_PATH="/usr/local/lib64" export CGO_CPPFLAGS="-I/usr/local/include" export CGO_CXXFLAGS="--std=c++1z" - export CGO_LDFLAGS="-L/usr/local/lib -lopencv_core -lopencv_face -lopencv_videoio -lopencv_imgproc -lopencv_highgui -lopencv_imgcodecs -lopencv_objdetect -lopencv_features2d -lopencv_video -lopencv_dnn -lopencv_xfeatures2d -lopencv_plot -lopencv_tracking" + export CGO_LDFLAGS="-L/usr/local/lib -lopencv_core -lopencv_face -lopencv_videoio -lopencv_imgproc -lopencv_highgui -lopencv_imgcodecs -lopencv_objdetect -lopencv_features2d -lopencv_video -lopencv_dnn -lopencv_xfeatures2d -lopencv_plot -lopencv_tracking -lopencv_img_hash" fi echo "Environment variables configured for Linux" else