Browse Source

Reverted image library. It was no longer processing jpg images. Added some saftey features to opening images. Cleaned up the library calls. Finished the lib conversion of PIHash struct. FFI stuff missing, that will come in another point release to the library.

develop
Drew Short 9 years ago
parent
commit
60f7015937
  1. 4
      Cargo.toml
  2. 56
      src/hash/ahash.rs
  3. 58
      src/hash/dhash.rs
  4. 42
      src/hash/mod.rs
  5. 118
      src/hash/phash.rs
  6. 3
      src/lib.rs

4
Cargo.toml

@ -1,6 +1,6 @@
[package] [package]
name = "pihash" name = "pihash"
version = "0.3.0"
version = "0.3.1"
authors = ["Drew Short <warrick@sothr.com>"] authors = ["Drew Short <warrick@sothr.com>"]
description = "A simple library for generating perceptual hashes for images and comparing images based on their perceptual hashes." description = "A simple library for generating perceptual hashes for images and comparing images based on their perceptual hashes."
repository = "https://github.com/warricksothr/Perceptual-Image-Hashing/" repository = "https://github.com/warricksothr/Perceptual-Image-Hashing/"
@ -22,7 +22,7 @@ bench = []
[dependencies] [dependencies]
docopt = "0.6.78" docopt = "0.6.78"
rustc-serialize = "0.3.18" rustc-serialize = "0.3.18"
image = "0.7.0"
image = "0.6.1"
complex = "0.8.0" complex = "0.8.0"
dft = "0.4.1" dft = "0.4.1"
sha1 = "0.1.1" sha1 = "0.1.1"

56
src/hash/ahash.rs

@ -30,34 +30,38 @@ impl<'a> PerceptualHash for AHash<'a> {
* A u64 representing the value of the hash * A u64 representing the value of the hash
*/ */
fn get_hash(&self, _: &Option<Box<Cache>>) -> u64 { fn get_hash(&self, _: &Option<Box<Cache>>) -> u64 {
let (width, height) = self.prepared_image.image.dimensions();
match self.prepared_image.image {
Some(ref image) => {
let (width, height) = image.dimensions();
// calculating the average pixel value
let mut total = 0u64;
for pixel in self.prepared_image.image.pixels() {
let channels = pixel.channels();
// println!("Pixel is: {}", channels[0]);
total += channels[0] as u64;
}
let mean = total / (width * height) as u64;
// println!("Mean for {} is {}", prepared_image.orig_path, mean);
// calculating the average pixel value
let mut total = 0u64;
for pixel in image.pixels() {
let channels = pixel.channels();
// println!("Pixel is: {}", channels[0]);
total += channels[0] as u64;
}
let mean = total / (width * height) as u64;
// println!("Mean for {} is {}", prepared_image.orig_path, mean);
// Calculating a hash based on the mean
let mut hash = 0u64;
for pixel in self.prepared_image.image.pixels() {
let channels = pixel.channels();
let pixel_sum = channels[0] as u64;
if pixel_sum >= mean {
hash |= 1;
// println!("Pixel {} is >= {} therefore {:b}", pixel_sum, mean, hash);
} else {
hash |= 0;
// println!("Pixel {} is < {} therefore {:b}", pixel_sum, mean, hash);
}
hash <<= 1;
// Calculating a hash based on the mean
let mut hash = 0u64;
for pixel in image.pixels() {
let channels = pixel.channels();
let pixel_sum = channels[0] as u64;
if pixel_sum >= mean {
hash |= 1;
// println!("Pixel {} is >= {} therefore {:b}", pixel_sum, mean, hash);
} else {
hash |= 0;
// println!("Pixel {} is < {} therefore {:b}", pixel_sum, mean, hash);
}
hash <<= 1;
}
// println!("Hash for {} is {}", prepared_image.orig_path, hash);
hash
},
None => 0u64
} }
// println!("Hash for {} is {}", prepared_image.orig_path, hash);
hash
} }
} }

58
src/hash/dhash.rs

@ -30,35 +30,39 @@ impl<'a> PerceptualHash for DHash<'a> {
* Returns a u64 representing the value of the hash * Returns a u64 representing the value of the hash
*/ */
fn get_hash(&self, _: &Option<Box<Cache>>) -> u64 { fn get_hash(&self, _: &Option<Box<Cache>>) -> u64 {
// Stored for later
let first_pixel_val = self.prepared_image.image.pixels().nth(0).unwrap().channels()[0];
let last_pixel_val = self.prepared_image.image.pixels().last().unwrap().channels()[0];
match self.prepared_image.image {
Some(ref image) => {
let first_pixel_val = image.pixels().nth(0).unwrap().channels()[0];
let last_pixel_val = image.pixels().last().unwrap().channels()[0];
// Calculate the dhash
let mut previous_pixel_val = 0u64;
let mut hash = 0u64;
for (index, pixel) in self.prepared_image.image.pixels().enumerate() {
if index == 0 {
previous_pixel_val = pixel.channels()[0] as u64;
continue;
}
let channels = pixel.channels();
let pixel_val = channels[0] as u64;
if pixel_val >= previous_pixel_val {
hash |= 1;
} else {
hash |= 0;
}
hash <<= 1;
previous_pixel_val = channels[0] as u64;
}
// Calculate the dhash
let mut previous_pixel_val = 0u64;
let mut hash = 0u64;
for (index, pixel) in image.pixels().enumerate() {
if index == 0 {
previous_pixel_val = pixel.channels()[0] as u64;
continue;
}
let channels = pixel.channels();
let pixel_val = channels[0] as u64;
if pixel_val >= previous_pixel_val {
hash |= 1;
} else {
hash |= 0;
}
hash <<= 1;
previous_pixel_val = channels[0] as u64;
}
if first_pixel_val >= last_pixel_val {
hash |= 1;
} else {
hash |= 0;
}
if first_pixel_val >= last_pixel_val {
hash |= 1;
} else {
hash |= 0;
}
hash
hash
},
None => 0u64
}
} }
} }

42
src/hash/mod.rs

@ -43,7 +43,7 @@ const HAMMING_DISTANCE_SIMILARITY_LIMIT: u64 = 5u64;
*/ */
pub struct PreparedImage<'a> { pub struct PreparedImage<'a> {
orig_path: &'a str, orig_path: &'a str,
image: image::ImageBuffer<image::Luma<u8>, Vec<u8>>,
image: Option<image::ImageBuffer<image::Luma<u8>, Vec<u8>>>,
} }
/** /**
@ -143,37 +143,49 @@ pub fn prepare_image<'a>(path: &'a Path,
Some(image) => { Some(image) => {
PreparedImage { PreparedImage {
orig_path: &*image_path, orig_path: &*image_path,
image: image,
image: Some(image),
} }
} }
None => { None => {
let image = process_image(&path, size);
let processed_image = process_image(&image_path, size);
// Oh, and save it in a cache // Oh, and save it in a cache
match c.put_image_in_cache(&path, size, &image.image) {
Ok(_) => {}
Err(e) => println!("Unable to store image in cache. {}", e),
match processed_image.image {
Some(ref image) => {
match c.put_image_in_cache(&path, size, &image) {
Ok(_) => {}
Err(e) => println!("Unable to store image in cache. {}", e),
};
},
None => {}
}; };
image
processed_image
} }
} }
}, },
None => process_image(&path, size),
None => process_image(&image_path, size),
} }
} }
/** /**
* Turn the image into something we can work with * Turn the image into something we can work with
*/ */
fn process_image(path: &Path,
size: u32) -> PreparedImage{
fn process_image<'a>(image_path: &'a str,
size: u32) -> PreparedImage<'a> {
// Otherwise let's do that work now and store it. // Otherwise let's do that work now and store it.
let image_path = path.to_str().unwrap();
let image = image::open(path).unwrap();
let small_image = image.resize_exact(size, size, FilterType::Lanczos3);
let grey_image = small_image.to_luma();
//println!("Path: {}", image_path);
let image = match image::open(Path::new(image_path)) {
Ok(image) => {
let small_image = image.resize_exact(size, size, FilterType::Lanczos3);
Some(small_image.to_luma())
},
Err(e) => {
println!("Error Processing Image [{}]: {} ", image_path, e);
None
}
};
PreparedImage { PreparedImage {
orig_path: &*image_path, orig_path: &*image_path,
image: grey_image,
image: image,
} }
} }

118
src/hash/phash.rs

@ -32,68 +32,73 @@ impl<'a> PerceptualHash for PHash<'a> {
* Returns a u64 representing the value of the hash * Returns a u64 representing the value of the hash
*/ */
fn get_hash(&self, cache: &Option<Box<Cache>>) -> u64 { fn get_hash(&self, cache: &Option<Box<Cache>>) -> u64 {
// Get the image data into a vector to perform the DFT on.
let width = self.prepared_image.image.width() as usize;
let height = self.prepared_image.image.height() as usize;
// Get 2d data to 2d FFT/DFT
// Either from the cache or calculate it
// Pretty fast already, so caching doesn't make a huge difference
// Atleast compared to opening and processing the images
let data_matrix: Vec<Vec<f64>> = match *cache {
Some(ref c) => {
match c.get_matrix_from_cache(&Path::new(self.prepared_image.orig_path), width as u32) {
Some(matrix) => matrix,
None => {
let matrix = create_data_matrix(width, height, &self.prepared_image);
// Store this DFT in the cache
match c.put_matrix_in_cache(&Path::new(self.prepared_image.orig_path), width as u32, &matrix) {
Ok(_) => {}
Err(e) => println!("Unable to store matrix in cache. {}", e),
};
matrix
match self.prepared_image.image {
Some(ref image) => {
// Get the image data into a vector to perform the DFT on.
let width = image.width() as usize;
let height = image.height() as usize;
// Get 2d data to 2d FFT/DFT
// Either from the cache or calculate it
// Pretty fast already, so caching doesn't make a huge difference
// Atleast compared to opening and processing the images
let data_matrix: Vec<Vec<f64>> = match *cache {
Some(ref c) => {
match c.get_matrix_from_cache(&Path::new(self.prepared_image.orig_path), width as u32) {
Some(matrix) => matrix,
None => {
let matrix = create_data_matrix(width, height, &image);
// Store this DFT in the cache
match c.put_matrix_in_cache(&Path::new(self.prepared_image.orig_path), width as u32, &matrix) {
Ok(_) => {}
Err(e) => println!("Unable to store matrix in cache. {}", e),
};
matrix
}
}
},
None => create_data_matrix(width, height, &image)
};
// Only need the top left quadrant
let target_width = (width / 4) as usize;
let target_height = (height / 4) as usize;
let dft_width = (width / 4) as f64;
let dft_height = (height / 4) as f64;
// Calculate the mean
let mut total = 0f64;
for x in 0..target_width {
for y in 0..target_height {
total += data_matrix[x][y];
} }
} }
},
None => create_data_matrix(width, height, &self.prepared_image)
};
// Only need the top left quadrant
let target_width = (width / 4) as usize;
let target_height = (height / 4) as usize;
let dft_width = (width / 4) as f64;
let dft_height = (height / 4) as f64;
// Calculate the mean
let mut total = 0f64;
for x in 0..target_width {
for y in 0..target_height {
total += data_matrix[x][y];
}
}
let mean = total / (dft_width * dft_height);
// Calculating a hash based on the mean
let mut hash = 0u64;
for x in 0..target_width {
// println!("Mean: {} Values: {:?}",mean,data_matrix[x]);
for y in 0..target_height {
if data_matrix[x][y] >= mean {
hash |= 1;
// println!("Pixel {} is >= {} therefore {:b}", pixel_sum, mean, hash);
} else {
hash |= 0;
// println!("Pixel {} is < {} therefore {:b}", pixel_sum, mean, hash);
let mean = total / (dft_width * dft_height);
// Calculating a hash based on the mean
let mut hash = 0u64;
for x in 0..target_width {
// println!("Mean: {} Values: {:?}",mean,data_matrix[x]);
for y in 0..target_height {
if data_matrix[x][y] >= mean {
hash |= 1;
// println!("Pixel {} is >= {} therefore {:b}", pixel_sum, mean, hash);
} else {
hash |= 0;
// println!("Pixel {} is < {} therefore {:b}", pixel_sum, mean, hash);
}
hash <<= 1;
}
} }
hash <<= 1;
}
// println!("Hash for {} is {}", prepared_image.orig_path, hash);
hash
},
None => 0u64
} }
// println!("Hash for {} is {}", prepared_image.orig_path, hash);
hash
} }
} }
fn create_data_matrix(width: usize, height: usize, prepared_image: &PreparedImage) -> Vec<Vec<f64>> {
fn create_data_matrix(width: usize, height: usize, image: &super::image::ImageBuffer<super::image::Luma<u8>, Vec<u8>>) -> Vec<Vec<f64>> {
let mut data_matrix: Vec<Vec<f64>> = Vec::new(); let mut data_matrix: Vec<Vec<f64>> = Vec::new();
// Preparing the results // Preparing the results
for x in 0..width { for x in 0..width {
@ -102,8 +107,7 @@ fn create_data_matrix(width: usize, height: usize, prepared_image: &PreparedImag
let pos_x = x as u32; let pos_x = x as u32;
let pos_y = y as u32; let pos_y = y as u32;
data_matrix[x] data_matrix[x]
.push(prepared_image
.image
.push(image
.get_pixel(pos_x, pos_y) .get_pixel(pos_x, pos_y)
.channels()[0] as f64); .channels()[0] as f64);
} }

3
src/lib.rs

@ -3,6 +3,9 @@
// Licensed under the MIT license<LICENSE-MIT or http://opensource.org/licenses/MIT>. // Licensed under the MIT license<LICENSE-MIT or http://opensource.org/licenses/MIT>.
// This file may not be copied, modified, or distributed except according to those terms. // This file may not be copied, modified, or distributed except according to those terms.
// Enable nightly features for extra testing behind the bench feature
#![cfg_attr(feature = "bench", feature(test))]
extern crate libc; extern crate libc;
extern crate rustc_serialize; extern crate rustc_serialize;
#[cfg(feature = "bench")] #[cfg(feature = "bench")]

Loading…
Cancel
Save