OpenCVで星のコンポジット合成
星空を比較明合成するプログラムを作ってみました。 まだ綺麗に出力はできていないですが、現状を整理するためにメモしておきます。
最終目標はこんな感じです。
上画像はSiriusCompというフリーソフトを使用させていただき作ってみました。
設定も簡単で綺麗!!
こんな感じに綺麗に出力できるようになりたい!!
最近C++のお勉強もしているのでいつもはpythonから使っているOpenCVをC++を使ってやってみます。 (C++ってどう書けばスマートなのかよくわからない...)
使用した画像の撮影環境はこちら
camera | D7000 |
焦点距離 | 8mm(35mm換算12mm) |
F値 | 4.5 |
SS | 25s |
インターバル | 5s |
枚数 | 40枚 |
まず手始めに、単純に連続で撮影した画像に対して2フレームの画素を比較してより明るい画素に順次置き換えるプログラムを作成しました。
*注) 適当ですがパラメータを割り振ったところ画素の差分が25以上ないときは書き換えないようにした時が一番きれいにみえたから...などのように適当にパラメータを割り振ってます。
#include <iostream> #include <sstream> #include <iomanip> #include <opencv2/core/core.hpp> #include <opencv2/opencv.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <opencv2/highgui/highgui.hpp> using namespace std; #define NUMBERIMAGE 40 //読み込む写真の数 #define THRESHOLD 25 string ImageFilePath(const string& dir, int num) { stringstream file_name; file_name << dir << setfill('0') << setw(4) << right << num << ".jpg"; return file_name.str(); } int main(int argc, char const* argv[]) { string file_dir = "./pano-DSC/"; cv::Mat compositeFrame = cv::imread("./pano-DSC/0001.jpg", 1); //書き換えに使うフレームを確保 for (int num = 1; num <= NUMBERIMAGE - 1; num++) { string nowFrame_name = ImageFilePath(file_dir, num); string nextFrame_name = ImageFilePath(file_dir, num + 1); cout << "number: " << num << endl; cv::Mat nowFrame = cv::imread(nowFrame_name, 1); cv::Mat nextFrame = cv::imread(nextFrame_name, 1); //比較明合成 for (int y = 0; y < compositeFrame.rows; y++) { for (int x = 0; x < compositeFrame.cols; x++) { cv::Vec3b &p_composite = compositeFrame.at<cv::Vec3b>(y,x); cv::Vec3b &p_now = nowFrame.at<cv::Vec3b>(y,x); cv::Vec3b &p_next = nextFrame.at<cv::Vec3b>(y,x); int average_now = (p_now[0] + p_now[1] + p_now[2]) / 3.0; int average_next = (p_next[0] + p_next[1] + p_next[2]) / 3.0; if (average_next - average_now > THRESHOLD) { p_composite[0] = p_next[0]; p_composite[1] = p_next[1]; p_composite[2] = p_next[2]; cout << "*"; } } } cout << endl; } cv::imwrite("composite.jpg", compositeFrame); return 0; }
合成結果は以下のようになりました。
ところどころ星の軌跡が切れてしまっていて残念なことになっています。
これをなんとかしたいところです。
次に覆い焼き(リニア)加算をすればノイズが消えて星の軌跡も見えるようになる?ということで
連続する2枚同士の画素を単純に加算した画像を用いて書き換えるように変更しました。
#include <iostream> #include <sstream> #include <iomanip> #include <opencv2/core/core.hpp> #include <opencv2/opencv.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <opencv2/highgui/highgui.hpp> using namespace std; #define NUMBERIMAGE 40 #define THRESHOLD 30 string ImageFilePath(const string& dir, int num) { stringstream file_name; file_name << dir << setfill('0') << setw(4) << right << num << ".jpg"; return file_name.str(); } void addLinearFrame(cv::Mat &frame1, cv::Mat &frame2) { for (int y = 0; y < frame1.rows; y++) { for (int x = 0; x < frame1.cols; x++) { cv::Vec3b &p_frame1 = frame1.at<cv::Vec3b>(y,x); cv::Vec3b &p_frame2 = frame2.at<cv::Vec3b>(y,x); p_frame1[0] = p_frame1[0] + p_frame2[0]; p_frame1[1] = p_frame1[1] + p_frame2[1]; p_frame1[2] = p_frame1[2] + p_frame2[2]; } } } int main(int argc, char const* argv[]) { string file_dir = "./pano-DSC/"; cv::Mat compositeFrame = cv::imread("./pano-DSC/0001.jpg", 1); for (int num = 1; num <= NUMBERIMAGE - 2; num++) { string oldFrame_name = ImageFilePath(file_dir, num); string nowFrame_name = ImageFilePath(file_dir, num + 1); string nextFrame_name = ImageFilePath(file_dir, num + 2); /* string writefile_name = ImageFilePath(write_dir, num); */ cout << "number: " << num << endl; cv::Mat oldFrame = cv::imread(oldFrame_name, 1); cv::Mat nowFrame = cv::imread(nowFrame_name, 1); cv::Mat nextFrame = cv::imread(nextFrame_name, 1); addLinearFrame(oldFrame, nowFrame); addLinearFrame(nowFrame, nextFrame); for (int y = 0; y < compositeFrame.rows; y++) { for (int x = 0; x < compositeFrame.cols; x++) { cv::Vec3b &p_composite = compositeFrame.at<cv::Vec3b>(y,x); cv::Vec3b &p_old = oldFrame.at<cv::Vec3b>(y,x); cv::Vec3b &p_now = nowFrame.at<cv::Vec3b>(y,x); int average_old = (p_old[0] + p_old[1] + p_old[2]) / 3.0; int average_now = (p_now[0] + p_now[1] + p_now[2]) / 3.0; if (average_now - average_old > THRESHOLD) { p_composite[0] = p_now[0]; p_composite[1] = p_now[1]; p_composite[2] = p_now[2]; cout << "*"; } } } cout << endl; } cv::imwrite("compositeLinear.jpg", compositeFrame); return 0; }
実行結果は以下となります。
ムムッ!なんか汚い??考え方が根本的に違うのか??
こっからはいろいろ遊んで見た結果です。
星の軌跡がプツプツ切れてしまうなら、画像をぼかせばいい感じに見えるのでは?ということで元画像にガウシアンフィルターをかけながら比較明合成してみました。
#include <iostream> #include <sstream> #include <iomanip> #include <opencv2/core/core.hpp> #include <opencv2/opencv.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <opencv2/highgui/highgui.hpp> using namespace std; #define NUMBERIMAGE 40 #define THRESHOLD 8 string ImageFilePath(const string& dir, int num) { stringstream file_name; file_name << dir << setfill('0') << setw(4) << right << num << ".jpg"; return file_name.str(); } int main(int argc, char const* argv[]) { string file_dir = "./pano-DSC/"; cv::Mat compositeFrame = cv::imread("./pano-DSC/0001.jpg", 1); for (int num = 1; num <= NUMBERIMAGE - 1; num++) { string nowFrame_name = ImageFilePath(file_dir, num); string nextFrame_name = ImageFilePath(file_dir, num + 1); cout << "number: " << num << endl; cv::Mat nowFrame = cv::imread(nowFrame_name, 1); cv::Mat nextFrame = cv::imread(nextFrame_name, 1); cv::Mat gau_nowFrame, gau_nextFrame; cv::GaussianBlur(nowFrame, gau_nowFrame, cv::Size(3, 3), 0, 0); cv::GaussianBlur(nextFrame, gau_nextFrame, cv::Size(3, 3), 0, 0); for (int y = 0; y < compositeFrame.rows; y++) { for (int x = 0; x < compositeFrame.cols; x++) { cv::Vec3b &p_composite = compositeFrame.at<cv::Vec3b>(y,x); cv::Vec3b &p_now = gau_nowFrame.at<cv::Vec3b>(y,x); cv::Vec3b &p_next = gau_nextFrame.at<cv::Vec3b>(y,x); int average_now = (p_now[0] + p_now[1] + p_now[2]) / 3.0; int average_next = (p_next[0] + p_next[1] + p_next[2]) / 3.0; if (average_next - average_now > THRESHOLD) { p_composite[0] = p_next[0]; p_composite[1] = p_next[1]; p_composite[2] = p_next[2]; cout << "*"; } } } cout << endl; } cv::imwrite("compositeGaussian.jpg", compositeFrame); return 0; }
実行結果はこちらになります。
これが一番好きかも。ただやはりぼかしているので星の光が小さくなってしまっています。
ついでにメディアンフィルターでもやってみました。 実行結果はこちらになります。