You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
210 lines
7.6 KiB
210 lines
7.6 KiB
package com.sothr.imagetools.engine.hash
|
|
|
|
import java.awt.image.BufferedImage
|
|
import java.io.{File, FileInputStream}
|
|
import javax.imageio.ImageIO
|
|
|
|
import com.sothr.imagetools.engine.dto.ImageHashDTO
|
|
import com.sothr.imagetools.engine.image.ImageService
|
|
import com.sothr.imagetools.engine.util.{Hamming, PropertiesService}
|
|
import grizzled.slf4j.Logging
|
|
import org.apache.commons.codec.digest.DigestUtils
|
|
import resource._
|
|
|
|
/**
|
|
* A service that exposes the ability to construct perceptive hashes from an
|
|
* image which can be used to find a perceptual difference between two or more
|
|
* images
|
|
*/
|
|
object HashService extends Logging {
|
|
|
|
def getImageHashes(imagePath: String): ImageHashDTO = {
|
|
//debug(s"Creating hashes for $imagePath")
|
|
getImageHashes(ImageIO.read(new File(imagePath)), imagePath)
|
|
}
|
|
|
|
def getImageHashes(image: BufferedImage, imagePath: String): ImageHashDTO = {
|
|
//debug("Creating hashes for an image")
|
|
|
|
var ahash: Long = 0L
|
|
var dhash: Long = 0L
|
|
var phash: Long = 0L
|
|
val sha1: String = getSHA1(imagePath)
|
|
|
|
//Get Image Data
|
|
val grayImage = ImageService.convertToGray(image)
|
|
|
|
if (PropertiesService.useAhash) {
|
|
ahash = getAhash(grayImage, alreadyGray = true)
|
|
}
|
|
if (PropertiesService.useDhash) {
|
|
dhash = getDhash(grayImage, alreadyGray = true)
|
|
}
|
|
if (PropertiesService.usePhash) {
|
|
phash = getPhash(grayImage, alreadyGray = true)
|
|
}
|
|
|
|
val hashes = new ImageHashDTO(ahash, dhash, phash, sha1)
|
|
debug(s"Generated hashes: $hashes")
|
|
|
|
hashes
|
|
}
|
|
|
|
def getAhash(image: BufferedImage, alreadyGray: Boolean = false): Long = {
|
|
//debug("Started generating an AHash")
|
|
var grayImage: BufferedImage = null
|
|
if (alreadyGray) {
|
|
grayImage = image
|
|
} else {
|
|
grayImage = ImageService.convertToGray(image)
|
|
}
|
|
val resizedImage = ImageService.resize(grayImage, PropertiesService.aHashPrecision, forced = true)
|
|
val imageData = ImageService.getImageData(resizedImage)
|
|
AHash.getHash(imageData)
|
|
}
|
|
|
|
def getDhash(image: BufferedImage, alreadyGray: Boolean = false): Long = {
|
|
//debug("Started generating an DHash")
|
|
var grayImage: BufferedImage = null
|
|
if (alreadyGray) {
|
|
grayImage = image
|
|
} else {
|
|
grayImage = ImageService.convertToGray(image)
|
|
}
|
|
val resizedImage = ImageService.resize(grayImage, PropertiesService.dHashPrecision, forced = true)
|
|
val imageData = ImageService.getImageData(resizedImage)
|
|
DHash.getHash(imageData)
|
|
}
|
|
|
|
def getPhash(image: BufferedImage, alreadyGray: Boolean = false): Long = {
|
|
//debug("Started generating an PHash")
|
|
var grayImage: BufferedImage = null
|
|
if (alreadyGray) {
|
|
grayImage = image
|
|
} else {
|
|
grayImage = ImageService.convertToGray(image)
|
|
}
|
|
val resizedImage = ImageService.resize(grayImage, PropertiesService.pHashPrecision, forced = true)
|
|
val imageData = ImageService.getImageData(resizedImage)
|
|
PHash.getHash(imageData)
|
|
}
|
|
|
|
def getMD5(filePath: String): String = {
|
|
managed(new FileInputStream(filePath)) acquireAndGet {
|
|
input => DigestUtils.md5Hex(input)
|
|
}
|
|
}
|
|
|
|
def getSHA1(filePath: String): String = {
|
|
managed(new FileInputStream(filePath)) acquireAndGet {
|
|
input => DigestUtils.sha1Hex(input)
|
|
}
|
|
}
|
|
|
|
def areAhashSimilar(ahash1: Long, ahash2: Long): Boolean = {
|
|
val tolerence = PropertiesService.aHashTolerance
|
|
val distance = Hamming.getDistance(ahash1, ahash2)
|
|
//debug(s"hash1: $ahash1 hash2: $ahash2 tolerence: $tolerence hamming distance: $distance")
|
|
if (distance <= tolerence) true else false
|
|
}
|
|
|
|
def areDhashSimilar(dhash1: Long, dhash2: Long): Boolean = {
|
|
val tolerence = PropertiesService.dHashTolerance
|
|
val distance = Hamming.getDistance(dhash1, dhash2)
|
|
//debug(s"hash1: $dhash1 hash2: $dhash2 tolerence: $tolerence hamming distance: $distance")
|
|
if (distance <= tolerence) true else false
|
|
}
|
|
|
|
def arePhashSimilar(phash1: Long, phash2: Long): Boolean = {
|
|
val tolerence = PropertiesService.pHashTolerance
|
|
val distance = Hamming.getDistance(phash1, phash2)
|
|
//debug(s"hash1: $phash1 hash2: $phash2 tolerence: $tolerence hamming distance: $distance")
|
|
if (distance <= tolerence) true else false
|
|
}
|
|
|
|
def areImageHashesSimilar(imageHash1: ImageHashDTO, imageHash2: ImageHashDTO): Boolean = {
|
|
val weightedHammingMean = getWeightedHashSimilarity(imageHash1, imageHash2)
|
|
val weightedToleranceMean = getWeightedHashTolerence
|
|
if (weightedHammingMean <= weightedToleranceMean) true else false
|
|
}
|
|
|
|
def getWeightedHashSimilarity(imageHash1: ImageHashDTO, imageHash2: ImageHashDTO): Float = {
|
|
//ahash
|
|
val aHashTolerance = PropertiesService.aHashTolerance
|
|
val aHashWeight = PropertiesService.aHashWeight
|
|
val useAhash = PropertiesService.useAhash
|
|
//dhash
|
|
val dHashTolerance = PropertiesService.dHashTolerance
|
|
val dHashWeight = PropertiesService.dHashWeight
|
|
val useDhash = PropertiesService.useAhash
|
|
//phash
|
|
val pHashTolerance = PropertiesService.pHashTolerance
|
|
val pHashWeight = PropertiesService.pHashWeight
|
|
val usePhash = PropertiesService.useAhash
|
|
|
|
//calculate weighted values
|
|
var weightedHammingTotal: Float = 0
|
|
var methodsTotal = 0
|
|
|
|
if (useAhash) {
|
|
val hamming = Hamming.getDistance(imageHash1.ahash, imageHash2.ahash)
|
|
weightedHammingTotal += hamming * aHashWeight
|
|
//debug(s"hash1: ${imageHash1.ahash} hash2: ${imageHash1.ahash} tolerence: $aHashTolerance hamming distance: $hamming weight: $aHashWeight")
|
|
methodsTotal += 1
|
|
}
|
|
if (useDhash) {
|
|
val hamming = Hamming.getDistance(imageHash1.dhash, imageHash2.dhash)
|
|
weightedHammingTotal += hamming * dHashWeight
|
|
//debug(s"hash1: ${imageHash1.dhash} hash2: ${imageHash1.dhash} tolerence: $dHashTolerance hamming distance: $hamming weight: $dHashWeight")
|
|
methodsTotal += 1
|
|
}
|
|
if (usePhash) {
|
|
val hamming = Hamming.getDistance(imageHash1.phash, imageHash2.phash)
|
|
weightedHammingTotal += hamming * pHashWeight
|
|
//debug(s"hash1: ${imageHash1.phash} hash2: ${imageHash1.phash} tolerence: $pHashTolerance hamming distance: $hamming weight: $pHashWeight")
|
|
methodsTotal += 1
|
|
}
|
|
val weightedHammingMean = weightedHammingTotal / methodsTotal
|
|
//debug(s"Calculated Weighted Hamming Mean: $weightedHammingMean")
|
|
weightedHammingMean
|
|
}
|
|
|
|
def getWeightedHashTolerence: Float = {
|
|
//ahash
|
|
val aHashTolerance = PropertiesService.aHashTolerance
|
|
val aHashWeight = PropertiesService.aHashWeight
|
|
val useAhash = PropertiesService.useAhash
|
|
//dhash
|
|
val dHashTolerance = PropertiesService.dHashTolerance
|
|
val dHashWeight = PropertiesService.dHashWeight
|
|
val useDhash = PropertiesService.useAhash
|
|
//phash
|
|
val pHashTolerance = PropertiesService.pHashTolerance
|
|
val pHashWeight = PropertiesService.pHashWeight
|
|
val usePhash = PropertiesService.useAhash
|
|
|
|
//calculate weighted values
|
|
var weightedToleranceTotal: Float = 0
|
|
var methodsTotal = 0
|
|
|
|
if (useAhash) {
|
|
weightedToleranceTotal += aHashTolerance * aHashWeight
|
|
//debug(s"Ahash Tolerance: $aHashTolerance Current Weighted Tolerance: $weightedToleranceTotal")
|
|
methodsTotal += 1
|
|
}
|
|
if (useDhash) {
|
|
weightedToleranceTotal += dHashTolerance * dHashWeight
|
|
//debug(s"Dhash Tolerance: $dHashTolerance Current Weighted Tolerance: $weightedToleranceTotal")
|
|
methodsTotal += 1
|
|
}
|
|
if (usePhash) {
|
|
weightedToleranceTotal += pHashTolerance * pHashWeight
|
|
//debug(s"Phash Tolerance: $pHashTolerance Current Weighted Tolerance: $weightedToleranceTotal")
|
|
methodsTotal += 1
|
|
}
|
|
val weightedTolerance = weightedToleranceTotal / methodsTotal
|
|
//debug(s"Calculated Weighted Tolerance: $weightedTolerance")
|
|
weightedTolerance
|
|
}
|
|
|
|
}
|