Skip to content

Commit

Permalink
Add the sixth model to the application. Aka DlibTaguchiHog model. =)
Browse files Browse the repository at this point in the history
In this model 6, we use the model newly trained by Tanguchi to obtain
the descriptor and perform the face recognition.

This model was trained from scratch to slightly improve the bias of the
original model on non-Caucasian/American people. It obtained a similar
result in the LFW tests, slightly lower, but within the acceptable
margins of error.

It may still have bias problems, they recognize that it fails with
African people and with childrens, but in principle it seems like an
improvement to the model used in 1, 2, 3 and 4. 😉

See: https://github.com/TaguchiModels/dlibModels

This model is added for testing, and I understand that it will be of
great help to those who have had problems with the original model.
  • Loading branch information
matiasdelellis committed May 16, 2024
1 parent 757eebb commit 2beb9bd
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 9 deletions.
6 changes: 3 additions & 3 deletions lib/Model/DlibCnnModel/DlibCnnModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -155,13 +155,13 @@ public function install() {

/* Download and install models */
$detectorModelBz2 = $this->downloadService->downloadFile(static::FACE_MODEL_FILES['detector']['url']);
$this->compressionService->bunzip2($detectorModelBz2, $this->modelService->getFileModelPath($this->getId(), static::FACE_MODEL_FILES['detector']['filename']));
$this->compressionService->decompress($detectorModelBz2, $this->modelService->getFileModelPath($this->getId(), static::FACE_MODEL_FILES['detector']['filename']));

$predictorModelBz2 = $this->downloadService->downloadFile(static::FACE_MODEL_FILES['predictor']['url']);
$this->compressionService->bunzip2($predictorModelBz2, $this->modelService->getFileModelPath($this->getId(), static::FACE_MODEL_FILES['predictor']['filename']));
$this->compressionService->decompress($predictorModelBz2, $this->modelService->getFileModelPath($this->getId(), static::FACE_MODEL_FILES['predictor']['filename']));

$resnetModelBz2 = $this->downloadService->downloadFile(static::FACE_MODEL_FILES['resnet']['url']);
$this->compressionService->bunzip2($resnetModelBz2, $this->modelService->getFileModelPath($this->getId(), static::FACE_MODEL_FILES['resnet']['filename']));
$this->compressionService->decompress($resnetModelBz2, $this->modelService->getFileModelPath($this->getId(), static::FACE_MODEL_FILES['resnet']['filename']));

/* Clean temporary files */
$this->downloadService->clean();
Expand Down
4 changes: 2 additions & 2 deletions lib/Model/DlibHogModel/DlibHogModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -166,10 +166,10 @@ public function install() {

/* Download and install models */
$predictorModelBz2 = $this->downloadService->downloadFile(static::FACE_MODEL_BZ2_URLS[self::I_MODEL_PREDICTOR]);
$this->compressionService->bunzip2($predictorModelBz2, $this->modelService->getFileModelPath($this->getId(), static::FACE_MODEL_FILES[self::I_MODEL_PREDICTOR]));
$this->compressionService->decompress($predictorModelBz2, $this->modelService->getFileModelPath($this->getId(), static::FACE_MODEL_FILES[self::I_MODEL_PREDICTOR]));

$resnetModelBz2 = $this->downloadService->downloadFile(static::FACE_MODEL_BZ2_URLS[self::I_MODEL_RESNET]);
$this->compressionService->bunzip2($resnetModelBz2, $this->modelService->getFileModelPath($this->getId(), static::FACE_MODEL_FILES[self::I_MODEL_RESNET]));
$this->compressionService->decompress($resnetModelBz2, $this->modelService->getFileModelPath($this->getId(), static::FACE_MODEL_FILES[self::I_MODEL_RESNET]));

/* Clean temporary files */
$this->downloadService->clean();
Expand Down
110 changes: 110 additions & 0 deletions lib/Model/DlibTaguchiHogModel/DlibTaguchiHogModel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?php
/**
* @copyright Copyright (c) 2020-2024, Matias De lellis <[email protected]>
*
* @author Matias De lellis <[email protected]>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace OCA\FaceRecognition\Model\DlibTaguchiHogModel;

use OCA\FaceRecognition\Helper\FaceRect;

use OCA\FaceRecognition\Model\DlibCnnModel\DlibCnnModel;
use OCA\FaceRecognition\Model\IModel;

class DlibTaguchiHogModel extends DlibCnnModel implements IModel {

/*
* Model files.
*/
const FACE_MODEL_ID = 6;
const FACE_MODEL_NAME = "DlibTaguchiHog";
const FACE_MODEL_DESC = "Extends the Taguchi model, doing a face validation with the Hog detector";
const FACE_MODEL_DOC = "https://github.com/matiasdelellis/facerecognition/wiki/Models#model-6";

/** Relationship between image size and memory consumed */
const MEMORY_AREA_RELATIONSHIP = 1 * 1024;
const MINIMUM_MEMORY_REQUIREMENTS = 1 * 1024 * 1024 * 1024;

/*
* Model files.
*/
const FACE_MODEL_FILES = [
'detector' => [
'url' => 'https://github.com/davisking/dlib-models/raw/94cdb1e40b1c29c0bfcaf7355614bfe6da19460e/mmod_human_face_detector.dat.bz2',
'filename' => 'mmod_human_face_detector.dat'
],
'predictor' => [
'url' => 'https://github.com/davisking/dlib-models/raw/4af9b776281dd7d6e2e30d4a2d40458b1e254e40/shape_predictor_5_face_landmarks.dat.bz2',
'filename' => 'shape_predictor_5_face_landmarks.dat',
],
'resnet' => [
'url' => 'https://github.com/TaguchiModels/dlibModels/raw/main/taguchi_face_recognition_resnet_model_v1.7z',
'filename' => 'taguchi_face_recognition_resnet_model_v1.dat'
]
];

public function detectFaces(string $imagePath, bool $compute = true): array {
$detectedFaces = [];

$cnnFaces = parent::detectFaces($imagePath);
if (count($cnnFaces) === 0) {
return $detectedFaces;
}

$hogFaces = dlib_face_detection($imagePath);

foreach ($cnnFaces as $proposedFace) {
$detectedFaces[] = $this->validateFace($proposedFace, $hogFaces);
}

return $detectedFaces;
}

private function validateFace($proposedFace, array $validateFaces) {
foreach ($validateFaces as $validateFace) {
$overlapPercent = FaceRect::overlapPercent($proposedFace, $validateFace);
/**
* The weak link in our default model is the landmark detector that
* can't align profile or rotate faces correctly.
*
* The Hog detector also fails and cannot detect these faces. So, we
* consider if Hog detector can detect it, to infer when the predictor
* will give good results.
*
* If Hog detects it (Overlap > 35%), we can assume that landmark
* detector will do it too. In this case, we consider the face valid,
* and just return it.
*/
if ($overlapPercent >= 0.35) {
return $proposedFace;
}
}

/**
* If Hog don't detect this face, they are probably in profile or rotated.
* These are bad to compare, so we lower the confidence, to avoid clustering.
*/
$confidence = $proposedFace['detection_confidence'];
$proposedFace['detection_confidence'] = $confidence * 0.6;

return $proposedFace;
}

}
15 changes: 13 additions & 2 deletions lib/Model/ModelManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
use OCA\FaceRecognition\Model\DlibHogModel\DlibHogModel;

use OCA\FaceRecognition\Model\DlibCnnHogModel\DlibCnnHogModel;
use OCA\FaceRecognition\Model\DlibTaguchiHogModel\DlibTaguchiHogModel;

use OCA\FaceRecognition\Model\ExternalModel\ExternalModel;

Expand Down Expand Up @@ -65,6 +66,9 @@ class ModelManager {
/** @var ExternalModel */
private $externalModel;

/** @var DlibTaguchiHogModel */
private $dlibTaguchiHogModel;

/**
* @patam IUserManager $userManager
* @param SettingsService $settingsService
Expand All @@ -73,14 +77,16 @@ class ModelManager {
* @param DlibHogModel $dlibHogModel
* @param DlibCnnHogModel $dlibCnnHogModel
* @param ExternalModel $externalModel
* @param DlibTaguchiHogModel $dlibTaguchiHogModel
*/
public function __construct(IUserManager $userManager,
SettingsService $settingsService,
DlibCnn5Model $dlibCnn5Model,
DlibCnn68Model $dlibCnn68Model,
DlibHogModel $dlibHogModel,
DlibCnnHogModel $dlibCnnHogModel,
ExternalModel $externalModel)
ExternalModel $externalModel,
DlibTaguchiHogModel $dlibTaguchiHogModel)
{
$this->userManager = $userManager;
$this->settingsService = $settingsService;
Expand All @@ -90,6 +96,7 @@ public function __construct(IUserManager $userManager,
$this->dlibHogModel = $dlibHogModel;
$this->dlibCnnHogModel = $dlibCnnHogModel;
$this->externalModel = $externalModel;
$this->dlibTaguchiHogModel = $dlibTaguchiHogModel;
}

/**
Expand All @@ -113,6 +120,9 @@ public function getModel(int $version): ?IModel {
case ExternalModel::FACE_MODEL_ID:
$model = $this->externalModel;
break;
case DlibTaguchiHogModel::FACE_MODEL_ID:
$model = $this->dlibTaguchiHogModel;
break;
default:
$model = null;
break;
Expand All @@ -137,7 +147,8 @@ public function getAllModels(): array {
$this->dlibCnn68Model,
$this->dlibHogModel,
$this->dlibCnnHogModel,
$this->externalModel
$this->externalModel,
$this->dlibTaguchiHogModel
];
}

Expand Down
29 changes: 27 additions & 2 deletions lib/Service/CompressionService.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
class CompressionService {

/**
* Uncompressing the file with the bzip2-extension
* Decompress according to file extension
*
* @param string $inputFile
* @param string $outputFile
Expand All @@ -37,14 +37,29 @@ class CompressionService {
*
* @return void
*/
public function bunzip2(string $inputFile, string $outputFile): void {
public function decompress(string $inputFile, string $outputFile): void {
if (!file_exists ($inputFile) || !is_readable ($inputFile))
throw new \Exception('The file ' . $inputFile . ' not exists or is not readable');

if ((!file_exists($outputFile) && !is_writeable(dirname($outputFile))) ||
(file_exists($outputFile) && !is_writable($outputFile)))
throw new \Exception('The file ' . $outputFile . ' exists or is not writable');

$extension = pathinfo($inputFile, PATHINFO_EXTENSION);
switch ($extension) {
case 'bz2':
$this->bunzip2($inputFile, $outputFile);
break;
case '7z':
$this->un7z($inputFile, $outputFile);
break;
default:
throw new \Exception('Unsupported file format: ' . $extension);
break;
}
}

private function bunzip2(string $inputFile, string $outputFile): void {
$in_file = bzopen ($inputFile, "r");
$out_file = fopen ($outputFile, "w");

Expand All @@ -64,4 +79,14 @@ public function bunzip2(string $inputFile, string $outputFile): void {
fclose ($out_file);
}

private function un7z(string $inputFile, string $outputFile): void {
$cmd = '7z x ' . $inputFile . ' -o/tmp/';
$output = null;
$retval = null;
exec($cmd, $output, $retval);
if ($retval != 0)
throw new \Exception('Fail to extract ' . $inputFile . ': ' . $output);
rename('/tmp/' . basename($outputFile), $outputFile);
}

}

0 comments on commit 2beb9bd

Please sign in to comment.