2017年10月30日 | ブログ記事

nari.meb!!

yamada

これはtraP Advent Calendar2017 8日目の記事です。


<2017/10/30/13:27 一部表記を訂正しました>

おはようございます、yamadaです☆(ゝω・)vキャピ

今回はanime.rbについて書きます☆(ゝω・)vキャピ☆(ゝω・)vキャピ

1.anime.rbについて

anime.rbとは某講義で毎年出ているらしい[要出典]課題です。

絵を1と0だけで表現して出力してね♡という課題で、ひたすらツライです。

こんな感じ

加えて、これを動かしてアニメーションを作れというツライツライな課題です。

ということで今回はこれを倒します。

全体的に作った人がザコいですが許してください☆(ゝω・)vキャピ

2.環境

今回用いた環境は以下の通りです。

3.実装

まず画像を0と1に直します。

#include <opencv2/core.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc/imgproc.hpp> 
#include <opencv2/highgui.hpp>
#include <opencv2/video/background_segm.hpp>
#include <iostream>

// 01に変換
cv::Mat toBinary(const cv::Mat &amp;src, double thresh){
    cv::Mat ret;
    cv::cvtColor(src, ret, CV_RGB2GRAY);
    cv::threshold(ret, ret, thresh, 1, cv::THRESH_BINARY_INV);
    return ret;
}

// サイズ変換
cv::Mat animeResize(const cv::Mat &amp;src, int row, int col){
    int max_size = std::max(src.rows, src.cols);
    cv::Size_&lt;int&gt; size(col, row);
    cv::Mat ret;
    cv::resize(src, ret, size);
    return ret;
}

int main(){
    // 画像データをファイルから読み込む
    std::string filename;
    // 縦、横、閾値
    int row;
    int col;
    int thresh;
    std::cin &gt;&gt; filename &gt;&gt; row &gt;&gt; col &gt;&gt; thresh;
    cv::Mat src = cv::imread(filename.c_str(), cv::IMREAD_COLOR);

    // 画像の読み込みに失敗したらエラー終了する
    if(src.empty()){
        std::cerr &lt;&lt; "sippai sita yo" &lt;&lt; std::endl;
        return -1; 
    }

    src = animeResize(toBinary(src, thresh), row, col);

    std::cout &lt;&lt; cv::format(src, cv::Formatter::FMT_CSV) &lt;&lt; std::endl;

    return 0;
}

OpenCVを使って0と1に変換します。OpenCVは神。

toBinaryで0と1に変換します。

通常、画像は赤、緑、青の三色で表されています。

赤、緑、青の濃さが0から255くらいの範囲で表されていることが多いです。透過度が設定されている場合もあります。

cv::cvtColorは色空間を変更してくれる関数です。赤、緑、青のそれぞれの色を別の色で置き換えます。

第三引数で変更後の色空間を指定します。今回は白黒に変更しました。

cv::thresholdはある数値(このコード内ではthreshold)を基準に、各ピクセルの値において、値がそれ以上のもの、もしくはそれ以下のものについて何らかの操作をします。

今回は値がthresholdより大きいものを0,そうでないものを1に変換しています。

animeResize関数は、わざわざ関数にするほどのものでもありませんが、画像の大きさを変更しています。

これもOpenCVがいい感じにやってくれます。

出力では、cv::formatterを用いて変換後の画像をcsvで吐き出しています(ここもっとうまく出来る気がする)。
こうすることで画像を0と1のcsv形式で出力できました。

#include<bits/stdc++.h>

int main(){
	std::string s;
	while(std::getline(std::cin, s)){
		for(char c : s){
			if(c != ' ' &amp;&amp; c != ','){
				std::cout &lt;&lt; c;
			}
		}
		std::cout &lt;&lt; std::endl;
	}
	return 0;
}

わざわざ載せる程のコードでもないですがcsvで吐き出されたファイルのカンマとスペースを除去します(すごく無駄なことをしている気がしますが僕にはうまい方法がわかりませんでした)。

縦幅50,横幅100,閾値100にしたところ以下の画像が冒頭に貼ったようになりました。

続いて動画を複数枚の画像に分割します。ffmpegというすごくすごいものがあったのでそれを使ったら一瞬でできました。

以下のサイトを参考にしました。

https://qiita.com/hirorock/items/2c500c2d46981d8087d9

これで画像がたくさん出来たので、あとはこれを0と1に変換してrubyのコードを生成すれば完了です。

そのためにシェルスクリプトで書こうと思いましたが僕がシェルスクリプトを書いたことがないため大変なことになりました。

一応動くものができたので下に載せておきますが多分もっとスマートなやり方があると思います。

sort.oはファイル名をソートするためのものです。適当にlsしてみたら順番がぐちゃぐちゃだったので(それはそう)作りました。(これもなんかうまい方法がある気がする)

一応そのソースコードも載せておきます。

#include<iostream>
#include<string>
#include<vector>
#include<algorithm>

int main(){
	std::vector&lt;std::string&gt; sortFileName;
	while(std::cin){
		std::string s;
		std::cin &gt;&gt; s;
		sortFileName.push_back(s);
	}
	std::sort(sortFileName.begin(), sortFileName.end());
	for(std::string s : sortFileName){
		std::cout &lt;&lt; s &lt;&lt; std::endl;
	}
	return 0;
}

続いてシェルスクリプトの方です。以下のサイトを参考にしました

https://qiita.com/saitamasaitama/items/3ffc15bd0de154a1fb30

#!/bin/bash
#$1: 縦幅
#$1: 横幅
#$3: 閾値
#$4: 元画像が入ってるディレクトリ

cp sort.o ./$4
cp toBinary.o ./$4
cp format.o ./$4

cd $4

# 画像リスト
ls 	*.png | ./sort.o > originFileList.txt

# 配列に入れる
originFileList=(`cat originFileList.txt|xargs`)

# 画像の数-1
end=`expr ${#originFileList[@]} - 1`

mkdir temp

# 各画像を0と1に変換
for i in $(seq 0 ${end}); do
	if [[ ${originFileList[$i]} =~ ([0-9a-zA-Z]+)\.png ]]; then
		echo ${originFileList[$i]} &gt; tempShell.txt
		echo $1 >> tempShell.txt
		echo $2 >> tempShell.txt
		echo $3 >> tempShell.txt
		./toBinary.o < tempShell.txt | ./format.o > ./temp/result00$i.txt
	fi
done


#rubyのコードを吐き出す
echo -n "anime = "
echo "["

binaryFileList=(`ls ./temp/* | ./sort.o |xargs`)

for file in ${binaryFileList[@]}; do
	echo -n "\""
	cat  ./$file
	echo "\","
done

echo "\"\n\""
echo "]"
echo "i = 0"

#"四則演算を用いること"が課題の条件なのでwhile文を用いてiをインクリメントする
echo "while i < anime.length"
echo "	puts anime[i]"
echo "	i = i + 1"
echo "	sleep(0.1)"
echo "end"

#rm ./temp -rf
#rm tempShell.txt sort.o toBinary.o format.o

cd ./../

やっていることは大したことないです。

ファイルのリストをソートして、それぞれ01になおして、最後にrubyのソースコードを生成しています。

ということでなんとか完成しました。

実際に生成されたコードとその動きを掲載するべきですが、いい感じの動画が手元になかったので許してください☆(ゝω・)vキャピ

良くない感じの動画で試しに生成したところコードは10000行くらいでした。

4.まとめ

こんなことせずに普通に書いたほうが楽な気がしました。

シェルスクリプトを書くのがとてもつらかったです。

明日はYosotsuさんとGabさんの記事です。お楽しみに。

この記事を書いた人
yamada

この記事をシェア

このエントリーをはてなブックマークに追加

関連する記事

2017年12月27日
Splatoon2~ボムの使い方~
shigurure
2017年12月26日
RustでMCMC(Metropolis-Hasting)
David
2017年12月26日
NinjaFlickerが完成しました
gotoh
2017年12月25日
Project Obelisk [traP Advent Calendar 2017]
nari
2017年12月25日
tobiom
2017年12月25日
ネタがないので迷路を自動生成してみた
tsukatomo

活動の紹介

カテゴリ

タグ