あいさつ
こんにちは.もう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を仮定しています.