KirIn 落書き帳

素人がプログラミング, FPGA, LSIをお勉強しているメモ書きです。間違いがあればご指導していただけたら幸いです。

OpenCVで星のコンポジット合成

星空を比較明合成するプログラムを作ってみました。 まだ綺麗に出力はできていないですが、現状を整理するためにメモしておきます。

最終目標はこんな感じです。

f:id:KirIn:20141103232500j:plain

上画像はSiriusCompというフリーソフトを使用させていただき作ってみました。
設定も簡単で綺麗!!

こんな感じに綺麗に出力できるようになりたい!!

最近C++のお勉強もしているのでいつもはpythonから使っているOpenCVC++を使ってやってみます。 (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;
}

合成結果は以下のようになりました。

f:id:KirIn:20141103232636j:plain

ところどころ星の軌跡が切れてしまっていて残念なことになっています。
これをなんとかしたいところです。

次に覆い焼き(リニア)加算をすればノイズが消えて星の軌跡も見えるようになる?ということで
連続する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;
}

実行結果は以下となります。

f:id:KirIn:20141103232653j:plain

ムムッ!なんか汚い??考え方が根本的に違うのか??

こっからはいろいろ遊んで見た結果です。
星の軌跡がプツプツ切れてしまうなら、画像をぼかせばいい感じに見えるのでは?ということで元画像にガウシアンフィルターをかけながら比較明合成してみました。

#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;
}

実行結果はこちらになります。

f:id:KirIn:20141103232740j:plain

これが一番好きかも。ただやはりぼかしているので星の光が小さくなってしまっています。

ついでにメディアンフィルターでもやってみました。 実行結果はこちらになります。

f:id:KirIn:20141103232751j:plain