Skip to content

Commit

Permalink
Add contrib/img_hash module
Browse files Browse the repository at this point in the history
  • Loading branch information
special committed Apr 12, 2018
1 parent ab6d932 commit 7f7e232
Show file tree
Hide file tree
Showing 8 changed files with 309 additions and 7 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
43 changes: 43 additions & 0 deletions contrib/img_hash.cpp
Original file line number Diff line number Diff line change
@@ -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);
}
108 changes: 108 additions & 0 deletions contrib/img_hash.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package contrib

//#include <stdlib.h>
//#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.
29 changes: 29 additions & 0 deletions contrib/img_hash.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#ifndef _OPENCV3_IMG_HASH_H_
#define _OPENCV3_IMG_HASH_H_

#ifdef __cplusplus
#include <opencv2/opencv.hpp>
#include <opencv2/img_hash.hpp>
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_
122 changes: 122 additions & 0 deletions contrib/img_hash_test.go
Original file line number Diff line number Diff line change
@@ -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()) })
}
2 changes: 1 addition & 1 deletion env.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -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.
)
8 changes: 4 additions & 4 deletions env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,26 @@ 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"
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
Expand Down

0 comments on commit 7f7e232

Please sign in to comment.