const hasard = require('hasard');
const AbstractAugmenter = require('./abstract');
/**
* Applies a random four-point perspective transform to the image (kinda like an advanced form of cropping).
* Each point has a random distance from the image corner, derived from a normal distribution with sigma `sigma`.
*
* Warning : this is only working with opencv4nodejs backend
*
* @param {Object} sigma options
* @param {NumberArgument} sigma.sigma the sigma of the normal distribution
* @param {BooleanArgument} sigma.keepSize If `keepSize` is set to True (default), each image will be resized back to its original size.
* @param {ColorArgument} [sigma.borderValue=[0,0,0]] if borderType is "constant" this is used as border pixel values
* @param {BorderTypeArgument} [sigma.borderType="constant"] can be "constant", "replicate", "transparent"
* @param {Array.<Array.<Number>>} [sigma.cornersVariation=null] if set, sigma is not used. For more deterministic behavior, use this to set manually the percent (x,y) variation of each corner
* @example
// Simple usage, perspective transform on a sigma=10% random perspective tarnsform
ia.perspectiveTransform(0.1);
* @example
// Now replicate the borders
ia.perspectiveTransform({
sigma: 0.1,
borderType: "replicate"
});
*/
class PerspectiveTransformAugmenter extends AbstractAugmenter {
constructor(opts, ia) {
let o;
if (typeof (opts) === 'number' || hasard.isHasard(opts)) {
o = {sigma: opts};
} else {
o = opts;
}
super(o, ia);
const {sigma, keepSize, borderValue = [0, 0, 0], borderType = 'constant', cornersVariation} = o;
this.sigma = this.toSize2(sigma) || [0, 0];
this.keepSize = keepSize;
this.borderValue = borderValue;
this.borderType = borderType;
this.cornersVariation = cornersVariation;
}
buildParams() {
return new hasard.Object({
cornersVariation: this.cornersVariation || new hasard.Array({
size: 4,
value: new hasard.Array([
new hasard.Number({
type: 'normal',
std: hasard.getProperty(0, this.sigma)
}),
new hasard.Number({
type: 'normal',
std: hasard.getProperty(1, this.sigma)
})
])
}),
borderValue: this.borderValue,
borderType: this.borderType
});
}
getTransformationMatrix({cornersVariation, width, height}) {
const cornersSrc = [[0, 0], [1, 0], [0, 1], [1, 1]];
const srcPoints = cornersSrc.map(c => this.backend.point(c[0] * width, c[1] * height));
const destPoints = cornersSrc.map((c, index) => this.backend.point((c[0] + cornersVariation[index][0]) * width, (c[1] + cornersVariation[index][1]) * height));
return this.backend.getPerspectiveTransform(srcPoints, destPoints);
}
augmentImage({image, width, height}, {cornersVariation, borderValue, borderType}) {
const transformationMatrix = this.getTransformationMatrix({width, height, cornersVariation});
const res = this.backend.perspective(image, {transformationMatrix, size: [width, height], borderType, borderValue});
this.backend.dispose(transformationMatrix);
return res;
}
augmentPoints({points, width, height}, {cornersVariation}) {
const transformationMatrix = this.getTransformationMatrix({width, height, cornersVariation});
const transformPoint = point => {
const res = this.backend.matMul(transformationMatrix, this.backend.point3(point.x, point.y, 1), transformationMatrix.type);
const p2 = this.backend.pointFromMat(res);
const res2 = this.backend.pointToArray(p2);
this.backend.dispose(res);
return [res2[0] / res2[2], res2[1] / res2[2]];
};
const res = points.map(transformPoint);
this.backend.dispose(transformationMatrix);
return res;
}
}
module.exports = PerspectiveTransformAugmenter;