From 7f7e232832b92ba189d0a8c616ed26a73c97cd58 Mon Sep 17 00:00:00 2001 From: John Brooks Date: Wed, 11 Apr 2018 20:54:52 -0700 Subject: [PATCH 1/2] Add contrib/img_hash module --- .travis.yml | 2 +- appveyor.yml | 2 +- contrib/img_hash.cpp | 43 ++++++++++++++ contrib/img_hash.go | 108 ++++++++++++++++++++++++++++++++++ contrib/img_hash.h | 29 ++++++++++ contrib/img_hash_test.go | 122 +++++++++++++++++++++++++++++++++++++++ env.cmd | 2 +- env.sh | 8 +-- 8 files changed, 309 insertions(+), 7 deletions(-) create mode 100644 contrib/img_hash.cpp create mode 100644 contrib/img_hash.go create mode 100644 contrib/img_hash.h create mode 100644 contrib/img_hash_test.go 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 From 2f688bf1c6a05d71caf27e926bdbdb9cb8a78ed0 Mon Sep 17 00:00:00 2001 From: John Brooks Date: Thu, 12 Apr 2018 08:41:43 -0700 Subject: [PATCH 2/2] Add img-similarity as an example for img_hash --- Makefile | 2 +- cmd/README.md | 6 ++ cmd/img-similarity/main.go | 133 +++++++++++++++++++++++++++++++++++++ 3 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 cmd/img-similarity/main.go diff --git a/Makefile b/Makefile index a1d9c150..6cef694e 100644 --- a/Makefile +++ b/Makefile @@ -61,7 +61,7 @@ astyle: install: download build clean -CMDS=basic-drawing caffe-classifier captest capwindow counter faceblur facedetect find-circles hand-gestures mjpeg-streamer motion-detect pose saveimage savevideo showimage ssd-facedetect tf-classifier tracking version +CMDS=basic-drawing caffe-classifier captest capwindow counter faceblur facedetect find-circles hand-gestures img-similarity mjpeg-streamer motion-detect pose saveimage savevideo showimage ssd-facedetect tf-classifier tracking version cmds: for cmd in $(CMDS) ; do \ go build -o build/$$cmd cmd/$$cmd/main.go ; diff --git a/cmd/README.md b/cmd/README.md index a40adf41..5db18e52 100644 --- a/cmd/README.md +++ b/cmd/README.md @@ -50,6 +50,12 @@ Count the number of fingers being held up in front of the camera by looking for https://github.com/hybridgroup/gocv/blob/master/cmd/hand-gestures/main.go +## Img-similarity + +Compute and compare perceptual hashes for a pair of images, with a variety of algorithms. + +https://github.com/hybridgroup/gocv/blob/master/cmd/img-similarity/main.go + ## MJPEG-Streamer Opens a video capture device, then streams MJPEG from it that you can view in any browser. diff --git a/cmd/img-similarity/main.go b/cmd/img-similarity/main.go new file mode 100644 index 00000000..15c4f48a --- /dev/null +++ b/cmd/img-similarity/main.go @@ -0,0 +1,133 @@ +// What it does: +// +// This example calculates perceptual hashes for a pair of images, +// and prints the hashes and calculated similarity between them. +// A variety of algorithms are supported. +// +// How to run: +// +// img-similarity [-flags] [image1.jpg] [image2.jpg] +// +// go run ./cmd/img-similarity/main.go -all images/space_shuttle.jpg images/toy.jpg +// +// +build example + +package main + +import ( + "flag" + "fmt" + "strings" + + "gocv.io/x/gocv" + "gocv.io/x/gocv/contrib" +) + +var ( + useAll = flag.Bool("all", false, "Compute all hashes") + usePHash = flag.Bool("phash", false, "Compute PHash") + useAverage = flag.Bool("average", false, "Compute AverageHash") + useBlockMean0 = flag.Bool("blockmean0", false, "Compute BlockMeanHash mode 0") + useBlockMean1 = flag.Bool("blockmean1", false, "Compute BlockMeanHash mode 1") + useColorMoment = flag.Bool("colormoment", false, "Compute ColorMomentHash") + useMarrHildreth = flag.Bool("marrhildreth", false, "Compute MarrHildrethHash") + useRadialVariance = flag.Bool("radialvariance", false, "Compute RadialVarianceHash") +) + +func setupHashes() []contrib.ImgHashBase { + var hashes []contrib.ImgHashBase + + if *usePHash || *useAll { + hashes = append(hashes, contrib.PHash{}) + } + if *useAverage || *useAll { + hashes = append(hashes, contrib.AverageHash{}) + } + if *useBlockMean0 || *useAll { + hashes = append(hashes, contrib.BlockMeanHash{}) + } + if *useBlockMean1 || *useAll { + hashes = append(hashes, contrib.BlockMeanHash{Mode: contrib.BlockMeanHashMode1}) + } + if *useColorMoment || *useAll { + hashes = append(hashes, contrib.ColorMomentHash{}) + } + if *useMarrHildreth || *useAll { + // MarrHildreth has default parameters for alpha/scale + hashes = append(hashes, contrib.NewMarrHildrethHash()) + } + if *useRadialVariance || *useAll { + // RadialVariance has default parameters too + hashes = append(hashes, contrib.NewRadialVarianceHash()) + } + + // If no hashes were selected, behave as if all hashes were selected + if len(hashes) == 0 { + *useAll = true + return setupHashes() + } + + return hashes +} + +func main() { + flag.Usage = func() { + fmt.Println("How to run:\n\timg-similarity [-flags] [image1.jpg] [image2.jpg]") + flag.PrintDefaults() + } + + printHashes := flag.Bool("print", false, "print hash values") + flag.Parse() + if flag.NArg() < 2 { + flag.Usage() + return + } + + // read images + inputs := flag.Args() + images := make([]gocv.Mat, len(inputs)) + + for i := 0; i < 2; i++ { + img := gocv.IMRead(inputs[i], gocv.IMReadColor) + if img.Empty() { + fmt.Printf("cannot read image %s\n", inputs[i]) + return + } + defer img.Close() + + images[i] = img + } + + // construct all of the hash types in a list. normally, you'd only use one of these. + hashes := setupHashes() + + // compute and compare the images for each hash type + for _, hash := range hashes { + results := make([]gocv.Mat, len(images)) + + for i, img := range images { + results[i] = gocv.NewMat() + defer results[i].Close() + hash.Compute(img, &results[i]) + if results[i].Empty() { + fmt.Printf("error computing hash for %s\n", inputs[i]) + return + } + } + + // compare for similarity; this returns a float64, but the meaning of values is + // unique to each algorithm. + similar := hash.Compare(results[0], results[1]) + + // make a pretty name for the hash + name := strings.TrimPrefix(fmt.Sprintf("%T", hash), "contrib.") + fmt.Printf("%s: similarity %g\n", name, similar) + + if *printHashes { + // print hash result for each image + for i, path := range inputs { + fmt.Printf("\t%s = %x\n", path, results[i].ToBytes()) + } + } + } +}