徒然なる日々を送るソフトウェアデベロッパーの記録(2)

技術上思ったことや感じたことを気ままに記録していくブログです。さくらから移設しました。

OpenCV の imread は画像の方向を考慮しない

OpenCV の imread は画像フォーマットを自分で判断することなく、自動で読んでくれるのでらくちんなのだが、
例えば JPEGExif タグを見てくれないため、orientation が normal でない場合は回転したりひっくり返ったり
した画像のまま処理しようとしてしまう。

今のところ、これを避けるには自分で何とかするしか無いようだ。
せっかく libjpeg に依存しているのでこれを使うのもよし、orientation だけなら自分で Exif を解析するもよし。
以下の記事を参考にして自作してみました。

DSAS開発者の部屋:Exif データにアクセスするコードを自作してみる
ExifのOrientationを見て画像を回転させる : hackmylife

後は orientation の値に従って画像を回転させれば良い。
flip と transpose で実装するらしいが今日はもう眠いので明日に順延。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

const char *estr[] = {
  "(no error)", "not JPEG file", "no EXif header", "JPEG header corrupted"
};
const char *oristr[] = {
  "normal", "inverse", "rot 180", "rot 180 + inverse", "rot 90 + inverse", "rot 
90", "rot 270 + inverse", "rot 270"
};

struct _Tiff_ {
  unsigned short order;
  unsigned char version[2];
  long offsetIFD0;
};

typedef struct _Exif_ {
  long offset;
  struct _Tiff_ tiff;
} Exif;

#define TAG_ORIENTATION (0x0112)
#define TYPE_SHORT (3)

int isBigEndian(Exif *exif);
short toShort(Exif *exif, unsigned char *);
long toLong(Exif *exif, unsigned char *);
int getOrientation(FILE *fp, int *pori);
int analyzeExif(Exif *exif, FILE *fp, int *pori);

int main(int argc, char **argv) {
  FILE *fp;
  int orientation = 1; // default
  int ecode = 0;

  if (argc < 2) {
    printf("usage: orientation <JPEG file>\n");
    return 0;
  }
  fp = fopen(argv[1], "rb");
  if (!fp) {
    printf("failed to open file\n");
    return 0;
  }

  ecode = getOrientation(fp, &orientation);
  if (ecode) {
    printf("%s\n", estr[ecode]);
  } else {
    if (orientation >= 1 && orientation <= 8) {
      printf("Orientation: %s(%d)\n", oristr[orientation - 1], orientation);
    } else {
      printf("illegal orientation(%d)\n", orientation);
    }
  }
  close(fp);
  return 0;
}

int getOrientation(FILE *fp, int *pori) {
  unsigned char marker[8];

  // check JPEG header
  fread(marker, sizeof(char), 2, fp);
  if (marker[0] != 0xff || marker[1] != 0xd8) {
    return 1;
  }

  while(1) {
    if (fread(marker, sizeof(char), 2, fp) != 2) {
      return 1;
    }
    if (marker[0] == 0xff && marker[1] == 0xe1) {
      // APP1 header
      int len;
      if (fread(marker, sizeof(char), 2, fp) != 2) {
        return 1;
      }
      len = (marker[0] << 8) + marker[1] - 2 - 6;
      if (fread(marker, sizeof(char), 6, fp) != 6) {
        return 1;
      }
      if (memcmp(marker, "Exif\0\0", 6) != 0) {
        // not exif header
        fseek(fp, len, SEEK_CUR);
      } else {
        // exif header; go ahead
        Exif exif;
        memset(&exif, 0, sizeof(exif));
        exif.offset = ftell(fp);
        return analyzeExif(&exif, fp, pori);
      }
    } else if (marker[0] == 0xff && (marker[1] & 0xf0) == 0xe0) {
      // other header
      int oft;
      if (fread(marker, sizeof(char), 2, fp) != 2) {
        return 1;
      }
      oft = (marker[0] << 8) + marker[1] - 2;
      fseek(fp, oft, SEEK_CUR);
    } else {
      // No Exif header
      return 2;
    }
  }
}

int analyzeExif(Exif *exif, FILE *fp, int *pori) {
  unsigned char marker[4];
  short ntag = 0;
  short i;

  // byte order byte
  if (fread(&exif->tiff.order, sizeof(short), 1, fp) != 1) {
    return 1;
  }
  // TIFF version; 0x2a
  if (fread(&exif->tiff.version, sizeof(char), 2, fp) != 2) {
    return 3;
  }
  if (toShort(exif, &exif->tiff.version[0]) != 0x2a) {
    return 3;
  }
  // offset to IFD0
  if (fread(marker, sizeof(char), 4, fp) != 4) {
    return 3;
  }
  exif->tiff.offsetIFD0 = toLong(exif, marker);
  fseek(fp, exif->offset + exif->tiff.offsetIFD0, SEEK_SET);

  // IFD0 tag quantity
  if (fread(marker, sizeof(char), 2, fp) != 2) {
    return 3;
  }
  ntag = toShort(exif, marker);
  for (i = 0; i < ntag; ++i) {
    short tag;
    if (fread(marker, sizeof(char), 2, fp) != 2) {
      return 3;
    }
    tag = toShort(exif, marker);
    if (tag != TAG_ORIENTATION) {
      fseek(fp, 10, SEEK_CUR);
    } else {
      // orientation tag を発見
      // TYPE は short
      if (fread(marker, sizeof(char), 2, fp) != 2) {
        return 3;
      }
      if (toShort(exif, marker) != TYPE_SHORT) {
        return 3;
      }

      // パラメータのカウント数は 1
      if (fread(marker, sizeof(char), 4, fp) != 4) {
        return 3;
      }
      if (toLong(exif, marker) != 1) {
        return 1;
      }
      // short value は詰めて格納される
      if (fread(marker, sizeof(char), 2, fp) != 2) {
        return 3;
      }
      *pori = toShort(exif, marker);
      return 0;
    }
  }

  // デフォルトの orientation
  *pori = 1;
  return 0;
}

int isBigEndian(Exif *exif) {
  if (exif->tiff.order == 0x4d4d) {
    return 1;
  } else {
    return 0;
  }
}

short toShort(Exif *exif, unsigned char *b) {
  if (!isBigEndian(exif)) {
    return (short)(b[0] + (b[1] << 8));
  } else {
    return (short)((b[0] << 8) + b[1]);
  }
}

long toLong(Exif *exif, unsigned char *b) {
  if (!isBigEndian(exif)) {
    return (long)(b[0] + (b[1] << 8) + (b[2] << 16) + (b[3] << 24));
  } else {
    return (long)((b[0] << 24) + (b[1] << 16) + (b[2] << 8) + b[3]);
  }
}