1
0
mirror of https://github.com/immich-app/immich.git synced 2024-12-25 10:43:13 +02:00

feat(web): add setting for minimum face count for face detection (#4128)

* feat: add setting for minimum face count for face detection

Adds the minimum face count setting to the web interface to
circumvent detection of strangers and random background people
if desired.

* fix: codestyle, remove max for face count
This commit is contained in:
GenericGuy 2023-09-18 06:05:35 +02:00 committed by GitHub
parent 40b802a5a9
commit 94cbbf3c4b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 53 additions and 4 deletions

View File

@ -2152,6 +2152,12 @@ export interface RecognitionConfig {
* @memberof RecognitionConfig * @memberof RecognitionConfig
*/ */
'maxDistance': number; 'maxDistance': number;
/**
*
* @type {number}
* @memberof RecognitionConfig
*/
'minFaces': number;
/** /**
* *
* @type {number} * @type {number}

View File

@ -70,7 +70,8 @@ The default configuration looks like this:
"enabled": true, "enabled": true,
"modelName": "buffalo_l", "modelName": "buffalo_l",
"minScore": 0.7, "minScore": 0.7,
"maxDistance": 0.6 "maxDistance": 0.6,
"minFaces": 1
} }
}, },
"oauth": { "oauth": {

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -6471,6 +6471,9 @@
"maxDistance": { "maxDistance": {
"type": "integer" "type": "integer"
}, },
"minFaces": {
"type": "integer"
},
"minScore": { "minScore": {
"type": "integer" "type": "integer"
}, },
@ -6484,6 +6487,7 @@
"required": [ "required": [
"minScore", "minScore",
"maxDistance", "maxDistance",
"minFaces",
"enabled", "enabled",
"modelName" "modelName"
], ],

View File

@ -205,6 +205,7 @@ describe(FacialRecognitionService.name, () => {
enabled: true, enabled: true,
maxDistance: 0.6, maxDistance: 0.6,
minScore: 0.7, minScore: 0.7,
minFaces: 1,
modelName: 'buffalo_l', modelName: 'buffalo_l',
}, },
); );

View File

@ -6,11 +6,13 @@ import {
newJobRepositoryMock, newJobRepositoryMock,
newPersonRepositoryMock, newPersonRepositoryMock,
newStorageRepositoryMock, newStorageRepositoryMock,
newSystemConfigRepositoryMock,
personStub, personStub,
} from '@test'; } from '@test';
import { BulkIdErrorReason } from '../asset'; import { BulkIdErrorReason } from '../asset';
import { IJobRepository, JobName } from '../job'; import { IJobRepository, JobName } from '../job';
import { IStorageRepository } from '../storage'; import { IStorageRepository } from '../storage';
import { ISystemConfigRepository } from '../system-config';
import { PersonResponseDto } from './person.dto'; import { PersonResponseDto } from './person.dto';
import { IPersonRepository } from './person.repository'; import { IPersonRepository } from './person.repository';
import { PersonService } from './person.service'; import { PersonService } from './person.service';
@ -26,14 +28,16 @@ const responseDto: PersonResponseDto = {
describe(PersonService.name, () => { describe(PersonService.name, () => {
let sut: PersonService; let sut: PersonService;
let personMock: jest.Mocked<IPersonRepository>; let personMock: jest.Mocked<IPersonRepository>;
let configMock: jest.Mocked<ISystemConfigRepository>;
let storageMock: jest.Mocked<IStorageRepository>; let storageMock: jest.Mocked<IStorageRepository>;
let jobMock: jest.Mocked<IJobRepository>; let jobMock: jest.Mocked<IJobRepository>;
beforeEach(async () => { beforeEach(async () => {
personMock = newPersonRepositoryMock(); personMock = newPersonRepositoryMock();
storageMock = newStorageRepositoryMock(); storageMock = newStorageRepositoryMock();
configMock = newSystemConfigRepositoryMock();
jobMock = newJobRepositoryMock(); jobMock = newJobRepositoryMock();
sut = new PersonService(personMock, storageMock, jobMock); sut = new PersonService(personMock, configMock, storageMock, jobMock);
}); });
it('should be defined', () => { it('should be defined', () => {

View File

@ -4,6 +4,7 @@ import { AuthUserDto } from '../auth';
import { mimeTypes } from '../domain.constant'; import { mimeTypes } from '../domain.constant';
import { IJobRepository, JobName } from '../job'; import { IJobRepository, JobName } from '../job';
import { IStorageRepository, ImmichReadStream } from '../storage'; import { IStorageRepository, ImmichReadStream } from '../storage';
import { ISystemConfigRepository, SystemConfigCore } from '../system-config';
import { import {
MergePersonDto, MergePersonDto,
PeopleResponseDto, PeopleResponseDto,
@ -17,17 +18,22 @@ import { IPersonRepository, UpdateFacesData } from './person.repository';
@Injectable() @Injectable()
export class PersonService { export class PersonService {
private configCore: SystemConfigCore;
readonly logger = new Logger(PersonService.name); readonly logger = new Logger(PersonService.name);
constructor( constructor(
@Inject(IPersonRepository) private repository: IPersonRepository, @Inject(IPersonRepository) private repository: IPersonRepository,
@Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository,
@Inject(IStorageRepository) private storageRepository: IStorageRepository, @Inject(IStorageRepository) private storageRepository: IStorageRepository,
@Inject(IJobRepository) private jobRepository: IJobRepository, @Inject(IJobRepository) private jobRepository: IJobRepository,
) {} ) {
this.configCore = new SystemConfigCore(configRepository);
}
async getAll(authUser: AuthUserDto, dto: PersonSearchDto): Promise<PeopleResponseDto> { async getAll(authUser: AuthUserDto, dto: PersonSearchDto): Promise<PeopleResponseDto> {
const { machineLearning } = await this.configCore.getConfig();
const people = await this.repository.getAllForUser(authUser.id, { const people = await this.repository.getAllForUser(authUser.id, {
minimumFaceCount: 1, minimumFaceCount: machineLearning.facialRecognition.minFaces,
withHidden: dto.withHidden || false, withHidden: dto.withHidden || false,
}); });
const persons: PersonResponseDto[] = people const persons: PersonResponseDto[] = people

View File

@ -48,4 +48,10 @@ export class RecognitionConfig extends ModelConfig {
@Type(() => Number) @Type(() => Number)
@ApiProperty({ type: 'integer' }) @ApiProperty({ type: 'integer' })
maxDistance!: number; maxDistance!: number;
@IsNumber()
@Min(1)
@Type(() => Number)
@ApiProperty({ type: 'integer' })
minFaces!: number;
} }

View File

@ -72,6 +72,7 @@ export const defaults = Object.freeze<SystemConfig>({
modelName: 'buffalo_l', modelName: 'buffalo_l',
minScore: 0.7, minScore: 0.7,
maxDistance: 0.6, maxDistance: 0.6,
minFaces: 1,
}, },
}, },
map: { map: {

View File

@ -71,6 +71,7 @@ const updatedConfig = Object.freeze<SystemConfig>({
modelName: 'buffalo_l', modelName: 'buffalo_l',
minScore: 0.7, minScore: 0.7,
maxDistance: 0.6, maxDistance: 0.6,
minFaces: 1,
}, },
}, },
map: { map: {

View File

@ -57,6 +57,7 @@ export enum SystemConfigKey {
MACHINE_LEARNING_FACIAL_RECOGNITION_MODEL_NAME = 'machineLearning.facialRecognition.modelName', MACHINE_LEARNING_FACIAL_RECOGNITION_MODEL_NAME = 'machineLearning.facialRecognition.modelName',
MACHINE_LEARNING_FACIAL_RECOGNITION_MIN_SCORE = 'machineLearning.facialRecognition.minScore', MACHINE_LEARNING_FACIAL_RECOGNITION_MIN_SCORE = 'machineLearning.facialRecognition.minScore',
MACHINE_LEARNING_FACIAL_RECOGNITION_MAX_DISTANCE = 'machineLearning.facialRecognition.maxDistance', MACHINE_LEARNING_FACIAL_RECOGNITION_MAX_DISTANCE = 'machineLearning.facialRecognition.maxDistance',
MACHINE_LEARNING_FACIAL_RECOGNITION_MIN_FACES = 'machineLearning.facialRecognition.minFaces',
MAP_ENABLED = 'map.enabled', MAP_ENABLED = 'map.enabled',
MAP_TILE_URL = 'map.tileUrl', MAP_TILE_URL = 'map.tileUrl',
@ -164,6 +165,7 @@ export interface SystemConfig {
enabled: boolean; enabled: boolean;
modelName: string; modelName: string;
minScore: number; minScore: number;
minFaces: number;
maxDistance: number; maxDistance: number;
}; };
}; };

View File

@ -2152,6 +2152,12 @@ export interface RecognitionConfig {
* @memberof RecognitionConfig * @memberof RecognitionConfig
*/ */
'maxDistance': number; 'maxDistance': number;
/**
*
* @type {number}
* @memberof RecognitionConfig
*/
'minFaces': number;
/** /**
* *
* @type {number} * @type {number}

View File

@ -196,6 +196,17 @@
isEdited={machineLearningConfig.facialRecognition.maxDistance !== isEdited={machineLearningConfig.facialRecognition.maxDistance !==
savedConfig.facialRecognition.maxDistance} savedConfig.facialRecognition.maxDistance}
/> />
<SettingInputField
inputType={SettingInputFieldType.NUMBER}
label="MIN FACES DETECTED"
desc="The minimum number of faces of a person that must be detected for them to appear in the People tab. Setting this to a value greater than 1 can prevent strangers or blurry faces that are not the main subject of the image from being displayed."
bind:value={machineLearningConfig.facialRecognition.minFaces}
step="1"
min="1"
disabled={disabled || !machineLearningConfig.enabled || !machineLearningConfig.facialRecognition.enabled}
isEdited={machineLearningConfig.facialRecognition.minFaces !== savedConfig.facialRecognition.minFaces}
/>
</div> </div>
</SettingAccordion> </SettingAccordion>