mirror of
https://github.com/immich-app/immich
synced 2026-08-15 13:03:57 +00:00
fix(server): resolve face detection failure on manually rotated assets
This commit is contained in:
parent
95e57a24cb
commit
185072d5ea
2 changed files with 47 additions and 15 deletions
|
|
@ -814,6 +814,10 @@ describe(PersonService.name, () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('handleDetectFaces', () => {
|
describe('handleDetectFaces', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mocks.assetEdit.getAll.mockResolvedValue([]);
|
||||||
|
});
|
||||||
|
|
||||||
it('should skip if machine learning is disabled', async () => {
|
it('should skip if machine learning is disabled', async () => {
|
||||||
mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.machineLearningDisabled);
|
mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.machineLearningDisabled);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ import { AssetFaceTable } from 'src/schema/tables/asset-face.table';
|
||||||
import { FaceSearchTable } from 'src/schema/tables/face-search.table';
|
import { FaceSearchTable } from 'src/schema/tables/face-search.table';
|
||||||
import { BaseService } from 'src/services/base.service';
|
import { BaseService } from 'src/services/base.service';
|
||||||
import { JobItem, JobOf } from 'src/types';
|
import { JobItem, JobOf } from 'src/types';
|
||||||
import { getDimensions } from 'src/utils/asset.util';
|
import { getAssetFiles, getDimensions } from 'src/utils/asset.util';
|
||||||
import { ImmichFileResponse } from 'src/utils/file';
|
import { ImmichFileResponse } from 'src/utils/file';
|
||||||
import { mimeTypes } from 'src/utils/mime-types';
|
import { mimeTypes } from 'src/utils/mime-types';
|
||||||
import { isFacialRecognitionEnabled } from 'src/utils/misc';
|
import { isFacialRecognitionEnabled } from 'src/utils/misc';
|
||||||
|
|
@ -307,8 +307,13 @@ export class PersonService extends BaseService {
|
||||||
}
|
}
|
||||||
|
|
||||||
const asset = await this.assetJobRepository.getForDetectFacesJob(id);
|
const asset = await this.assetJobRepository.getForDetectFacesJob(id);
|
||||||
const previewFile = asset?.files[0];
|
if (!asset) {
|
||||||
if (!asset || asset.files.length !== 1 || !previewFile) {
|
return JobStatus.Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = getAssetFiles(asset.files as any);
|
||||||
|
const previewFile = files.editedPreviewFile || files.previewFile;
|
||||||
|
if (!previewFile) {
|
||||||
return JobStatus.Failed;
|
return JobStatus.Failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -332,14 +337,37 @@ export class PersonService extends BaseService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const heightScale = imageHeight / (asset.faces[0]?.imageHeight || 1);
|
const edits = (await this.assetEditRepository.getAll(id)) || [];
|
||||||
const widthScale = imageWidth / (asset.faces[0]?.imageWidth || 1);
|
let uneditedDimensions = { width: imageWidth, height: imageHeight };
|
||||||
|
if (previewFile.isEdited && files.previewFile) {
|
||||||
|
uneditedDimensions = await this.mediaRepository.getImageMetadata(files.previewFile.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
const heightScale = uneditedDimensions.height / (asset.faces[0]?.imageHeight || 1);
|
||||||
|
const widthScale = uneditedDimensions.width / (asset.faces[0]?.imageWidth || 1);
|
||||||
for (const { boundingBox, embedding } of faces) {
|
for (const { boundingBox, embedding } of faces) {
|
||||||
|
let originalBox = boundingBox;
|
||||||
|
if (previewFile.isEdited && edits.length > 0) {
|
||||||
|
const points = [
|
||||||
|
{ x: boundingBox.x1, y: boundingBox.y1 },
|
||||||
|
{ x: boundingBox.x2, y: boundingBox.y2 },
|
||||||
|
];
|
||||||
|
const {
|
||||||
|
points: [p1, p2],
|
||||||
|
} = transformPoints(points, edits as any, { width: imageWidth, height: imageHeight }, { inverse: true });
|
||||||
|
originalBox = {
|
||||||
|
x1: Math.trunc(Math.min(p1.x, p2.x)),
|
||||||
|
y1: Math.trunc(Math.min(p1.y, p2.y)),
|
||||||
|
x2: Math.trunc(Math.max(p1.x, p2.x)),
|
||||||
|
y2: Math.trunc(Math.max(p1.y, p2.y)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const scaledBox = {
|
const scaledBox = {
|
||||||
x1: boundingBox.x1 * widthScale,
|
x1: originalBox.x1 * widthScale,
|
||||||
y1: boundingBox.y1 * heightScale,
|
y1: originalBox.y1 * heightScale,
|
||||||
x2: boundingBox.x2 * widthScale,
|
x2: originalBox.x2 * widthScale,
|
||||||
y2: boundingBox.y2 * heightScale,
|
y2: originalBox.y2 * heightScale,
|
||||||
};
|
};
|
||||||
const match = asset.faces.find((face) => this.iou(face, scaledBox) > 0.5);
|
const match = asset.faces.find((face) => this.iou(face, scaledBox) > 0.5);
|
||||||
|
|
||||||
|
|
@ -350,12 +378,12 @@ export class PersonService extends BaseService {
|
||||||
facesToAdd.push({
|
facesToAdd.push({
|
||||||
id: faceId,
|
id: faceId,
|
||||||
assetId: asset.id,
|
assetId: asset.id,
|
||||||
imageHeight,
|
imageHeight: uneditedDimensions.height,
|
||||||
imageWidth,
|
imageWidth: uneditedDimensions.width,
|
||||||
boundingBoxX1: boundingBox.x1,
|
boundingBoxX1: originalBox.x1,
|
||||||
boundingBoxY1: boundingBox.y1,
|
boundingBoxY1: originalBox.y1,
|
||||||
boundingBoxX2: boundingBox.x2,
|
boundingBoxX2: originalBox.x2,
|
||||||
boundingBoxY2: boundingBox.y2,
|
boundingBoxY2: originalBox.y2,
|
||||||
});
|
});
|
||||||
embeddings.push({ faceId, embedding });
|
embeddings.push({ faceId, embedding });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue