よしなに画像の四隅を切り出してみる

目次

  1. 1. あいさつ
  2. 2. モチベーション
  3. 3. プログラム
  4. 4. 使い方
  5. 5. 結果
  6. 6. リミテーション

あいさつ

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

モチベーション

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

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

プログラム

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
#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度回して,横長にする
  • 切り出し開始点を計算
  • 描画して画像に出力
    というのをやっているだけです.
    設定ファイルは以下のような書式をとります.
    1
    2
    3
    4
    5
    6
    7
    8
    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

使い方

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

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

結果

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

リミテーション

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