pdq/cpp/io/pdqio.cpp (158 lines of code) (raw):

// ================================================================ // Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved // ================================================================ #include <pdq/cpp/hashing/pdqhashing.h> #include <pdq/cpp/downscaling/downscaling.h> #include <pdq/cpp/hashing/torben.h> #include <stdlib.h> #include <math.h> #include <chrono> #include "CImg.h" using namespace std; using namespace cimg_library; namespace facebook { namespace pdq { namespace hashing { // The two-pass Jarosz filter is prohibitively expensive for larger images // so we use off-the-shelf downsampling to get to an intermediate size. const int DOWNSAMPLE_DIMS = 512; // ---------------------------------------------------------------- void showDecoderInfo() { cimg_library::cimg::info(); } // ---------------------------------------------------------------- // Returns matrix as num_rows x num_cols in row-major order. // The caller must free the return value. float* loadFloatLumaFromCImg(CImg<uint8_t>& img, int& numRows, int& numCols) { if (img.spectrum() == 3) { // X,Y,Z,color -> column,row,zero,{R,G,B} uint8_t* pr = &img(0, 0, 0, 0); uint8_t* pg = &img(0, 0, 0, 1); uint8_t* pb = &img(0, 0, 0, 2); numRows = img.height(); numCols = img.width(); int rowStride = &img(0, 1, 0, 0) - &img(0, 0, 0, 0); int colStride = &img(1, 0, 0, 0) - &img(0, 0, 0, 0); float* luma = new float[numRows * numCols]; facebook::pdq::downscaling::fillFloatLumaFromRGB( pr, pg, pb, numRows, numCols, rowStride, colStride, luma); return luma; } else if (img.spectrum() == 1) { uint8_t* p = &img(0, 0, 0, 0); numRows = img.height(); numCols = img.width(); int rowStride = &img(0, 1, 0, 0) - &img(0, 0, 0, 0); int colStride = &img(1, 0, 0, 0) - &img(0, 0, 0, 0); // xxx cl float* luma = new float[numRows * numCols]; facebook::pdq::downscaling::fillFloatLumaFromGrey( p, numRows, numCols, rowStride, colStride, luma); return luma; } else { fprintf(stderr, "Internal coding error detected at file %s line %d.\n", __FILE__, __LINE__); exit(1); return nullptr; // not reached } } // ---------------------------------------------------------------- bool pdqHash256FromFile( const char* filename, Hash256& hash, int& quality, int& imageHeightTimesWidth, float& readSeconds, float& hashSeconds ) { chrono::time_point<chrono::system_clock> t1 = chrono::system_clock::now(); if (!filename) { return false; } CImg<uint8_t> input; try { input.load(filename); } catch (const CImgIOException& ex){ return false; } chrono::time_point<chrono::system_clock> t2 = chrono::system_clock::now(); chrono::duration<float> elapsed_seconds_outer = t2 - t1; readSeconds = elapsed_seconds_outer.count(); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if (input.height() > DOWNSAMPLE_DIMS || input.width() > DOWNSAMPLE_DIMS) { input = input.resize(DOWNSAMPLE_DIMS, DOWNSAMPLE_DIMS); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - imageHeightTimesWidth = input.height() * input.width(); t1 = chrono::system_clock::now(); int numRows, numCols; float* fullBuffer1 = loadFloatLumaFromCImg(input, numRows, numCols); float* fullBuffer2 = new float[numRows * numCols]; float buffer64x64[64][64]; float buffer16x64[16][64]; float buffer16x16[16][16]; pdqHash256FromFloatLuma(fullBuffer1, fullBuffer2, numRows, numCols, buffer64x64, buffer16x64, buffer16x16, hash, quality); delete[] fullBuffer1; delete[] fullBuffer2; t2 = chrono::system_clock::now(); elapsed_seconds_outer = t2 - t1; hashSeconds = elapsed_seconds_outer.count(); return true; } // ---------------------------------------------------------------- bool pdqDihedralHash256esFromFile( const char* filename, Hash256* hashptrOriginal, Hash256* hashptrRotate90, Hash256* hashptrRotate180, Hash256* hashptrRotate270, Hash256* hashptrFlipX, Hash256* hashptrFlipY, Hash256* hashptrFlipPlus1, Hash256* hashptrFlipMinus1, int& quality, int& imageHeightTimesWidth, float& readSeconds, float& hashSeconds ) { chrono::time_point<chrono::system_clock> t1 = chrono::system_clock::now(); if (!filename) { return false; } CImg<uint8_t> input; try { input.load(filename); } catch (const CImgIOException& ex){ return false; } chrono::time_point<chrono::system_clock> t2 = chrono::system_clock::now(); chrono::duration<float> elapsed_seconds_outer = t2 - t1; readSeconds = elapsed_seconds_outer.count(); if (input.height() > DOWNSAMPLE_DIMS || input.width() > DOWNSAMPLE_DIMS) { input = input.resize(DOWNSAMPLE_DIMS, DOWNSAMPLE_DIMS); } imageHeightTimesWidth = input.height() * input.width(); t1 = chrono::system_clock::now(); int numRows, numCols; float* fullBuffer1 = loadFloatLumaFromCImg(input, numRows, numCols); float* fullBuffer2 = new float[numRows * numCols]; float buffer64x64[64][64]; float buffer16x64[16][64]; float buffer16x16[16][16]; float buffer16x16Aux[16][16]; bool rv = pdqDihedralHash256esFromFloatLuma( fullBuffer1, fullBuffer2, numRows, numCols, buffer64x64, buffer16x64, buffer16x16, buffer16x16Aux, hashptrOriginal, hashptrRotate90, hashptrRotate180, hashptrRotate270, hashptrFlipX, hashptrFlipY, hashptrFlipPlus1, hashptrFlipMinus1, quality ); delete[] fullBuffer1; delete[] fullBuffer2; t2 = chrono::system_clock::now(); elapsed_seconds_outer = t2 - t1; hashSeconds = elapsed_seconds_outer.count(); return rv; } // ---------------------------------------------------------------- // matrix as num_rows x num_cols in row-major order void floatMatrixToCImg(float* matrix, int numRows, int numCols, const char filename[]) { CImg<float> cimg(numCols, numRows); for (int i = 0; i < numRows; i++) { for (int j = 0; j < numCols; j++) { cimg(j, i) = matrix[i * numCols + j]; } } cimg.save(filename); printf("Saved tap %s\n", filename); } } // namespace hashing } // namespace pdq } // namespace facebook