What is FPEG?
FPEG detects faces in an image and compresses those regions less than the surrounding pixels. In theory the human visual cortex is more sensitive to compression artifacts on faces then surrounding context, therefore using higher compression in non-facial regions will lead to smaller images with comparable perceived quality. — Andrew Schreiber
Using the face detection API at RapidAPI to reduce the background quality of images while maintaining the resolution of faces and reduce the image size considerably. The name is a play on “Faces JPEG”. This library is based on the strategy of FPEG by Andrew Schreiber.
Using Face Detection with PHP Example
This is a single page PHP script that takes a URL to a jpg, png, webp, bmp, or gif and finds all the faces in the image then reduces the quality of the background while saving the resolution of the faces. The effect of this transformation is an image that appears to maintain the resolution of the original image because the faces in the image are the focus and the resolution of the faces has not changed.
The example image we are using can be found at https://upload.wikimedia.org/wikipedia/en/7/7d/Lenna_%28test_image%29.png
Output size is flexible based on the -b=backgroundQuality
and -f=foregroundQuality
variables. The effect of this transformation on an example image measured backgroundQuality/foregroundQuality
@ 20/80 is:
Original Image | FPEG Image @ 20/80 quality |
---|---|
Size: 473,831 | Size: 32,161 |
Architecture
- An image is loaded from a URL.
- The settings for the script are the image URL and optionally the foreground image quality, the background image quality, and the accuracy boost.
- Adjust the image quality as desired.
The RapidAPI Face Detection API is used to find all faces in the image, then each face is copied from the original image into a working image at the same location.
After all the faces have been copied the original image is reduced in quality through jpeg compression.
The compressed image is then loaded back into the script and the faces from the working image are copied onto the original image. Then the original image is saved as output.jpg.
RapidAPI offers several other face detection, facial recognition APIs, and OCR APIs, but for this script, we only need to perform facial detection.
How to Perform Facial Detection with PHP (using a Face Detector API)
Making requests to the API is uncomplicated with only 2 endpoints and 2 parameters. There is an endpoint for detecting faces and calculating age and gender and an endpoint for just detecting faces.
In this article, we’ll be using the second endpoint because we are only interested in the positions of faces and not the details of each face.
The endpoint URL is located at https://face-detection6.p.rapidapi.com/img/face which only accepts a POST. The POST parameters are
url: "path-to-image" accuracy_boost: default 2; range 1-4
The API returns a response like
{ "detected_faces": [ { "BoundingBox": { "startX": 151, "startY": 113, "endX": 402, "endY": 479 }, "Probability": 83.6599349975586 } ] }
This JSON result tells us:
- where each face is,
- where it starts,
- and the dimensions from the start.
"detected_faces
” is an array of all the faces found in the url
image.
Parameters
-i Url to input image (required) -a Accuracy Boost [1-4] default 2 (optional) -b Background JPEG Quality default 20 (optional) -f Foreground JPEG Quality default 80 (optional)
Execute
Copy the code to a local file fpeg.php
. Then run the script with the image and quality parameters
php fpeg.php -i=https://upload.wikimedia.org/wikipedia/en/7/7d/Lenna_%28test_image%29.png -b=20 -f=80
and a new file, output.jpg
, will be created in the same directory.
1. Set Your API Key
// Set your RapidAPI key $rapidApiKey = '1234567890123456789012345678901234567890';
2. Set Command Line Parameters
// Command Line Parameters $options = getopt("" . "i:" // Image URL . "a::" // Accuracy Boost 1-4 . "b::" // Background Quality (optional) . "f::" // Foreground Quality (optional) , [] );
3. Set the image URL and JPEG quality for the output image
// Set the image url and jpeg quality for the output image $imageUrl = $options['i'] ?? ''; if (! filter_var($imageUrl, FILTER_VALIDATE_URL)) { throw new Exception('Invalid URL'); }
4. Fetch accuracy boost, background quality, and foreground quality
// Fetch accuracy boost $accuracyBoost = $options['a'] ?? 2; if ($accuracyBoost < 1) { $accuracyBoost = 1; } elseif ($accuracyBoost > 4) { $accuracyBoost = 4; } // Fetch background quality and validate $backgroundQuality = $options['b'] ?? 20; if ($backgroundQuality < 1) { $backgroundQuality = 1; } elseif ($backgroundQuality > 100) { $backgroundQuality = 100; } // Fetch foreground quality and validate $foregroundQuality = $options['f'] ?? 80; if ($foregroundQuality < 1) { $foregroundQuality = 1; } elseif ($foregroundQuality > 100) { $foregroundQuality = 100; }
5. Get the file size of the image
// Get the file size of the remote image $curl = curl_init($imageUrl); curl_setopt_array($curl, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HEADER => true, CURLOPT_NOBODY => true, ]); curl_exec($curl); $error = curl_error($curl); if ($error) { throw new Exception( "Unable to fetch remote image size. Curl Error #:" . $error ); } $imageFileSize = curl_getinfo($curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD); curl_close($curl); // Init curl for the request to RapidAPI $curl = curl_init(); curl_setopt_array($curl, array( CURLOPT_URL => "https://face-detection6.p.rapidapi.com/img/face", CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_CUSTOMREQUEST => "POST", CURLOPT_POSTFIELDS => "{"url":"$imageUrl","accuracy_boost":$accuracyBoost}", CURLOPT_HTTPHEADER => array( "accept: application/json", "content-type: application/json", "x-rapidapi-host: face-detection6.p.rapidapi.com", "x-rapidapi-key: $rapidApiKey" ), )); // Execute the curl and decode the response $response = curl_exec($curl); $error = curl_error($curl); curl_close($curl); if ($error) { throw new Exception( "Error response from Face Detection API. curl Error #:" . $error ); } $response = json_decode($response, true);
6. Init and execute curl request
// Init curl for the request to RapidAPI $curl = curl_init(); curl_setopt_array($curl, array( CURLOPT_URL => "https://face-detection6.p.rapidapi.com/img/face", CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_CUSTOMREQUEST => "POST", CURLOPT_POSTFIELDS => "{"url":"$imageUrl","accuracy_boost":$accuracyBoost}", CURLOPT_HTTPHEADER => array( "accept: application/json", "content-type: application/json", "x-rapidapi-host: face-detection6.p.rapidapi.com", "x-rapidapi-key: $rapidApiKey" ), )); // Execute the curl and decode the response $response = curl_exec($curl); $error = curl_error($curl); curl_close($curl); if ($error) { throw new Exception( "Error response from Face Detection API. curl Error #:" . $error ); } $response = json_decode($response, true);
7. Initialize the working images
// Initialize the working images $imageDimentions = getimagesize($imageUrl); switch($imageDimentions['mime']) { case 'image/gif': $imageResource = imagecreatefromgif($imageUrl); break; case 'image/png': $imageResource = imagecreatefrompng($imageUrl); break; case 'image/webp': $imageResource = imagecreatefromwebp($imageUrl); break; case 'image/x-ms-bmp': $imageResource = imagecreatefrombmp($imageUrl); break; case 'image/jpeg': $imageResource = imagecreatefromjpeg($imageUrl); break; default: throw new Exception('Unsupported image type'); } $workingImageResource = imagecreatetruecolor($imageDimentions[0], $imageDimentions[1]);
8. Copy faces from the initial image onto the working image
// Copy faces from initial image onto working image $faceCount = 0; foreach ($response['detected_faces'] as $face) { $boundingBox = $face['BoundingBox']; $success = imagecopy( $workingImageResource, $imageResource, $boundingBox['startX'], // Source X $boundingBox['startY'], // Source Y $boundingBox['startX'], // Destination X $boundingBox['startY'], // Destination Y $boundingBox['endX'] - $boundingBox['startX'], // width $boundingBox['endY'] - $boundingBox['startY'] // height ); $faceCount ++; if (! $success) { throw new Exception(error_get_last()['message']); } } // All faces are now copied into the working image
9. Reduce image quality of the original image and Apply faces to the reduced background image
// Reduce image quality of Source image $imageTempName = tempnam(sys_get_temp_dir(), 'fpeg'); imagejpeg($imageResource, $imageTempName, $backgroundQuality); // Load quality reduced image $imageResource = imagecreatefromjpeg($imageTempName); unlink($imageTempName); // Apply faces to reduced background quality image foreach ($response['detected_faces'] as $face) { $boundingBox = $face['BoundingBox']; $success = imagecopy( $imageResource, $workingImageResource, $boundingBox['startX'], // Source X $boundingBox['startY'], // Source Y $boundingBox['startX'], // Destination X $boundingBox['startY'], // Destination Y $boundingBox['endX'] - $boundingBox['startX'], // width $boundingBox['endY'] - $boundingBox['startY'] // height ); if (! $success) { throw new Exception(error_get_last()['message']); } }
10. Verify faces were found and save the image
// Verify faces were found if (! $faceCount) { throw new Exception('No faces were found in image'); } // Save the created image and output statistics imagejpeg($imageResource, 'output.jpg', $foregroundQuality); $imageOutputSize = filesize('output.jpg'); if ($imageFileSize <= $imageOutputSize) { throw new Exception("FPEG DID NOT IMPROVE COMPRESSION"); } echo 'Original Size: ' . $imageFileSize . "n"; echo 'FPEG Size: ' . $imageOutputSize . "n"; echo 'Faces: ' . $faceCount . "n"; imagedestroy($imageResource); imagedestroy($workingImageResource);
The Full Code
<?php /** * Copyright (c) 2020 API Skeletons <contact@apiskeletons.com> * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ // Set your RapidAPI key $rapidApiKey = '1234567890123456789012345678901234567890'; // Command Line Parameters $options = getopt("" . "i:" // Image URL . "a::" // Accuracy Boost 1-4 . "b::" // Background Quality (optional) . "f::" // Foreground Quality (optional) , [] ); // Set the image url and jpeg quality for the output image $imageUrl = $options['i'] ?? ''; if (! filter_var($imageUrl, FILTER_VALIDATE_URL)) { throw new Exception('Invalid URL'); } // Fetch accuracy boost $accuracyBoost = $options['a'] ?? 2; if ($accuracyBoost < 1) { $accuracyBoost = 1; } elseif ($accuracyBoost > 4) { $accuracyBoost = 4; } // Fetch background quality and validate $backgroundQuality = $options['b'] ?? 20; if ($backgroundQuality < 1) { $backgroundQuality = 1; } elseif ($backgroundQuality > 100) { $backgroundQuality = 100; } // Fetch foreground quality and validate $foregroundQuality = $options['f'] ?? 80; if ($foregroundQuality < 1) { $foregroundQuality = 1; } elseif ($foregroundQuality > 100) { $foregroundQuality = 100; } // Get the file size of the remote image $curl = curl_init($imageUrl); curl_setopt_array($curl, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HEADER => true, CURLOPT_NOBODY => true, ]); curl_exec($curl); $error = curl_error($curl); if ($error) { throw new Exception( "Unable to fetch remote image size. Curl Error #:" . $error ); } $imageFileSize = curl_getinfo($curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD); curl_close($curl); // Init curl for the request to RapidAPI $curl = curl_init(); curl_setopt_array($curl, array( CURLOPT_URL => "https://face-detection6.p.rapidapi.com/img/face", CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_CUSTOMREQUEST => "POST", CURLOPT_POSTFIELDS => "{"url":"$imageUrl","accuracy_boost":$accuracyBoost}", CURLOPT_HTTPHEADER => array( "accept: application/json", "content-type: application/json", "x-rapidapi-host: face-detection6.p.rapidapi.com", "x-rapidapi-key: $rapidApiKey" ), )); // Execute the curl and decode the response $response = curl_exec($curl); $error = curl_error($curl); curl_close($curl); if ($error) { throw new Exception( "Error response from Face Detection API. curl Error #:" . $error ); } $response = json_decode($response, true); // Initialize the working images $imageDimentions = getimagesize($imageUrl); switch($imageDimentions['mime']) { case 'image/gif': $imageResource = imagecreatefromgif($imageUrl); break; case 'image/png': $imageResource = imagecreatefrompng($imageUrl); break; case 'image/webp': $imageResource = imagecreatefromwebp($imageUrl); break; case 'image/x-ms-bmp': $imageResource = imagecreatefrombmp($imageUrl); break; case 'image/jpeg': $imageResource = imagecreatefromjpeg($imageUrl); break; default: throw new Exception('Unsupported image type'); } $workingImageResource = imagecreatetruecolor($imageDimentions[0], $imageDimentions[1]); // Copy faces from initial image onto working image $faceCount = 0; foreach ($response['detected_faces'] as $face) { $boundingBox = $face['BoundingBox']; $success = imagecopy( $workingImageResource, $imageResource, $boundingBox['startX'], // Source X $boundingBox['startY'], // Source Y $boundingBox['startX'], // Destination X $boundingBox['startY'], // Destination Y $boundingBox['endX'] - $boundingBox['startX'], // width $boundingBox['endY'] - $boundingBox['startY'] // height ); $faceCount ++; if (! $success) { throw new Exception(error_get_last()['message']); } } // All faces are now copied into the working image // Reduce image quality of Source image $imageTempName = tempnam(sys_get_temp_dir(), 'fpeg'); imagejpeg($imageResource, $imageTempName, $backgroundQuality); // Load quality reduced image $imageResource = imagecreatefromjpeg($imageTempName); unlink($imageTempName); // Apply faces to reduced background quality image foreach ($response['detected_faces'] as $face) { $boundingBox = $face['BoundingBox']; $success = imagecopy( $imageResource, $workingImageResource, $boundingBox['startX'], // Source X $boundingBox['startY'], // Source Y $boundingBox['startX'], // Destination X $boundingBox['startY'], // Destination Y $boundingBox['endX'] - $boundingBox['startX'], // width $boundingBox['endY'] - $boundingBox['startY'] // height ); if (! $success) { throw new Exception(error_get_last()['message']); } } // Verify faces were found if (! $faceCount) { throw new Exception('No faces were found in image'); } // Save the created image and output statistics imagejpeg($imageResource, 'output.jpg', $foregroundQuality); $imageOutputSize = filesize('output.jpg'); if ($imageFileSize <= $imageOutputSize) { throw new Exception("FPEG DID NOT IMPROVE COMPRESSION"); } echo 'Original Size: ' . $imageFileSize . "n"; echo 'FPEG Size: ' . $imageOutputSize . "n"; echo 'Faces: ' . $faceCount . "n"; imagedestroy($imageResource); imagedestroy($workingImageResource);
Conclusion
The clever technique this script creates using the face detection API at RapidAPI will reduce your website’s load time across any device.
If you have a large number of images primarily of faces (such as LinkedIn), then this technique could really change your company’s offering.
Leave a Reply