Support fill_value for ImageProjectiveTransform
Fix conflict Fix typo Update number
This commit is contained in:
parent
9f86089e45
commit
13a9df90f4
@ -0,0 +1,63 @@
|
||||
op {
|
||||
graph_op_name: "ImageProjectiveTransformV3"
|
||||
visibility: HIDDEN
|
||||
in_arg {
|
||||
name: "images"
|
||||
description: <<END
|
||||
4-D with shape `[batch, height, width, channels]`.
|
||||
END
|
||||
}
|
||||
in_arg {
|
||||
name: "transforms"
|
||||
description: <<END
|
||||
2-D Tensor, `[batch, 8]` or `[1, 8]` matrix, where each row corresponds to a 3 x 3
|
||||
projective transformation matrix, with the last entry assumed to be 1. If there
|
||||
is one row, the same transformation will be applied to all images.
|
||||
END
|
||||
}
|
||||
in_arg {
|
||||
name: "output_shape"
|
||||
description: <<END
|
||||
1-D Tensor [new_height, new_width].
|
||||
END
|
||||
}
|
||||
in_arg {
|
||||
name: "fill_value"
|
||||
description: <<END
|
||||
float, the value to be filled when fill_mode is constant".
|
||||
END
|
||||
}
|
||||
out_arg {
|
||||
name: "transformed_images"
|
||||
description: <<END
|
||||
4-D with shape
|
||||
`[batch, new_height, new_width, channels]`.
|
||||
END
|
||||
}
|
||||
attr {
|
||||
name: "dtype"
|
||||
description: <<END
|
||||
Input dtype.
|
||||
END
|
||||
}
|
||||
attr {
|
||||
name: "interpolation"
|
||||
description: <<END
|
||||
Interpolation method, "NEAREST" or "BILINEAR".
|
||||
END
|
||||
}
|
||||
attr {
|
||||
name: "fill_mode"
|
||||
description: <<END
|
||||
Fill mode, "REFLECT", "WRAP", or "CONSTANT".
|
||||
END
|
||||
}
|
||||
summary: "Applies the given transform to each of the images."
|
||||
description: <<END
|
||||
If one row of `transforms` is `[a0, a1, a2, b0, b1, b2, c0, c1]`, then it maps
|
||||
the *output* point `(x, y)` to a transformed *input* point
|
||||
`(x', y') = ((a0 x + a1 y + a2) / k, (b0 x + b1 y + b2) / k)`, where
|
||||
`k = c0 x + c1 y + 1`. If the transformed point lays outside of the input
|
||||
image, the output pixel is set to fill_value.
|
||||
END
|
||||
}
|
@ -48,6 +48,68 @@ using functor::FillProjectiveTransform;
|
||||
using generator::Interpolation;
|
||||
using generator::Mode;
|
||||
|
||||
template <typename Device, typename T>
|
||||
void DoImageProjectiveTransformOp(OpKernelContext* ctx,
|
||||
const Interpolation& interpolation,
|
||||
const Mode& fill_mode) {
|
||||
const Tensor& images_t = ctx->input(0);
|
||||
const Tensor& transform_t = ctx->input(1);
|
||||
OP_REQUIRES(ctx, images_t.shape().dims() == 4,
|
||||
errors::InvalidArgument("Input images must have rank 4"));
|
||||
OP_REQUIRES(ctx,
|
||||
(TensorShapeUtils::IsMatrix(transform_t.shape()) &&
|
||||
(transform_t.dim_size(0) == images_t.dim_size(0) ||
|
||||
transform_t.dim_size(0) == 1) &&
|
||||
transform_t.dim_size(1) == 8),
|
||||
errors::InvalidArgument(
|
||||
"Input transform should be num_images x 8 or 1 x 8"));
|
||||
|
||||
int32 out_height, out_width;
|
||||
// Kernel is shared by legacy "ImageProjectiveTransform" op with 2 args.
|
||||
if (ctx->num_inputs() >= 3) {
|
||||
const Tensor& shape_t = ctx->input(2);
|
||||
OP_REQUIRES(ctx, shape_t.dims() == 1,
|
||||
errors::InvalidArgument("output shape must be 1-dimensional",
|
||||
shape_t.shape().DebugString()));
|
||||
OP_REQUIRES(ctx, shape_t.NumElements() == 2,
|
||||
errors::InvalidArgument("output shape must have two elements",
|
||||
shape_t.shape().DebugString()));
|
||||
auto shape_vec = shape_t.vec<int32>();
|
||||
out_height = shape_vec(0);
|
||||
out_width = shape_vec(1);
|
||||
OP_REQUIRES(ctx, out_height > 0 && out_width > 0,
|
||||
errors::InvalidArgument("output dimensions must be positive"));
|
||||
} else {
|
||||
// Shape is N (batch size), H (height), W (width), C (channels).
|
||||
out_height = images_t.shape().dim_size(1);
|
||||
out_width = images_t.shape().dim_size(2);
|
||||
}
|
||||
|
||||
T fill_value(0);
|
||||
// Kernel is shared by "ImageProjectiveTransformV2" with 3 args.
|
||||
if (ctx->num_inputs() >= 4) {
|
||||
const Tensor& fill_value_t = ctx->input(3);
|
||||
OP_REQUIRES(ctx, TensorShapeUtils::IsScalar(fill_value_t.shape()),
|
||||
errors::InvalidArgument("fill_value must be a scalar",
|
||||
fill_value_t.shape().DebugString()));
|
||||
fill_value = static_cast<T>(*(fill_value_t.scalar<float>().data()));
|
||||
}
|
||||
|
||||
Tensor* output_t;
|
||||
OP_REQUIRES_OK(
|
||||
ctx, ctx->allocate_output(0,
|
||||
TensorShape({images_t.dim_size(0), out_height,
|
||||
out_width, images_t.dim_size(3)}),
|
||||
&output_t));
|
||||
auto output = output_t->tensor<T, 4>();
|
||||
auto images = images_t.tensor<T, 4>();
|
||||
auto transform = transform_t.matrix<float>();
|
||||
|
||||
(FillProjectiveTransform<Device, T>(interpolation))(
|
||||
ctx->eigen_device<Device>(), &output, images, transform, fill_mode,
|
||||
fill_value);
|
||||
}
|
||||
|
||||
template <typename Device, typename T>
|
||||
class ImageProjectiveTransformV2 : public OpKernel {
|
||||
private:
|
||||
@ -84,52 +146,7 @@ class ImageProjectiveTransformV2 : public OpKernel {
|
||||
}
|
||||
|
||||
void Compute(OpKernelContext* ctx) override {
|
||||
const Tensor& images_t = ctx->input(0);
|
||||
const Tensor& transform_t = ctx->input(1);
|
||||
OP_REQUIRES(ctx, images_t.shape().dims() == 4,
|
||||
errors::InvalidArgument("Input images must have rank 4"));
|
||||
OP_REQUIRES(ctx,
|
||||
(TensorShapeUtils::IsMatrix(transform_t.shape()) &&
|
||||
(transform_t.dim_size(0) == images_t.dim_size(0) ||
|
||||
transform_t.dim_size(0) == 1) &&
|
||||
transform_t.dim_size(1) == 8),
|
||||
errors::InvalidArgument(
|
||||
"Input transform should be num_images x 8 or 1 x 8"));
|
||||
|
||||
int32 out_height, out_width;
|
||||
// Kernel is shared by legacy "ImageProjectiveTransform" op with 2 args.
|
||||
if (ctx->num_inputs() >= 3) {
|
||||
const Tensor& shape_t = ctx->input(2);
|
||||
OP_REQUIRES(ctx, shape_t.dims() == 1,
|
||||
errors::InvalidArgument("output shape must be 1-dimensional",
|
||||
shape_t.shape().DebugString()));
|
||||
OP_REQUIRES(ctx, shape_t.NumElements() == 2,
|
||||
errors::InvalidArgument("output shape must have two elements",
|
||||
shape_t.shape().DebugString()));
|
||||
auto shape_vec = shape_t.vec<int32>();
|
||||
out_height = shape_vec(0);
|
||||
out_width = shape_vec(1);
|
||||
OP_REQUIRES(
|
||||
ctx, out_height > 0 && out_width > 0,
|
||||
errors::InvalidArgument("output dimensions must be positive"));
|
||||
} else {
|
||||
// Shape is N (batch size), H (height), W (width), C (channels).
|
||||
out_height = images_t.shape().dim_size(1);
|
||||
out_width = images_t.shape().dim_size(2);
|
||||
}
|
||||
|
||||
Tensor* output_t;
|
||||
OP_REQUIRES_OK(ctx, ctx->allocate_output(
|
||||
0,
|
||||
TensorShape({images_t.dim_size(0), out_height,
|
||||
out_width, images_t.dim_size(3)}),
|
||||
&output_t));
|
||||
auto output = output_t->tensor<T, 4>();
|
||||
auto images = images_t.tensor<T, 4>();
|
||||
auto transform = transform_t.matrix<float>();
|
||||
|
||||
(FillProjectiveTransform<Device, T>(interpolation_))(
|
||||
ctx->eigen_device<Device>(), &output, images, transform, fill_mode_);
|
||||
DoImageProjectiveTransformOp<Device, T>(ctx, interpolation_, fill_mode_);
|
||||
}
|
||||
};
|
||||
|
||||
@ -148,6 +165,29 @@ TF_CALL_double(REGISTER);
|
||||
|
||||
#undef REGISTER
|
||||
|
||||
template <typename Device, typename T>
|
||||
class ImageProjectiveTransformV3
|
||||
: public ImageProjectiveTransformV2<Device, T> {
|
||||
public:
|
||||
explicit ImageProjectiveTransformV3(OpKernelConstruction* ctx)
|
||||
: ImageProjectiveTransformV2<Device, T>(ctx) {}
|
||||
};
|
||||
|
||||
#define REGISTER(TYPE) \
|
||||
REGISTER_KERNEL_BUILDER(Name("ImageProjectiveTransformV3") \
|
||||
.Device(DEVICE_CPU) \
|
||||
.TypeConstraint<TYPE>("dtype"), \
|
||||
ImageProjectiveTransformV3<CPUDevice, TYPE>)
|
||||
|
||||
TF_CALL_uint8(REGISTER);
|
||||
TF_CALL_int32(REGISTER);
|
||||
TF_CALL_int64(REGISTER);
|
||||
TF_CALL_half(REGISTER);
|
||||
TF_CALL_float(REGISTER);
|
||||
TF_CALL_double(REGISTER);
|
||||
|
||||
#undef REGISTER
|
||||
|
||||
#if GOOGLE_CUDA
|
||||
|
||||
typedef Eigen::GpuDevice GPUDevice;
|
||||
@ -161,7 +201,8 @@ namespace functor {
|
||||
template <> \
|
||||
void FillProjectiveTransform<GPUDevice, TYPE>::operator()( \
|
||||
const GPUDevice& device, OutputType* output, const InputType& images, \
|
||||
const TransformsType& transform, const Mode fill_mode) const; \
|
||||
const TransformsType& transform, const Mode fill_mode, \
|
||||
const TYPE fill_value) const; \
|
||||
extern template struct FillProjectiveTransform<GPUDevice, TYPE>
|
||||
|
||||
TF_CALL_uint8(DECLARE_PROJECT_FUNCTOR);
|
||||
@ -204,6 +245,23 @@ TF_CALL_double(REGISTER);
|
||||
|
||||
#undef REGISTER
|
||||
|
||||
#define REGISTER(TYPE) \
|
||||
REGISTER_KERNEL_BUILDER(Name("ImageProjectiveTransformV3") \
|
||||
.Device(DEVICE_GPU) \
|
||||
.TypeConstraint<TYPE>("dtype") \
|
||||
.HostMemory("output_shape") \
|
||||
.HostMemory("fill_value"), \
|
||||
ImageProjectiveTransformV3<GPUDevice, TYPE>)
|
||||
|
||||
TF_CALL_uint8(REGISTER);
|
||||
TF_CALL_int32(REGISTER);
|
||||
TF_CALL_int64(REGISTER);
|
||||
TF_CALL_half(REGISTER);
|
||||
TF_CALL_float(REGISTER);
|
||||
TF_CALL_double(REGISTER);
|
||||
|
||||
#undef REGISTER
|
||||
|
||||
#endif // GOOGLE_CUDA
|
||||
|
||||
} // end namespace tensorflow
|
||||
|
@ -107,17 +107,20 @@ class ProjectiveGenerator {
|
||||
typename TTypes<T, 4>::ConstTensor input_;
|
||||
typename TTypes<float>::ConstMatrix transforms_;
|
||||
const Interpolation interpolation_;
|
||||
const T fill_value_;
|
||||
|
||||
public:
|
||||
EIGEN_DEVICE_FUNC EIGEN_ALWAYS_INLINE
|
||||
ProjectiveGenerator(typename TTypes<T, 4>::ConstTensor input,
|
||||
typename TTypes<float>::ConstMatrix transforms,
|
||||
const Interpolation interpolation)
|
||||
: input_(input), transforms_(transforms), interpolation_(interpolation) {}
|
||||
const Interpolation interpolation, const T fill_value)
|
||||
: input_(input),
|
||||
transforms_(transforms),
|
||||
interpolation_(interpolation),
|
||||
fill_value_(fill_value) {}
|
||||
|
||||
EIGEN_DEVICE_FUNC EIGEN_ALWAYS_INLINE T
|
||||
operator()(const array<DenseIndex, 4>& coords) const {
|
||||
const T fill_value = T(0);
|
||||
const int64 output_y = coords[1];
|
||||
const int64 output_x = coords[2];
|
||||
const float* transform =
|
||||
@ -126,9 +129,9 @@ class ProjectiveGenerator {
|
||||
: &transforms_.data()[transforms_.dimension(1) * coords[0]];
|
||||
float projection = transform[6] * output_x + transform[7] * output_y + 1.f;
|
||||
if (projection == 0) {
|
||||
// Return the fill value (0) for infinite coordinates,
|
||||
// Return the fill value for infinite coordinates,
|
||||
// which are outside the input image
|
||||
return fill_value;
|
||||
return fill_value_;
|
||||
}
|
||||
const float input_x =
|
||||
(transform[0] * output_x + transform[1] * output_y + transform[2]) /
|
||||
@ -146,13 +149,13 @@ class ProjectiveGenerator {
|
||||
const DenseIndex channels = coords[3];
|
||||
switch (interpolation_) {
|
||||
case NEAREST:
|
||||
return nearest_interpolation(batch, y, x, channels, fill_value);
|
||||
return nearest_interpolation(batch, y, x, channels, fill_value_);
|
||||
case BILINEAR:
|
||||
return bilinear_interpolation(batch, y, x, channels, fill_value);
|
||||
return bilinear_interpolation(batch, y, x, channels, fill_value_);
|
||||
}
|
||||
// Unreachable; ImageProjectiveTransform only uses INTERPOLATION_NEAREST
|
||||
// or INTERPOLATION_BILINEAR.
|
||||
return fill_value;
|
||||
return fill_value_;
|
||||
}
|
||||
|
||||
EIGEN_DEVICE_FUNC EIGEN_ALWAYS_INLINE T
|
||||
@ -225,27 +228,27 @@ struct FillProjectiveTransform {
|
||||
EIGEN_ALWAYS_INLINE
|
||||
void operator()(const Device& device, OutputType* output,
|
||||
const InputType& images, const TransformsType& transform,
|
||||
const Mode fill_mode) const {
|
||||
const Mode fill_mode, const T fill_value) const {
|
||||
switch (fill_mode) {
|
||||
case Mode::FILL_REFLECT:
|
||||
output->device(device) =
|
||||
output->generate(ProjectiveGenerator<Device, T, Mode::FILL_REFLECT>(
|
||||
images, transform, interpolation));
|
||||
images, transform, interpolation, fill_value));
|
||||
break;
|
||||
case Mode::FILL_WRAP:
|
||||
output->device(device) =
|
||||
output->generate(ProjectiveGenerator<Device, T, Mode::FILL_WRAP>(
|
||||
images, transform, interpolation));
|
||||
images, transform, interpolation, fill_value));
|
||||
break;
|
||||
case Mode::FILL_CONSTANT:
|
||||
output->device(device) = output->generate(
|
||||
ProjectiveGenerator<Device, T, Mode::FILL_CONSTANT>(
|
||||
images, transform, interpolation));
|
||||
images, transform, interpolation, fill_value));
|
||||
break;
|
||||
case Mode::FILL_NEAREST:
|
||||
output->device(device) =
|
||||
output->generate(ProjectiveGenerator<Device, T, Mode::FILL_NEAREST>(
|
||||
images, transform, interpolation));
|
||||
images, transform, interpolation, fill_value));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -1146,8 +1146,9 @@ REGISTER_OP("GenerateBoundingBoxProposals")
|
||||
return Status::OK();
|
||||
});
|
||||
|
||||
// TODO(ringwalt): Add a "fill_constant" argument for constant mode (default 0).
|
||||
// V2 op supports output_shape. V1 op is in contrib.
|
||||
// V3 op supports fill_value.
|
||||
// V2 op supports output_shape.
|
||||
// V1 op is in contrib.
|
||||
REGISTER_OP("ImageProjectiveTransformV2")
|
||||
.Input("images: dtype")
|
||||
.Input("transforms: float32")
|
||||
@ -1163,4 +1164,20 @@ REGISTER_OP("ImageProjectiveTransformV2")
|
||||
c->Dim(input, 3));
|
||||
});
|
||||
|
||||
REGISTER_OP("ImageProjectiveTransformV3")
|
||||
.Input("images: dtype")
|
||||
.Input("transforms: float32")
|
||||
.Input("output_shape: int32")
|
||||
.Input("fill_value: float32")
|
||||
.Attr("dtype: {uint8, int32, int64, float16, float32, float64}")
|
||||
.Attr("interpolation: string")
|
||||
.Attr("fill_mode: string = 'CONSTANT'")
|
||||
.Output("transformed_images: dtype")
|
||||
.SetShapeFn([](InferenceContext* c) {
|
||||
ShapeHandle input;
|
||||
TF_RETURN_IF_ERROR(c->WithRank(c->input(0), 4, &input));
|
||||
return SetOutputToSizedImage(c, c->Dim(input, 0), 2 /* size_input_idx */,
|
||||
c->Dim(input, 3));
|
||||
});
|
||||
|
||||
} // namespace tensorflow
|
||||
|
@ -19102,6 +19102,54 @@ op {
|
||||
}
|
||||
}
|
||||
}
|
||||
op {
|
||||
name: "ImageProjectiveTransformV3"
|
||||
input_arg {
|
||||
name: "images"
|
||||
type_attr: "dtype"
|
||||
}
|
||||
input_arg {
|
||||
name: "transforms"
|
||||
type: DT_FLOAT
|
||||
}
|
||||
input_arg {
|
||||
name: "output_shape"
|
||||
type: DT_INT32
|
||||
}
|
||||
input_arg {
|
||||
name: "fill_value"
|
||||
type: DT_FLOAT
|
||||
}
|
||||
output_arg {
|
||||
name: "transformed_images"
|
||||
type_attr: "dtype"
|
||||
}
|
||||
attr {
|
||||
name: "dtype"
|
||||
type: "type"
|
||||
allowed_values {
|
||||
list {
|
||||
type: DT_UINT8
|
||||
type: DT_INT32
|
||||
type: DT_INT64
|
||||
type: DT_HALF
|
||||
type: DT_FLOAT
|
||||
type: DT_DOUBLE
|
||||
}
|
||||
}
|
||||
}
|
||||
attr {
|
||||
name: "interpolation"
|
||||
type: "string"
|
||||
}
|
||||
attr {
|
||||
name: "fill_mode"
|
||||
type: "string"
|
||||
default_value {
|
||||
s: "CONSTANT"
|
||||
}
|
||||
}
|
||||
}
|
||||
op {
|
||||
name: "ImageSummary"
|
||||
input_arg {
|
||||
|
@ -50,7 +50,7 @@ auto OpGradientInfoInit(const T &a) {
|
||||
|
||||
absl::optional<tensorflow::gtl::FlatSet<int>> OpGradientUnusedInputIndices(
|
||||
const tensorflow::string &op_name) {
|
||||
static std::array<OpIndexInfo, 349> a = {{
|
||||
static std::array<OpIndexInfo, 350> a = {{
|
||||
{"Acosh"},
|
||||
{"AllToAll", 1, {0}},
|
||||
{"ApproximateEqual"},
|
||||
@ -152,6 +152,7 @@ absl::optional<tensorflow::gtl::FlatSet<int>> OpGradientUnusedInputIndices(
|
||||
{"IdentityReader"},
|
||||
{"Imag"},
|
||||
{"ImageProjectiveTransformV2", 1, {2}},
|
||||
{"ImageProjectiveTransformV3", 2, {2, 3}},
|
||||
{"ImageSummary"},
|
||||
{"InitializeTable"},
|
||||
{"InitializeTableFromTextFile"},
|
||||
@ -412,7 +413,7 @@ absl::optional<tensorflow::gtl::FlatSet<int>> OpGradientUnusedInputIndices(
|
||||
|
||||
absl::optional<tensorflow::gtl::FlatSet<int>> OpGradientUnusedOutputIndices(
|
||||
const tensorflow::string &op_name) {
|
||||
static std::array<OpIndexInfo, 465> a = {{
|
||||
static std::array<OpIndexInfo, 466> a = {{
|
||||
{"Abs"},
|
||||
{"AccumulateNV2"},
|
||||
{"Acos"},
|
||||
@ -568,6 +569,7 @@ absl::optional<tensorflow::gtl::FlatSet<int>> OpGradientUnusedOutputIndices(
|
||||
{"Igammac"},
|
||||
{"Imag"},
|
||||
{"ImageProjectiveTransformV2"},
|
||||
{"ImageProjectiveTransformV3"},
|
||||
{"ImageSummary"},
|
||||
{"InitializeTable"},
|
||||
{"InitializeTableFromTextFile"},
|
||||
|
@ -21,6 +21,7 @@ from __future__ import print_function
|
||||
import numpy as np
|
||||
|
||||
from tensorflow.python.eager import context
|
||||
from tensorflow.python.compat import compat
|
||||
from tensorflow.python.framework import dtypes
|
||||
from tensorflow.python.framework import ops
|
||||
from tensorflow.python.framework import tensor_shape
|
||||
@ -466,6 +467,8 @@ class RandomTranslation(PreprocessingLayer):
|
||||
The input is extended by wrapping around to the opposite edge.
|
||||
- *nearest*: `(a a a a | a b c d | d d d d)`
|
||||
The input is extended by the nearest pixel.
|
||||
fill_value: a float represents the value to be filled outside the
|
||||
boundaries when `fill_mode` is "constant".
|
||||
interpolation: Interpolation mode. Supported values: "nearest", "bilinear".
|
||||
seed: Integer. Used to create a random seed.
|
||||
name: A string, the name of the layer.
|
||||
@ -487,6 +490,7 @@ class RandomTranslation(PreprocessingLayer):
|
||||
height_factor,
|
||||
width_factor,
|
||||
fill_mode='reflect',
|
||||
fill_value=0.0,
|
||||
interpolation='bilinear',
|
||||
seed=None,
|
||||
name=None,
|
||||
@ -522,6 +526,7 @@ class RandomTranslation(PreprocessingLayer):
|
||||
check_fill_mode_and_interpolation(fill_mode, interpolation)
|
||||
|
||||
self.fill_mode = fill_mode
|
||||
self.fill_value = fill_value
|
||||
self.interpolation = interpolation
|
||||
self.seed = seed
|
||||
self._rng = make_generator(self.seed)
|
||||
@ -559,7 +564,8 @@ class RandomTranslation(PreprocessingLayer):
|
||||
inputs,
|
||||
get_translation_matrix(translations),
|
||||
interpolation=self.interpolation,
|
||||
fill_mode=self.fill_mode)
|
||||
fill_mode=self.fill_mode,
|
||||
fill_value=self.fill_value)
|
||||
|
||||
output = control_flow_util.smart_cond(training, random_translated_inputs,
|
||||
lambda: inputs)
|
||||
@ -574,6 +580,7 @@ class RandomTranslation(PreprocessingLayer):
|
||||
'height_factor': self.height_factor,
|
||||
'width_factor': self.width_factor,
|
||||
'fill_mode': self.fill_mode,
|
||||
'fill_value': self.fill_value,
|
||||
'interpolation': self.interpolation,
|
||||
'seed': self.seed,
|
||||
}
|
||||
@ -617,6 +624,7 @@ def get_translation_matrix(translations, name=None):
|
||||
def transform(images,
|
||||
transforms,
|
||||
fill_mode='reflect',
|
||||
fill_value=0.0,
|
||||
interpolation='bilinear',
|
||||
output_shape=None,
|
||||
name=None):
|
||||
@ -636,6 +644,8 @@ def transform(images,
|
||||
not backpropagated into transformation parameters.
|
||||
fill_mode: Points outside the boundaries of the input are filled according
|
||||
to the given mode (one of `{'constant', 'reflect', 'wrap', 'nearest'}`).
|
||||
fill_value: a float represents the value to be filled outside the
|
||||
boundaries when `fill_mode` is "constant".
|
||||
interpolation: Interpolation mode. Supported values: "nearest", "bilinear".
|
||||
output_shape: Output dimesion after the transform, [height, width]. If None,
|
||||
output is the same size as input image.
|
||||
@ -689,6 +699,18 @@ def transform(images,
|
||||
'new_height, new_width, instead got '
|
||||
'{}'.format(output_shape))
|
||||
|
||||
fill_value = ops.convert_to_tensor_v2(
|
||||
fill_value, dtypes.float32, name='fill_value')
|
||||
|
||||
if compat.forward_compatible(2020, 8, 5):
|
||||
return gen_image_ops.ImageProjectiveTransformV3(
|
||||
images=images,
|
||||
output_shape=output_shape,
|
||||
fill_value=fill_value,
|
||||
transforms=transforms,
|
||||
fill_mode=fill_mode.upper(),
|
||||
interpolation=interpolation.upper())
|
||||
|
||||
return gen_image_ops.ImageProjectiveTransformV2(
|
||||
images=images,
|
||||
output_shape=output_shape,
|
||||
@ -774,6 +796,8 @@ class RandomRotation(PreprocessingLayer):
|
||||
The input is extended by wrapping around to the opposite edge.
|
||||
- *nearest*: `(a a a a | a b c d | d d d d)`
|
||||
The input is extended by the nearest pixel.
|
||||
fill_value: a float represents the value to be filled outside the
|
||||
boundaries when `fill_mode` is "constant".
|
||||
interpolation: Interpolation mode. Supported values: "nearest", "bilinear".
|
||||
seed: Integer. Used to create a random seed.
|
||||
name: A string, the name of the layer.
|
||||
@ -793,6 +817,7 @@ class RandomRotation(PreprocessingLayer):
|
||||
def __init__(self,
|
||||
factor,
|
||||
fill_mode='reflect',
|
||||
fill_value=0.0,
|
||||
interpolation='bilinear',
|
||||
seed=None,
|
||||
name=None,
|
||||
@ -809,6 +834,7 @@ class RandomRotation(PreprocessingLayer):
|
||||
'got {}'.format(factor))
|
||||
check_fill_mode_and_interpolation(fill_mode, interpolation)
|
||||
self.fill_mode = fill_mode
|
||||
self.fill_value = fill_value
|
||||
self.interpolation = interpolation
|
||||
self.seed = seed
|
||||
self._rng = make_generator(self.seed)
|
||||
@ -834,6 +860,7 @@ class RandomRotation(PreprocessingLayer):
|
||||
inputs,
|
||||
get_rotation_matrix(angles, img_hd, img_wd),
|
||||
fill_mode=self.fill_mode,
|
||||
fill_value=self.fill_value,
|
||||
interpolation=self.interpolation)
|
||||
|
||||
output = control_flow_util.smart_cond(training, random_rotated_inputs,
|
||||
@ -848,6 +875,7 @@ class RandomRotation(PreprocessingLayer):
|
||||
config = {
|
||||
'factor': self.factor,
|
||||
'fill_mode': self.fill_mode,
|
||||
'fill_value': self.fill_value,
|
||||
'interpolation': self.interpolation,
|
||||
'seed': self.seed,
|
||||
}
|
||||
@ -889,6 +917,8 @@ class RandomZoom(PreprocessingLayer):
|
||||
The input is extended by wrapping around to the opposite edge.
|
||||
- *nearest*: `(a a a a | a b c d | d d d d)`
|
||||
The input is extended by the nearest pixel.
|
||||
fill_value: a float represents the value to be filled outside the
|
||||
boundaries when `fill_mode` is "constant".
|
||||
interpolation: Interpolation mode. Supported values: "nearest", "bilinear".
|
||||
seed: Integer. Used to create a random seed.
|
||||
name: A string, the name of the layer.
|
||||
@ -914,11 +944,11 @@ class RandomZoom(PreprocessingLayer):
|
||||
negative.
|
||||
"""
|
||||
|
||||
# TODO(b/156526279): Add `fill_value` argument.
|
||||
def __init__(self,
|
||||
height_factor,
|
||||
width_factor=None,
|
||||
fill_mode='reflect',
|
||||
fill_value=0.0,
|
||||
interpolation='bilinear',
|
||||
seed=None,
|
||||
name=None,
|
||||
@ -951,6 +981,7 @@ class RandomZoom(PreprocessingLayer):
|
||||
check_fill_mode_and_interpolation(fill_mode, interpolation)
|
||||
|
||||
self.fill_mode = fill_mode
|
||||
self.fill_value = fill_value
|
||||
self.interpolation = interpolation
|
||||
self.seed = seed
|
||||
self._rng = make_generator(self.seed)
|
||||
@ -985,6 +1016,7 @@ class RandomZoom(PreprocessingLayer):
|
||||
return transform(
|
||||
inputs, get_zoom_matrix(zooms, img_hd, img_wd),
|
||||
fill_mode=self.fill_mode,
|
||||
fill_value=self.fill_value,
|
||||
interpolation=self.interpolation)
|
||||
|
||||
output = control_flow_util.smart_cond(training, random_zoomed_inputs,
|
||||
@ -1000,6 +1032,7 @@ class RandomZoom(PreprocessingLayer):
|
||||
'height_factor': self.height_factor,
|
||||
'width_factor': self.width_factor,
|
||||
'fill_mode': self.fill_mode,
|
||||
'fill_value': self.fill_value,
|
||||
'interpolation': self.interpolation,
|
||||
'seed': self.seed,
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ from __future__ import print_function
|
||||
from absl.testing import parameterized
|
||||
import numpy as np
|
||||
|
||||
from tensorflow.python.compat import compat
|
||||
from tensorflow.python.distribute.mirrored_strategy import MirroredStrategy
|
||||
from tensorflow.python.framework import errors
|
||||
from tensorflow.python.framework import test_util as tf_test_util
|
||||
@ -698,11 +699,13 @@ class RandomTransformTest(keras_parameterized.TestCase):
|
||||
transform_matrix,
|
||||
expected_output,
|
||||
mode,
|
||||
fill_value=0.0,
|
||||
interpolation='bilinear'):
|
||||
inp = np.arange(15).reshape((1, 5, 3, 1)).astype(np.float32)
|
||||
with self.cached_session(use_gpu=True):
|
||||
output = image_preprocessing.transform(
|
||||
inp, transform_matrix, fill_mode=mode, interpolation=interpolation)
|
||||
inp, transform_matrix, fill_mode=mode,
|
||||
fill_value=fill_value, interpolation=interpolation)
|
||||
self.assertAllClose(expected_output, output)
|
||||
|
||||
def test_random_translation_reflect(self):
|
||||
@ -871,7 +874,7 @@ class RandomTransformTest(keras_parameterized.TestCase):
|
||||
self._run_random_transform_with_mock(transform_matrix, expected_output,
|
||||
'nearest')
|
||||
|
||||
def test_random_translation_constant(self):
|
||||
def test_random_translation_constant_0(self):
|
||||
# constant output is (0000|abcd|0000)
|
||||
|
||||
# Test down shift by 1.
|
||||
@ -926,6 +929,62 @@ class RandomTransformTest(keras_parameterized.TestCase):
|
||||
self._run_random_transform_with_mock(transform_matrix, expected_output,
|
||||
'constant')
|
||||
|
||||
def test_random_translation_constant_1(self):
|
||||
with compat.forward_compatibility_horizon(2020, 8, 6):
|
||||
# constant output is (1111|abcd|1111)
|
||||
|
||||
# Test down shift by 1.
|
||||
# pyformat: disable
|
||||
expected_output = np.asarray(
|
||||
[[1., 1., 1.],
|
||||
[0., 1., 2.],
|
||||
[3., 4., 5.],
|
||||
[6., 7., 8],
|
||||
[9., 10., 11]]).reshape((1, 5, 3, 1)).astype(np.float32)
|
||||
# pyformat: enable
|
||||
transform_matrix = np.asarray([[1., 0., 0., 0., 1., -1., 0., 0.]])
|
||||
self._run_random_transform_with_mock(transform_matrix, expected_output,
|
||||
'constant', fill_value=1.0)
|
||||
|
||||
# Test up shift by 1.
|
||||
# pyformat: disable
|
||||
expected_output = np.asarray(
|
||||
[[3., 4., 5.],
|
||||
[6., 7., 8],
|
||||
[9., 10., 11.],
|
||||
[12., 13., 14.],
|
||||
[1., 1., 1.]]).reshape((1, 5, 3, 1)).astype(np.float32)
|
||||
# pyformat: enable
|
||||
transform_matrix = np.asarray([[1., 0., 0., 0., 1., 1., 0., 0.]])
|
||||
self._run_random_transform_with_mock(transform_matrix, expected_output,
|
||||
'constant', fill_value=1.0)
|
||||
|
||||
# Test left shift by 1.
|
||||
# pyformat: disable
|
||||
expected_output = np.asarray(
|
||||
[[1., 2., 1.],
|
||||
[4., 5., 1.],
|
||||
[7., 8., 1.],
|
||||
[10., 11., 1.],
|
||||
[13., 14., 1.]]).reshape((1, 5, 3, 1)).astype(np.float32)
|
||||
# pyformat: enable
|
||||
transform_matrix = np.asarray([[1., 0., 1., 0., 1., 0., 0., 0.]])
|
||||
self._run_random_transform_with_mock(transform_matrix, expected_output,
|
||||
'constant', fill_value=1.0)
|
||||
|
||||
# Test right shift by 1.
|
||||
# pyformat: disable
|
||||
expected_output = np.asarray(
|
||||
[[1., 0., 1.],
|
||||
[1., 3., 4],
|
||||
[1., 6., 7.],
|
||||
[1., 9., 10.],
|
||||
[1., 12., 13.]]).reshape((1, 5, 3, 1)).astype(np.float32)
|
||||
# pyformat: enable
|
||||
transform_matrix = np.asarray([[1., 0., -1., 0., 1., 0., 0., 0.]])
|
||||
self._run_random_transform_with_mock(transform_matrix, expected_output,
|
||||
'constant', fill_value=1.0)
|
||||
|
||||
def test_random_translation_nearest_interpolation(self):
|
||||
# nearest output is (aaaa|abcd|dddd)
|
||||
|
||||
|
@ -271,3 +271,38 @@ def _image_projective_transform_grad(op, grad):
|
||||
interpolation=interpolation,
|
||||
fill_mode=fill_mode)
|
||||
return [output, None, None]
|
||||
|
||||
|
||||
@ops.RegisterGradient("ImageProjectiveTransformV3")
|
||||
def _image_projective_transform_v3_grad(op, grad):
|
||||
"""Computes the gradient for ImageProjectiveTransform."""
|
||||
images = op.inputs[0]
|
||||
transforms = op.inputs[1]
|
||||
interpolation = op.get_attr("interpolation")
|
||||
fill_mode = op.get_attr("fill_mode")
|
||||
|
||||
image_or_images = ops.convert_to_tensor(images, name="images")
|
||||
transform_or_transforms = ops.convert_to_tensor(
|
||||
transforms, name="transforms", dtype=dtypes.float32)
|
||||
|
||||
if image_or_images.dtype.base_dtype not in _IMAGE_DTYPES:
|
||||
raise TypeError("Invalid dtype %s." % image_or_images.dtype)
|
||||
if len(transform_or_transforms.get_shape()) == 1:
|
||||
transforms = transform_or_transforms[None]
|
||||
elif len(transform_or_transforms.get_shape()) == 2:
|
||||
transforms = transform_or_transforms
|
||||
else:
|
||||
raise TypeError("Transforms should have rank 1 or 2.")
|
||||
|
||||
# Invert transformations
|
||||
transforms = flat_transforms_to_matrices(transforms=transforms)
|
||||
inverse = linalg_ops.matrix_inverse(transforms)
|
||||
transforms = matrices_to_flat_transforms(inverse)
|
||||
output = gen_image_ops.image_projective_transform_v3(
|
||||
images=grad,
|
||||
transforms=transforms,
|
||||
output_shape=array_ops.shape(image_or_images)[1:3],
|
||||
interpolation=interpolation,
|
||||
fill_mode=fill_mode,
|
||||
fill_value=0.0)
|
||||
return [output, None, None, None]
|
||||
|
@ -118,7 +118,7 @@ tf_class {
|
||||
}
|
||||
member_method {
|
||||
name: "__init__"
|
||||
argspec: "args=[\'self\', \'factor\', \'fill_mode\', \'interpolation\', \'seed\', \'name\'], varargs=None, keywords=kwargs, defaults=[\'reflect\', \'bilinear\', \'None\', \'None\'], "
|
||||
argspec: "args=[\'self\', \'factor\', \'fill_mode\', \'fill_value\', \'interpolation\', \'seed\', \'name\'], varargs=None, keywords=kwargs, defaults=[\'reflect\', \'0.0\', \'bilinear\', \'None\', \'None\'], "
|
||||
}
|
||||
member_method {
|
||||
name: "adapt"
|
||||
|
@ -118,7 +118,7 @@ tf_class {
|
||||
}
|
||||
member_method {
|
||||
name: "__init__"
|
||||
argspec: "args=[\'self\', \'height_factor\', \'width_factor\', \'fill_mode\', \'interpolation\', \'seed\', \'name\'], varargs=None, keywords=kwargs, defaults=[\'reflect\', \'bilinear\', \'None\', \'None\'], "
|
||||
argspec: "args=[\'self\', \'height_factor\', \'width_factor\', \'fill_mode\', \'fill_value\', \'interpolation\', \'seed\', \'name\'], varargs=None, keywords=kwargs, defaults=[\'reflect\', \'0.0\', \'bilinear\', \'None\', \'None\'], "
|
||||
}
|
||||
member_method {
|
||||
name: "adapt"
|
||||
|
@ -118,7 +118,7 @@ tf_class {
|
||||
}
|
||||
member_method {
|
||||
name: "__init__"
|
||||
argspec: "args=[\'self\', \'height_factor\', \'width_factor\', \'fill_mode\', \'interpolation\', \'seed\', \'name\'], varargs=None, keywords=kwargs, defaults=[\'None\', \'reflect\', \'bilinear\', \'None\', \'None\'], "
|
||||
argspec: "args=[\'self\', \'height_factor\', \'width_factor\', \'fill_mode\', \'fill_value\', \'interpolation\', \'seed\', \'name\'], varargs=None, keywords=kwargs, defaults=[\'None\', \'reflect\', \'0.0\', \'bilinear\', \'None\', \'None\'], "
|
||||
}
|
||||
member_method {
|
||||
name: "adapt"
|
||||
|
@ -1880,6 +1880,10 @@ tf_module {
|
||||
name: "ImageProjectiveTransformV2"
|
||||
argspec: "args=[\'images\', \'transforms\', \'output_shape\', \'interpolation\', \'fill_mode\', \'name\'], varargs=None, keywords=None, defaults=[\'CONSTANT\', \'None\'], "
|
||||
}
|
||||
member_method {
|
||||
name: "ImageProjectiveTransformV3"
|
||||
argspec: "args=[\'images\', \'transforms\', \'output_shape\', \'fill_value\', \'interpolation\', \'fill_mode\', \'name\'], varargs=None, keywords=None, defaults=[\'CONSTANT\', \'None\'], "
|
||||
}
|
||||
member_method {
|
||||
name: "ImageSummary"
|
||||
argspec: "args=[\'tag\', \'tensor\', \'max_images\', \'bad_color\', \'name\'], varargs=None, keywords=None, defaults=[\'3\', \'dtype: DT_UINT8\\ntensor_shape {\\n dim {\\n size: 4\\n }\\n}\\nint_val: 255\\nint_val: 0\\nint_val: 0\\nint_val: 255\\n\', \'None\'], "
|
||||
|
@ -118,7 +118,7 @@ tf_class {
|
||||
}
|
||||
member_method {
|
||||
name: "__init__"
|
||||
argspec: "args=[\'self\', \'factor\', \'fill_mode\', \'interpolation\', \'seed\', \'name\'], varargs=None, keywords=kwargs, defaults=[\'reflect\', \'bilinear\', \'None\', \'None\'], "
|
||||
argspec: "args=[\'self\', \'factor\', \'fill_mode\', \'fill_value\', \'interpolation\', \'seed\', \'name\'], varargs=None, keywords=kwargs, defaults=[\'reflect\', \'0.0\', \'bilinear\', \'None\', \'None\'], "
|
||||
}
|
||||
member_method {
|
||||
name: "adapt"
|
||||
|
@ -118,7 +118,7 @@ tf_class {
|
||||
}
|
||||
member_method {
|
||||
name: "__init__"
|
||||
argspec: "args=[\'self\', \'height_factor\', \'width_factor\', \'fill_mode\', \'interpolation\', \'seed\', \'name\'], varargs=None, keywords=kwargs, defaults=[\'reflect\', \'bilinear\', \'None\', \'None\'], "
|
||||
argspec: "args=[\'self\', \'height_factor\', \'width_factor\', \'fill_mode\', \'fill_value\', \'interpolation\', \'seed\', \'name\'], varargs=None, keywords=kwargs, defaults=[\'reflect\', \'0.0\', \'bilinear\', \'None\', \'None\'], "
|
||||
}
|
||||
member_method {
|
||||
name: "adapt"
|
||||
|
@ -118,7 +118,7 @@ tf_class {
|
||||
}
|
||||
member_method {
|
||||
name: "__init__"
|
||||
argspec: "args=[\'self\', \'height_factor\', \'width_factor\', \'fill_mode\', \'interpolation\', \'seed\', \'name\'], varargs=None, keywords=kwargs, defaults=[\'None\', \'reflect\', \'bilinear\', \'None\', \'None\'], "
|
||||
argspec: "args=[\'self\', \'height_factor\', \'width_factor\', \'fill_mode\', \'fill_value\', \'interpolation\', \'seed\', \'name\'], varargs=None, keywords=kwargs, defaults=[\'None\', \'reflect\', \'0.0\', \'bilinear\', \'None\', \'None\'], "
|
||||
}
|
||||
member_method {
|
||||
name: "adapt"
|
||||
|
@ -1880,6 +1880,10 @@ tf_module {
|
||||
name: "ImageProjectiveTransformV2"
|
||||
argspec: "args=[\'images\', \'transforms\', \'output_shape\', \'interpolation\', \'fill_mode\', \'name\'], varargs=None, keywords=None, defaults=[\'CONSTANT\', \'None\'], "
|
||||
}
|
||||
member_method {
|
||||
name: "ImageProjectiveTransformV3"
|
||||
argspec: "args=[\'images\', \'transforms\', \'output_shape\', \'fill_value\', \'interpolation\', \'fill_mode\', \'name\'], varargs=None, keywords=None, defaults=[\'CONSTANT\', \'None\'], "
|
||||
}
|
||||
member_method {
|
||||
name: "ImageSummary"
|
||||
argspec: "args=[\'tag\', \'tensor\', \'max_images\', \'bad_color\', \'name\'], varargs=None, keywords=None, defaults=[\'3\', \'dtype: DT_UINT8\\ntensor_shape {\\n dim {\\n size: 4\\n }\\n}\\nint_val: 255\\nint_val: 0\\nint_val: 0\\nint_val: 255\\n\', \'None\'], "
|
||||
|
Loading…
Reference in New Issue
Block a user