add rotate op for EXIF image info

pull/9303/head
xulei2020 4 years ago
parent 44ea3902b8
commit ada57d0e58

@ -0,0 +1,128 @@
/**
* Copyright 2020 Huawei Technologies Co., Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "minddata/dataset/kernels/image/exif_utils.h"
#include <algorithm>
#include <cstdint>
#define UNKNOW_ORIENTATION 0
namespace mindspore {
namespace dataset {
template <typename T>
T parse_bytes(const uint8_t *buf, bool intel_align);
template <>
uint8_t parse_bytes(const uint8_t *buf, bool intel_align) {
return *buf;
}
template <>
uint16_t parse_bytes(const uint8_t *buf, bool intel_align) {
uint16_t res;
if (intel_align) {
res = (static_cast<uint16_t>(buf[1]) << 8) | buf[0];
} else {
res = (static_cast<uint16_t>(buf[0]) << 8) | buf[1];
}
return res;
}
template <>
uint32_t parse_bytes(const uint8_t *buf, bool intel_align) {
uint32_t res;
if (intel_align) {
res = (static_cast<uint32_t>(buf[3]) << 24) | (static_cast<uint32_t>(buf[2]) << 16) |
(static_cast<uint32_t>(buf[1]) << 8) | buf[0];
} else {
res = (static_cast<uint32_t>(buf[0]) << 24) | (static_cast<uint32_t>(buf[1]) << 16) |
(static_cast<uint32_t>(buf[2]) << 8) | buf[3];
}
return res;
}
int parseExif(const uint8_t *buf, uint32_t len) {
bool intel_align = true;
uint32_t offset = 0;
if (!buf || len < 6) return UNKNOW_ORIENTATION;
if (!std::equal(buf, buf + 6, "Exif\0\0")) return UNKNOW_ORIENTATION;
offset += 6;
if (offset + 8 > len) return UNKNOW_ORIENTATION;
if (buf[offset] == 'I' && buf[offset + 1] == 'I') {
intel_align = true;
} else {
if (buf[offset] == 'M' && buf[offset + 1] == 'M')
intel_align = false;
else
return UNKNOW_ORIENTATION;
}
offset += 2;
if (parse_bytes<uint16_t>(buf + offset, intel_align) != 0x2a) return UNKNOW_ORIENTATION;
offset += 2;
uint32_t first_ifd_offset = parse_bytes<uint32_t>(buf + offset, intel_align);
offset += first_ifd_offset - 4;
if (offset >= len) return UNKNOW_ORIENTATION;
if (offset + 2 > len) return UNKNOW_ORIENTATION;
int num_entries = parse_bytes<uint16_t>(buf + offset, intel_align);
if (offset + 6 + 12 * num_entries > len) return UNKNOW_ORIENTATION;
offset += 2;
while (num_entries > 0) {
uint16_t tag = parse_bytes<uint16_t>(buf + offset, intel_align);
if (tag == 0x112) {
uint16_t format = parse_bytes<uint16_t>(buf + offset + 2, intel_align);
uint32_t length = parse_bytes<uint32_t>(buf + offset + 4, intel_align);
if (format == 3 && length) {
uint16_t orient = parse_bytes<uint16_t>(buf + offset + 8, intel_align);
return static_cast<int>(orient);
}
}
offset += 12;
num_entries--;
}
return UNKNOW_ORIENTATION;
}
int ExifInfo::parseOrientation(const unsigned char *data, unsigned len) {
if (!data || len < 4) return UNKNOW_ORIENTATION;
if (data[0] != 0xFF || data[1] != 0xD8) return UNKNOW_ORIENTATION;
while (len > 2) {
if (data[len - 1] == 0xD9 && data[len - 2] == 0xFF) break;
len--;
}
if (len <= 2) return UNKNOW_ORIENTATION;
unsigned int offset = 0;
for (; offset < len - 1; offset++) {
if (data[offset] == 0xFF && data[offset + 1] == 0xE1) break;
}
if (offset + 4 > len) return UNKNOW_ORIENTATION;
offset += 2;
uint16_t section_length = parse_bytes<uint16_t>(data + offset, false);
if (offset + section_length > len || section_length < 16) return UNKNOW_ORIENTATION;
offset += 2;
return parseExif(data + offset, len - offset);
}
} // namespace dataset
} // namespace mindspore

@ -0,0 +1,29 @@
/**
* Copyright 2020 Huawei Technologies Co., Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef MINDSPORE_CCSRC_MINDDATA_DATASET_KERNELS_IMAGE_EXIF_H
#define MINDSPORE_CCSRC_MINDDATA_DATASET_KERNELS_IMAGE_EXIF_H
namespace mindspore {
namespace dataset {
class ExifInfo {
public:
int parseOrientation(const unsigned char *data, unsigned len);
};
} // namespace dataset
} // namespace mindspore
#endif

@ -16,6 +16,7 @@
#include "minddata/dataset/kernels/image/lite_cv/image_process.h"
#include <limits.h>
#include <string.h>
#include <cmath>
#include <vector>
@ -61,6 +62,9 @@ static void ResizeBilinear3C(const unsigned char *src, int src_width, int src_he
double scale_width = static_cast<double>(src_width) / dst_width;
double scale_height = static_cast<double>(src_height) / dst_height;
if (dst_height >= (INT_MAX / 2 - dst_width)) {
return;
}
int *data_buf = new int[2 * dst_width + 2 * dst_height];
int *x_offset = data_buf;
@ -140,6 +144,9 @@ static void ResizeBilinear1C(const unsigned char *src, int src_width, int src_he
double scale_width = static_cast<double>(src_width) / dst_width;
double scale_height = static_cast<double>(src_height) / dst_height;
if (dst_height >= (INT_MAX / 2 - dst_width)) {
return;
}
int *data_buf = new int[2 * dst_width + 2 * dst_height];
int *x_offset = data_buf;
@ -408,7 +415,9 @@ bool ConvertTo(const LiteMat &src, LiteMat &dst, double scale) {
if (src.data_type_ != LDataType::UINT8) {
return false;
}
if (scale < 0.0 || scale > 100) {
return false;
}
(void)dst.Init(src.width_, src.height_, src.channel_, LDataType::FLOAT32);
const unsigned char *src_start_p = src;
float *dst_start_p = dst;
@ -442,7 +451,7 @@ bool Crop(const LiteMat &src, LiteMat &dst, int x, int y, int w, int h) {
if (x < 0 || y < 0 || w <= 0 || h <= 0) {
return false;
}
if (y + h > src.height_ || x + w > src.width_) {
if (y > src.height_ - h || x > src.width_ - w) {
return false;
}

@ -15,8 +15,8 @@
*/
#include "minddata/dataset/kernels/image/lite_cv/lite_mat.h"
#include <limits.h>
#include <algorithm>
#include <limits>
#include <cmath>
#ifdef ENABLE_ANDROID
#if defined(__arm__) || defined(__aarch64__) || defined(_M_ARM) || defined(_M_ARM64)
@ -252,6 +252,9 @@ void LiteMat::Release() {
void *LiteMat::AlignMalloc(unsigned int size) {
unsigned int length = sizeof(void *) + ALIGN - 1;
if (size > INT_MAX - length) {
return nullptr;
}
void *p_raw = reinterpret_cast<void *>(malloc(size + length));
if (p_raw) {
void **p_algin = reinterpret_cast<void **>(((size_t)(p_raw) + length) & ~(ALIGN - 1));

@ -441,5 +441,154 @@ Status Pad(const std::shared_ptr<Tensor> &input, std::shared_ptr<Tensor> *output
return Status::OK();
}
static Status RotateAngleWithOutMirror(const std::shared_ptr<Tensor> &input, std::shared_ptr<Tensor> *output,
const uint64_t orientation) {
try {
int height = 0;
int width = 0;
double M[6] = {};
LiteMat lite_mat_rgb(input->shape()[1], input->shape()[0], input->shape()[2],
const_cast<void *>(reinterpret_cast<const void *>(input->GetBuffer())),
GetLiteCVDataType(input->type()));
LiteMat lite_mat_affine;
if (orientation == 3) {
height = lite_mat_rgb.height_;
width = lite_mat_rgb.width_;
M[0] = -1.0f;
M[1] = 0.0f;
M[2] = lite_mat_rgb.width_ - 1;
M[3] = 0.0f;
M[4] = -1.0f;
M[5] = lite_mat_rgb.height_ - 1;
} else if (orientation == 6) {
height = lite_mat_rgb.width_;
width = lite_mat_rgb.height_;
M[0] = 0.0f;
M[1] = -1.0f;
M[2] = lite_mat_rgb.height_ - 1;
M[3] = 1.0f;
M[4] = 0.0f;
M[5] = 0.0f;
} else if (orientation == 8) {
height = lite_mat_rgb.width_;
width = lite_mat_rgb.height_;
M[0] = 0.0f;
M[1] = 1.0f;
M[2] = 0.0f;
M[3] = -1.0f;
M[4] = 0.0f;
M[5] = lite_mat_rgb.width_ - 1.0f;
} else {
}
std::vector<size_t> dsize;
dsize.push_back(width);
dsize.push_back(height);
bool ret = Affine(lite_mat_rgb, lite_mat_affine, M, dsize, UINT8_C3(0, 0, 0));
CHECK_FAIL_RETURN_UNEXPECTED(ret, "Rotate failed in lite cv");
// new shape for output tensor
TensorShape new_shape = TensorShape({lite_mat_affine.height_, lite_mat_affine.width_, input->shape()[2]});
std::shared_ptr<Tensor> output_tensor;
RETURN_IF_NOT_OK(Tensor::CreateFromMemory(new_shape, input->type(), static_cast<uchar *>(lite_mat_affine.data_ptr_),
&output_tensor));
*output = output_tensor;
} catch (std::runtime_error &e) {
RETURN_STATUS_UNEXPECTED("Error in image Rotate.");
}
return Status::OK();
}
static Status RotateAngleWithMirror(const std::shared_ptr<Tensor> &input, std::shared_ptr<Tensor> *output,
const uint64_t orientation) {
try {
int height = 0;
int width = 0;
double M[6] = {};
LiteMat lite_mat_rgb(input->shape()[1], input->shape()[0], input->shape()[2],
const_cast<void *>(reinterpret_cast<const void *>(input->GetBuffer())),
GetLiteCVDataType(input->type()));
LiteMat lite_mat_affine;
if (orientation == 2) {
height = lite_mat_rgb.height_;
width = lite_mat_rgb.width_;
M[0] = -1.0f;
M[1] = 0.0f;
M[2] = lite_mat_rgb.width_ - 1;
M[3] = 0.0f;
M[4] = 1.0f;
M[5] = 0.0f;
} else if (orientation == 5) {
height = lite_mat_rgb.width_;
width = lite_mat_rgb.height_;
M[0] = 0.0f;
M[1] = 1.0f;
M[2] = 0.0f;
M[3] = 1.0f;
M[4] = 0.0f;
M[5] = 0.0f;
} else if (orientation == 7) {
height = lite_mat_rgb.width_;
width = lite_mat_rgb.height_;
M[0] = 0.0f;
M[1] = -1.0f;
M[2] = lite_mat_rgb.height_ - 1;
M[3] = -1.0f;
M[4] = 0.0f;
M[5] = lite_mat_rgb.width_ - 1;
} else if (orientation == 4) {
height = lite_mat_rgb.height_;
width = lite_mat_rgb.width_;
M[0] = 1.0f;
M[1] = 0.0f;
M[2] = 0.0f;
M[3] = 0.0f;
M[4] = -1.0f;
M[5] = lite_mat_rgb.height_ - 1;
} else {
}
std::vector<size_t> dsize;
dsize.push_back(width);
dsize.push_back(height);
bool ret = Affine(lite_mat_rgb, lite_mat_affine, M, dsize, UINT8_C3(0, 0, 0));
CHECK_FAIL_RETURN_UNEXPECTED(ret, "Rotate failed in lite cv");
// new shape for output tensor
TensorShape new_shape = TensorShape({lite_mat_affine.height_, lite_mat_affine.width_, input->shape()[2]});
std::shared_ptr<Tensor> output_tensor;
RETURN_IF_NOT_OK(Tensor::CreateFromMemory(new_shape, input->type(), static_cast<uchar *>(lite_mat_affine.data_ptr_),
&output_tensor));
*output = output_tensor;
} catch (std::runtime_error &e) {
RETURN_STATUS_UNEXPECTED("Error in image Rotate.");
}
return Status::OK();
}
static bool IsMirror(int orientation) {
if (orientation == 2 || orientation == 4 || orientation == 5 || orientation == 7) {
return true;
}
return false;
}
// rotate the image by EXIF orientation
Status Rotate(const std::shared_ptr<Tensor> &input, std::shared_ptr<Tensor> *output, const uint64_t orientation) {
if (input->Rank() != 3) {
RETURN_STATUS_UNEXPECTED("Input Tensor is not in shape of <H,W,C>");
}
if (input->type() != DataType::DE_FLOAT32 && input->type() != DataType::DE_UINT8) {
RETURN_STATUS_UNEXPECTED("Only float32, uint8 support in Pad");
}
if (!IsMirror(orientation)) {
return RotateAngleWithOutMirror(input, output, orientation);
} else {
return RotateAngleWithMirror(input, output, orientation);
}
}
} // namespace dataset
} // namespace mindspore

@ -109,6 +109,8 @@ Status Resize(const std::shared_ptr<Tensor> &input, std::shared_ptr<Tensor> *out
Status Pad(const std::shared_ptr<Tensor> &input, std::shared_ptr<Tensor> *output, const int32_t &pad_top,
const int32_t &pad_bottom, const int32_t &pad_left, const int32_t &pad_right, const BorderType &border_types,
uint8_t fill_r = 0, uint8_t fill_g = 0, uint8_t fill_b = 0);
Status Rotate(const std::shared_ptr<Tensor> &input, std::shared_ptr<Tensor> *output, const uint64_t orientation);
} // namespace dataset
} // namespace mindspore
#endif // MINDSPORE_CCSRC_MINDDATA_DATASET_KERNELS_IMAGE_IMAGE_UTILS_H_

@ -16,7 +16,12 @@
#include "minddata/dataset/util/status.h"
#include <sstream>
#include "utils/ms_utils.h"
#ifndef ENABLE_ANDROID
#include "minddata/dataset/util/task_manager.h"
#else
#include "minddata/dataset/util/log_adapter.h"
#endif
namespace mindspore {
namespace dataset {
@ -104,11 +109,14 @@ Status::Status(const StatusCode code, const std::string &msg) : code_(code), err
Status::Status(const StatusCode code, int line_of_code, const char *file_name, const std::string &extra) {
code_ = code;
std::ostringstream ss;
#ifndef ENABLE_ANDROID
ss << "Thread ID " << this_thread::get_id() << " " << CodeAsString(code) << ". ";
if (!extra.empty()) {
ss << extra;
}
ss << "\n";
#endif
ss << "Line of code : " << line_of_code << "\n";
if (file_name != nullptr) {
ss << "File : " << file_name << "\n";

@ -81,12 +81,12 @@ AUX_SOURCE_DIRECTORY(${MINDDATA_DIR}/util MINDDATA_UTIL_SRC_FILES)
AUX_SOURCE_DIRECTORY(${MINDDATA_DIR}/kernels/image/lite_cv MINDDATA_KERNELS_IMAGE_LITE_CV_FILES)
if (PLATFORM_ARM32 OR PLATFORM_ARM64)
if (BUILD_MINDDATA STREQUAL "full")
set(BUILD_MINDDATA "wrapper")
endif ()
if (BUILD_MINDDATA STREQUAL "full")
set(BUILD_MINDDATA "wrapper")
endif ()
if (BUILD_MINDDATA STREQUAL "full")
include_directories("${CMAKE_SOURCE_DIR}/../ccsrc/minddata/dataset/kernels/image")
list(REMOVE_ITEM MINDDATA_API_SRC_FILES
@ -334,6 +334,7 @@ elseif (BUILD_MINDDATA STREQUAL "wrapper")
${MINDDATA_DIR}/kernels/data/random_choice_op.cc
${MINDDATA_DIR}/kernels/data/type_cast_op.cc
${MINDDATA_DIR}/kernels/data/data_utils.cc
${MINDDATA_DIR}/kernels/image/exif_utils.cc
${CMAKE_CURRENT_SOURCE_DIR}/wrapper/MDToDApi.cc
${CMAKE_CURRENT_SOURCE_DIR}/wrapper/album_op_android.cc
)

@ -17,6 +17,8 @@
#define DATASET_MDTODAPI_H_
#include <stdint.h>
#include <sys/types.h>
class MDToDApi;
typedef struct MDToDBuff {

@ -18,6 +18,7 @@
#include <iomanip>
#include "minddata/dataset/core/tensor_shape.h"
#include "minddata/dataset/kernels/image/lite_image_utils.h"
#include "minddata/dataset/kernels/image/exif_utils.h"
namespace mindspore {
namespace dataset {
@ -33,7 +34,8 @@ AlbumOp::AlbumOp(const std::string &file_dir, bool do_decode, const std::string
current_cnt_(0),
dirname_offset_(0),
sampler_(false),
sampler_index_(0) {
sampler_index_(0),
rotate_(true) {
PrescanEntry();
}
@ -48,7 +50,8 @@ AlbumOp::AlbumOp(const std::string &file_dir, bool do_decode, const std::string
current_cnt_(0),
dirname_offset_(0),
sampler_(true),
sampler_index_(index) {
sampler_index_(index),
rotate_(true) {
PrescanEntry();
}
@ -168,6 +171,7 @@ bool AlbumOp::CheckImageType(const std::string &file_name, bool *valid) {
Status AlbumOp::LoadImageTensor(const std::string &image_file_path, uint32_t col_num, TensorPtr *tensor) {
TensorPtr image;
TensorPtr rotate_tensor;
std::ifstream fs;
fs.open(image_file_path, std::ios::binary | std::ios::in);
if (fs.fail()) {
@ -204,21 +208,66 @@ Status AlbumOp::LoadImageTensor(const std::string &image_file_path, uint32_t col
}
// if it is a jpeg image, load and try to decode
RETURN_IF_NOT_OK(Tensor::CreateFromFile(image_file_path, &image));
Status rc;
if (decode_ && valid) {
Status rc = Decode(image, tensor);
if (rc.IsError()) {
RETURN_IF_NOT_OK(LoadEmptyTensor(col_num, tensor));
return Status::OK();
int orientation = GetOrientation(image_file_path);
if (orientation > 1 && this->rotate_) {
rc = Decode(image, &rotate_tensor);
if (rc.IsError()) {
RETURN_IF_NOT_OK(LoadEmptyTensor(col_num, tensor));
return Status::OK();
}
rc = Rotate(rotate_tensor, tensor, orientation);
if (rc.IsError()) {
RETURN_IF_NOT_OK(LoadEmptyTensor(col_num, tensor));
return Status::OK();
}
} else {
rc = Decode(image, tensor);
if (rc.IsError()) {
RETURN_IF_NOT_OK(LoadEmptyTensor(col_num, tensor));
return Status::OK();
}
}
}
// row->push_back(std::move(image));
return Status::OK();
}
// get orientation from EXIF file
int AlbumOp::GetOrientation(const std::string &folder_path) {
FILE *fp = fopen(folder_path.c_str(), "rb");
if (!fp) {
MS_LOG(WARNING) << "Can't read file for EXIF: file = " << folder_path;
return 0;
}
fseek(fp, 0, SEEK_END);
int64_t fsize = ftell(fp);
rewind(fp);
unsigned char *buf = new unsigned char[fsize];
if (fread(buf, 1, fsize, fp) != fsize) {
MS_LOG(WARNING) << "read file size error for EXIF: file = " << folder_path;
delete[] buf;
fclose(fp);
return 0;
}
fclose(fp);
// Parse EXIF
mindspore::dataset::ExifInfo result;
int code = result.parseOrientation(buf, fsize);
delete[] buf;
if (code == 0) {
MS_LOG(WARNING) << "Error parsing EXIF, use default code = " << code << ".";
}
return code;
}
Status AlbumOp::LoadStringArrayTensor(const nlohmann::json &json_obj, uint32_t col_num, TensorPtr *tensor) {
std::vector<std::string> data = json_obj.get<std::vector<std::string>>();
MS_LOG(WARNING) << "String array label found: " << data << ".";
MS_LOG(INFO) << "String array label found: " << data << ".";
// TensorPtr label;
RETURN_IF_NOT_OK(Tensor::CreateFromVector(data, tensor));
// row->push_back(std::move(label));

@ -30,7 +30,6 @@
#include "minddata/dataset/engine/data_buffer.h"
#include "minddata/dataset/engine/data_schema.h"
#include "minddata/dataset/util/path.h"
#include "minddata/dataset/util/queue.h"
#include "minddata/dataset/util/status.h"
namespace mindspore {
@ -50,6 +49,7 @@ class AlbumOp {
/// \param[in] do_decode - decode image files
/// \param[in] schema_file - schema file
/// \param[in] exts - set of file extensions to read, if empty, read everything under the dir
/// \param[in] rotate - rotate image exif orientation
AlbumOp(const std::string &file_dir, bool do_decode, const std::string &schema_file,
const std::set<std::string> &exts);
@ -59,6 +59,7 @@ class AlbumOp {
/// \param[in] schema_file - schema file
/// \param[in] exts - set of file extensions to read, if empty, read everything under the dir
/// \param[in] index - the specific file index
/// \param[in] rotate - rotate image exif orientation
AlbumOp(const std::string &file_dir, bool do_decode, const std::string &schema_file,
const std::set<std::string> &exts, uint32_t index);
@ -82,6 +83,10 @@ class AlbumOp {
// @return Name of the current Op
std::string Name() const { return "AlbumOp"; }
// Op name DisableRotate
// @return
void DisableRotate() { this->rotate_ = false; }
private:
/// \brief Load image to tensor
/// \param[in] image_file Image name of file
@ -153,6 +158,10 @@ class AlbumOp {
Status LoadTensorRow(row_id_type row_id, const std::string &file,
std::unordered_map<std::string, std::shared_ptr<Tensor>> *map_row);
/// \brief get image exif orientation
/// \param[in] file file path
int GetOrientation(const std::string &file);
std::string folder_path_; // directory of image folder
bool decode_;
std::vector<std::string> columns_to_load_;
@ -167,6 +176,7 @@ class AlbumOp {
int64_t sampler_index_;
std::vector<std::string> image_rows_;
std::unordered_map<std::string, int32_t> column_name_id_map_;
bool rotate_;
};
} // namespace dataset
} // namespace mindspore

Loading…
Cancel
Save