あいさつ

こんにちは.もう5月も終盤ということで,時間が経つのは早いですね.
16bit tiff形式の画像から四隅(フルサイズと仮定された場合はAPSCの四隅も)を切り出して画像にするプログラムを書きました.(無いものは作ろう)
Visual Studio 2017で開発してgithubにそのまま上げてます(いいのかこれ…?).同じような環境があるなら.slnを開いて,OpenCVの設定だけ自分の環境に合わせていただければ動くと思います.そうでなく,C++のコンパイラとOpenCVが入った環境なら,以下のプログラムコードを参考にしてください.

モチベーション

りっちー氏の以下のツイートを見て

割と簡単に書けるのでは?と思ったので書いてみました.

プログラム

きれいなプログラムではないですが以下がプログラムです.

#include "stdafx.h"
#include <opencv2/opencv.hpp>
#include <iostream>
#include <fstream>
#include <string>
#include <cmath>
#include <queue>

#define PI 3.14159265358979

typedef struct Point { int x;  int y; } _point;

int main(int argc, char* argv[])
{
    std::string inputfilename, configfilename;

    if (argc != 3) {
        std::cerr << "Usage: corner_maker [filename] [config]" << std::endl;
        return -1;
    }

    inputfilename = argv[1];
    configfilename = argv[2];

    // 設定ファイル読み込み
    std::ifstream ifs(configfilename);
    
    if (!ifs.is_open()) {
        std::cerr << "Error: Couldn't read config: " << argv[2] << std::endl;
        return -1;
    }
    
    // 設定ファイル検査
    double f = 0.0;
    unsigned int side = 0;
    std::string pattern;
    int drawable[5][5] = {0};
    
    ifs >> f >> side >> pattern;
    
    if (f <= 0.0) {
        std::cerr << "Error: Invalid focal length: " << f  << "mm > 0.0mm." << std::endl;
        return -1;
    }
    else if (side <= 0) {
        std::cerr << "Error: Invalid side pixels: " << side << " > 0." << std::endl;
        return -1;
    }
    else if (pattern != "full-size" && pattern != "apsc") {
        std::cerr << "Error: Invalid angle pattern " << pattern  << " = full-size or apsc."<< std::endl;
        return -1;
    }

    if (pattern == "full-size") {
        for (int i = 0; i < 5; ++i) {
            for (int j = 0; j < 5; ++j) {
                ifs >> drawable[i][j];
                if (ifs.eof() && i != 4 && j != 4) {
                    std::cerr << "Error: Lack of drawing squares." << std::endl;
                    std::cerr << "     if use full-size pattern, describe in your config as follows" << std::endl;
                    std::cerr << "    1 1 1 1 1" << std::endl;
                    std::cerr << "    1 1 1 1 1" << std::endl;
                    std::cerr << "    1 1 1 1 1" << std::endl;
                    std::cerr << "    1 1 1 1 1" << std::endl;
                    std::cerr << "    1 1 1 1 1" << std::endl;
                    return -1;
                }
            }
        }
    }
    else {
        for (int i = 0; i < 3; ++i) {
            for (int j = 0; j < 3; ++j) {
                ifs >> drawable[i][j];
                if (ifs.eof() && i != 2 && j != 2) {
                    std::cerr << "Error: Lack of drawing squares." << std::endl;
                    std::cerr << "     if use apsc pattern, describe in your config as follows" << std::endl;
                    std::cerr << "    1 1 1" << std::endl;
                    std::cerr << "    1 1 1" << std::endl;
                    std::cerr << "    1 1 1" << std::endl;
                    return -1;
                }
            }
        }
    }
    
    // 画像読み込み
    cv::Mat image = cv::imread(inputfilename, 2 | 4);
    if (image.data == NULL) {
        std::cerr << "Error: Couldn't read image file " << inputfilename << std::endl;
        return -1;
    }
    
    int xsize = image.cols, ysize = image.rows;
    int newxsize, newysize;
    // 画像が縦長の場合,横長(反時計回りに90度回す)に変換
    if (xsize < ysize) {
        newxsize = ysize;
        newysize = xsize;
    }
    else {
        newxsize = xsize;
        newysize = ysize;
    }


    cv::Mat data = cv::Mat::zeros(newysize, newxsize, CV_16UC3);
    
    if (xsize < ysize) {
        for (int j = 0; j < ysize; ++j) {
            for (int i = 0; i < xsize; ++i) {
                data.at<cv::Vec3w>(xsize - i - 1, j) = image.at<cv::Vec3w>(j, i);
            }
        }
    }
    else {
        for (int j = 0; j < ysize; ++j) {
            for (int i = 0; i < xsize; ++i) {
                data.at<cv::Vec3w>(j, i) = image.at<cv::Vec3w>(j, i);
            }
        }
    }

    // 元画像のメモリを開放
    image.release();
    xsize = newxsize;
    ysize = newysize;

    // 不要な処理.焦点距離から,1画素あたりの角度を算出.
    double horizontal = (pattern == "full-size") ? 2.0 * (atan(36.0  / (2.0 * f)) * 180 / PI)
        : 2.0 * (atan(23.4 / (2.0 * f)) * 180 / PI);
    std::cout << horizontal * 3600.0 / (double)xsize << " arcsec / pixel" << std::endl;

    cv::Mat output = cv::Mat::zeros(side * ((pattern == "full-size") ? 5 : 3), side * ((pattern == "full-size") ? 5 : 3), CV_16UC3);

    //画像の中央の座標
    _point center = { (xsize / 2), (ysize / 2) };

    // 切り出しの開始点(左上の点)をpush
    std::queue<_point> start_points;

    if (pattern == "full-size") {
        start_points.push(Point{ 0, 0 });
        start_points.push(Point{ (int)(xsize * 6.3 / 36), 0 });
        start_points.push(Point{ (int)(center.x - side / 2.0), 0 });
        start_points.push(Point{ (int)(xsize * 29.7 / 36.0 - side), 0 });
        start_points.push(Point{ xsize - (int)side, 0 });

        start_points.push(Point{ 0, (int)(ysize * 3.65 / 24.0)});
        start_points.push(Point{ (int)(xsize * 6.3 / 36), (int)(ysize * 3.65 / 24.0) });
        start_points.push(Point{ (int)(center.x - side / 2.0), (int)(ysize * 3.65 / 24.0) });
        start_points.push(Point{ (int)(xsize * 29.7 / 36.0 - side), (int)(ysize * 3.65 / 24.0) });
        start_points.push(Point{ xsize - (int)side, (int)(ysize * 3.65 / 24.0) });

        start_points.push(Point{ 0,(int)(center.y - side / 2.0) });
        start_points.push(Point{ (int)(xsize * 6.3 / 36), (int)(center.y - side / 2.0) });
        start_points.push(Point{ (int)(center.x - side / 2.0), (int)(center.y - side / 2.0) });
        start_points.push(Point{ (int)(xsize * 29.7 / 36.0 - side), (int)(center.y - side / 2.0) });
        start_points.push(Point{ xsize - (int)side, (int)(center.y - side / 2.0) });

        start_points.push(Point{ 0, (int)(ysize * 20.35 / 24.0 - side) });
        start_points.push(Point{ (int)(xsize * 6.3 / 36), (int)(ysize * 20.35 / 24.0 - side) });
        start_points.push(Point{ (int)(center.x - side / 2.0), (int)(ysize * 20.35 / 24.0 - side) });
        start_points.push(Point{ (int)(xsize * 29.7 / 36.0 - side), (int)(ysize * 20.35 / 24.0 - side) });
        start_points.push(Point{ xsize - (int)side, (int)(ysize * 20.35 / 24.0 - side) });

        start_points.push(Point{ 0, ysize - (int)side });
        start_points.push(Point{ (int)(xsize * 6.3 / 36), ysize - (int)side });
        start_points.push(Point{ (int)(center.x - side / 2.0), ysize - (int)side });
        start_points.push(Point{ (int)(xsize * 29.7 / 36.0 - side), ysize - (int)side });
        start_points.push(Point{ xsize - (int)side, ysize - (int)side });
    }
    else {
        start_points.push(Point{ 0, 0 });
        start_points.push(Point{ (int)(center.x - side / 2.0), 0});
        start_points.push(Point{ xsize - (int)side, 0});

        start_points.push(Point{ 0, (int)(center.y - side / 2.0)});
        start_points.push(Point{ (int)(center.x - side / 2.0), (int)(center.y - side / 2.0) });
        start_points.push(Point{ xsize - (int)side, (int)(center.y - side / 2.0) });

        start_points.push(Point{0, ysize - (int)side });
        start_points.push(Point{ (int)(center.x - side / 2.0), ysize - (int)side });
        start_points.push(Point{ xsize - (int)side, ysize - (int)side });
    }

    // 出力画像の描画
    for (int c = 0; c < (pattern == "full-size" ? 5 : 3) ; ++c) {
        for (int r = 0; r < (pattern == "full-size" ? 5 : 3); ++r) {
            _point start = start_points.front(); start_points.pop();
            if (drawable[c][r] == 1) {
                for (int j = 0; j < (int)side; ++j) {
                    for (int i = 0; i < (int)side; ++i) {
                        output.at<cv::Vec3w>(c * side + j, r * side + i) = data.at<cv::Vec3w>(start.y + j, start.x + i);
                    }
                }
            }
            else {
                for (int j = 0; j < (int)side; ++j) {
                    for (int i = 0; i < (int)side; ++i) {
                        output.at<cv::Vec3w>(c * side + j, r * side + i) = { 0xffff, 0xffff, 0xffff };
                    }
                }
            }
        }
    }

    cv::imwrite("output.tif", output);
    return 0;
}

あまり簡単なコードではなくなっちゃいましたが,基本的に

  • 設定ファイルを読み込む(切り出す正方形の一辺は?フルサイズ?APS-C?)
  • 画像を読み込む
  • 画像が縦長なら反時計回りに90度回して,横長にする
  • 切り出し開始点を計算
  • 描画して画像に出力
    というのをやっているだけです.
    設定ファイルは以下のような書式をとります.
135       // 焦点距離(0より大きな値なら特にプログラムに影響を与えません)
300       // 切り出す正方形の一辺の画素数
full-size // フルサイズかAPS-Cか?フルサイズなら以下に5×5,APS-Cなら3×3の行列を書きます.
1 0 1 0 1 // どの部分が描画されるか? 
0 1 1 1 0 // 一番外側四隅がフルサイズ,一つ内側の四隅がAPS-C、中央は中央の正方形
1 1 1 1 1 // 1 -> 描画する / 0 -> 描画されない
0 1 1 1 0
1 0 1 0 1

使い方

ビルドして,以下のコマンドで動きます.

$ corner_maker [tiffファイル名] [設定ファイル名]

結果

入力画像
入力画像
出力画像
出力画像
よしなに切り出せました.やったー.

リミテーション

  • 16bit tiffしか読み込めません.
  • 設定ファイルは簡単なエラーハンドリングしかしてません.変な入力を受けてクラッシュする可能性は大いにあります.
  • フルサイズとAPS-Cしか仮定できません.フルサイズは36mm×24mm,APS-Cは23.4mm×16.7mmを仮定しています.