にせねこメモ

はてなダイアリーがUTF-8じゃないので移ってきました。

デジカメで撮った文書をきれいに印刷する

文書をコピーしたいけれどコピー機が手元にない時など、デジカメで文書を撮影しておくということがある。
ただ、今度それを印刷しようと思った場合、そのまま印刷してもコピー機でコピーを取ったときの様にきれいに印刷することは難しい。

ここでは、デジカメで撮った白黒文書をPhotoshopできれいに加工して印刷することを目指す。
これにより、あまり大きく開いてスキャンのできない本などのコピーにも応用が可能である。

概略

  1. 傾きを補正する
  2. ハイパスフィルタをかける
  3. 2諧調化する

普通にコントラスト調整などを行うだけだと、光が均一に当たっていない場合など、字が掠れたり紙ごと黒く潰れてしまう場合が多い。

これを回避するために、ハイパスフィルタを用いる。ハイパスフィルタとは、その名の通り高周波成分を残すフィルタで、画像でいうと変化が激しいところ、つまりエッジ部分を抽出する。これにより、画像自体の色の濃淡ではなく、色の濃淡の変化を検出することで、ライティングが均一でなくても黒部分をきれいに抽出することが可能になる。

制限

  • 白黒はっきり分かれた画像にしか使用できない(グレースケールやカラーは無理)
  • 撮影時にボケたものはあまりきれいにはならない

手順

元画像がこれ。スマホのカメラで撮影した。デスクライトをつけて撮影している。紙のサイズはB5。
f:id:nixeneko:20170405004954p:plain

等倍だとこう。
f:id:nixeneko:20170405005213p:plain

文書の傾きを補正する

遠近法の切り抜きツールを選択
f:id:nixeneko:20170405010128p:plain

文書の4隅に合うように切り抜きの四角形を調整し、右上の「○」ボタンを押して決定する。
f:id:nixeneko:20170405010648p:plain

結果。印刷がずれてるので水平が傾いてる……
f:id:nixeneko:20170405010824p:plain

さすがに傾き過ぎなので切り抜きツールでちょちょいと傾きを直した。
f:id:nixeneko:20170405011207p:plain

ハイパスフィルタをかける

「フィルター」→「その他」→「ハイパス」を選択。この時画像を等倍程度で文字を表示しておくとフィルターの適用結果がみやすい。
f:id:nixeneko:20170405011917p:plain

今回は線の幅が3~4px程度だったので4pxと指定してみる(7px位にした方が線の掠れが小さくなるかもしれない。要試行錯誤)。小さいより大きめの方が良いが、あまり大きすぎるとフィルターをかける意味がなくなるような気がする。OKを押す。
f:id:nixeneko:20170405012255p:plain

するとハイパスフィルタが適用された状態になる。

二値化

次は二値化を行う。「イメージ」→「色調補正」→「2階調化」を選択
f:id:nixeneko:20170405012459p:plain

しきい値の設定を、ゴマ状のノイズが目立たなくなるまで小さくし(ここでは116にした)、OKを押す。しきい値を小さくすると文字が削れていくので適当な値に設定する。
f:id:nixeneko:20170405012812p:plain


結果、等倍だとこんな感じになる。
f:id:nixeneko:20170405013009p:plain

それを再度印刷したものを元文書と比較するとこのようになる。
f:id:nixeneko:20170405014653p:plain
「書」「暑」などの線が混んでいる文字は掠れているが、読めないことはない。もとがスマホの写真からということであるから、そこそこ実用性はあるのではないかと思う。

備考

  • 暗い環境で撮ったものであるとか、撮影時にブレがあると綺麗に出ない。その場合は元画像の諧調を残したままの方がいいだろうと思う。
  • 一眼レフなどのちゃんとしたカメラを使って、ちゃんと明るくライトを当てることで、大きく開いてスキャンのできない本などのコピーにも応用が可能である。
  • 実際にコピー機でコピーする際にも、ハイパスフィルタをかけたような諧調がでていることがあるため、内部処理でハイパスフィルタ相当の処理を行っているのではないかと思う。
  • ハイパスフィルタが使えない場合は、元画像から元画像にガウスぼかしをかけたものを引き算すると、ハイパスフィルタ相当になる。ガウスぼかしが高周波成分を除去するローパスフィルタだから納得という感じもある。

SVGでフレームアニメーション w/ Javascript

前回の記事でSMILによるフレームアニメーションを実装したが、これだとIEやEdgeで動かないので、Javascriptを用いてアニメーションを実装しIEやEdgeでも動くようにした。

デモ

<img>タグによる読み込み

<img src="animate_js.svg" style="width:100%">

imgタグで埋め込むとJavascriptによるアニメーションは動かないようだ(Firefox, Chromeで確認)。

セキュリティ上の目的で、GekoはSVGコンテンツを画像として扱う場合にいくつかの制限を設けています。

SVG as an Image - SVG | MDN

<object>タグによる読み込み

<object type="image/svg+xml" data="animate_js.svg" width="100%"></object>

objectタグで埋め込むとアニメーションは動作するようだが、一方で単純に<a>タグで囲むだけなどではリンクを張ることができないので、工夫が必要である。
参考: SVGをobject要素で表示してリンクにする - ういはるかぜの化学 - subtech

ソース

<svg xmlns="http://www.w3.org/2000/svg" 
     xmlns:xlink="http://www.w3.org/1999/xlink"
     width="768" height="432" viewBox="0 0 764 432">
 <def>
  <image width="100%" height="100%" id="00" xlink:href="img/00.jpg"/>
  <image width="100%" height="100%" id="01" xlink:href="img/01.jpg"/>
  <image width="100%" height="100%" id="02" xlink:href="img/02.jpg"/>
  <image width="100%" height="100%" id="03" xlink:href="img/03.jpg"/>
  <image width="100%" height="100%" id="04" xlink:href="img/04.jpg"/>
  <image width="100%" height="100%" id="05" xlink:href="img/05.jpg"/>
  <image width="100%" height="100%" id="06" xlink:href="img/06.jpg"/>
  <image width="100%" height="100%" id="07" xlink:href="img/07.jpg"/>
  <image width="100%" height="100%" id="08" xlink:href="img/08.jpg"/>
  <image width="100%" height="100%" id="09" xlink:href="img/09.jpg"/>
  <image width="100%" height="100%" id="10" xlink:href="img/10.jpg"/>
  <image width="100%" height="100%" id="11" xlink:href="img/11.jpg"/>
  <image width="100%" height="100%" id="12" xlink:href="img/12.jpg"/>
  <image width="100%" height="100%" id="13" xlink:href="img/13.jpg"/>
  <image width="100%" height="100%" id="14" xlink:href="img/14.jpg"/>
  <image width="100%" height="100%" id="15" xlink:href="img/15.jpg"/>
 </def>
 <use id="frame" xlink:href="#00"/>
 <script type="text/javascript"><![CDATA[
  (function(){
    var c = document.getElementById("frame");
    var fcnt = 0;
    setInterval(function(){
        fcnt = (fcnt + 1) % 16;
        fid = fcnt < 10 ? "#0"+fcnt : "#"+fcnt;
        c.setAttribute("xlink:href", fid);
    },125)})();
 ]]></script>
</svg>

ここでは<image>要素でxlink:hrefにファイル名を指定しているが、代わりにdata URI schemeで画像ファイルを埋め込んで外部参照をなくした方が汎用性が上がると思う。

何をやっているか

  1. <def>内で<image>によって各フレームに対応するラスタ画像を読み込んでいて、連番のidを振ってある。
  2. <use>は他の要素をコピーして表示するというもので、初期フレームをxlink:hrefに指定している。このxlink:hrefをJavascriptで更新する。
  3. 次に<script>でアニメーションをするJavascriptを定義している。setIntervalにより125ms = 1/8s (… 8fps)ごとに実行される関数の中で、setAttributeを用いて先ほどの<use>要素のxlink:hrefを次のフレームのidになるように書き換えていく。

以上、結構シンプルになったと思う。

改良可能な点

  • imageのidを2桁で0パディングしているけれども、しなくてもいいかもしれない。各フレームに表示するidの計算が楽になる。
  • また、2桁なので泥臭く0パディングしているが、0パディングするにはslice()メソッドを使うと楽らしい。桁数が多くなってくるとこっちの方がいいかも。

まとめ

SVGでフレームアニメーション?

  1. SVGにはラスタ画像を埋め込める
  2. SVGではアニメーションが可能

なら、GIFアニメみたいなアニメーションも可能では?と思ってやってみた。

作成したもの

https://nixeneko.sakura.ne.jp/hatenablog/20170330-svganim/animate.svg
アニメーションは使いまわし: 【けものフレンズ】「ネコ科のフレンズ」イラスト/にせねこ [pixiv]


はてなブログsvgタグをコピペしようとしたら、文字数が多すぎると怒られた(data URI schemeを利用して200万文字を超えていた)。

ソース

<svg xmlns="http://www.w3.org/2000/svg" 
     xmlns:xlink="http://www.w3.org/1999/xlink"
     width="768" height="432" viewBox="0 0 764 432">
  <image width="768" height="432" xlink:href="img/00015.jpg">
    <animate 
      attributeName="opacity"
      values="0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;1" dur="2s" calcMode="discrete"
      begin="0s" repeatCount="indefinite" />
  </image>
  <image width="768" height="432" xlink:href="img/00014.jpg">
    <animate 
      attributeName="opacity"
      values="0;0;0;0;0;0;0;0;0;0;0;0;0;0;1;0" dur="2s" calcMode="discrete"
      begin="0s" repeatCount="indefinite" />
  </image>
  <image width="768" height="432" xlink:href="img/00013.jpg">
    <animate 
      attributeName="opacity"
      values="0;0;0;0;0;0;0;0;0;0;0;0;0;1;0;0" dur="2s" calcMode="discrete"
      begin="0s" repeatCount="indefinite" />
  </image>
  <image width="768" height="432" xlink:href="img/00012.jpg">
    <animate 
      attributeName="opacity"
      values="0;0;0;0;0;0;0;0;0;0;0;0;1;0;0;0" dur="2s" calcMode="discrete"
      begin="0s" repeatCount="indefinite" />
  </image>
  <image width="768" height="432" xlink:href="img/00011.jpg">
    <animate 
      attributeName="opacity"
      values="0;0;0;0;0;0;0;0;0;0;0;1;0;0;0;0" dur="2s" calcMode="discrete"
      begin="0s" repeatCount="indefinite" />
  </image>
  <image width="768" height="432" xlink:href="img/00010.jpg">
    <animate 
      attributeName="opacity"
      values="0;0;0;0;0;0;0;0;0;0;1;0;0;0;0;0" dur="2s" calcMode="discrete"
      begin="0s" repeatCount="indefinite" />
  </image>
  <image width="768" height="432" xlink:href="img/00009.jpg">
    <animate 
      attributeName="opacity"
      values="0;0;0;0;0;0;0;0;0;1;0;0;0;0;0;0" dur="2s" calcMode="discrete"
      begin="0s" repeatCount="indefinite" />
  </image>
  <image width="768" height="432" xlink:href="img/00008.jpg">
    <animate 
      attributeName="opacity"
      values="0;0;0;0;0;0;0;0;1;0;0;0;0;0;0;0" dur="2s" calcMode="discrete"
      begin="0s" repeatCount="indefinite" />
  </image>
  <image width="768" height="432" xlink:href="img/00007.jpg">
    <animate 
      attributeName="opacity"
      values="0;0;0;0;0;0;0;1;0;0;0;0;0;0;0;0" dur="2s" calcMode="discrete"
      begin="0s" repeatCount="indefinite" />
  </image>
  <image width="768" height="432" xlink:href="img/00006.jpg">
    <animate 
      attributeName="opacity"
      values="0;0;0;0;0;0;1;0;0;0;0;0;0;0;0;0" dur="2s" calcMode="discrete"
      begin="0s" repeatCount="indefinite" />
  </image>
  <image width="768" height="432" xlink:href="img/00005.jpg">
    <animate 
      attributeName="opacity"
      values="0;0;0;0;0;1;0;0;0;0;0;0;0;0;0;0" dur="2s" calcMode="discrete"
      begin="0s" repeatCount="indefinite" />
  </image>
  <image width="768" height="432" xlink:href="img/00004.jpg">
    <animate 
      attributeName="opacity"
      values="0;0;0;0;1;0;0;0;0;0;0;0;0;0;0;0" dur="2s" calcMode="discrete"
      begin="0s" repeatCount="indefinite" />
  </image>
  <image width="768" height="432" xlink:href="img/00003.jpg">
    <animate 
      attributeName="opacity"
      values="0;0;0;1;0;0;0;0;0;0;0;0;0;0;0;0" dur="2s" calcMode="discrete"
      begin="0s" repeatCount="indefinite" />
  </image>
  <image width="768" height="432" xlink:href="img/00002.jpg">
    <animate 
      attributeName="opacity"
      values="0;0;1;0;0;0;0;0;0;0;0;0;0;0;0;0" dur="2s" calcMode="discrete"
      begin="0s" repeatCount="indefinite" />
  </image>
  <image width="768" height="432" xlink:href="img/00001.jpg">
    <animate 
      attributeName="opacity"
      values="0;1;0;0;0;0;0;0;0;0;0;0;0;0;0;0" dur="2s" calcMode="discrete"
      begin="0s" repeatCount="indefinite" />
  </image>
  <image width="768" height="432" xlink:href="img/00000.jpg">
    <animate 
      attributeName="opacity"
      values="1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0" dur="2s" calcMode="discrete"
      begin="0s" repeatCount="indefinite" />
  </image>
</svg>

もちろん<image>要素のxlink:href属性にdata URI schemeを突っ込めば画像ファイルを埋め込むことができる。

なお、HTMLから<img>タグなどで画像としてSVGを読み込む場合には、セキュリティ上の理由から外部参照が無視されるため、xlink:hrefにはdata URI schemeによって画像を埋め込まないといけない。(参考: SVG as an Image - SVG | MDN)

何をやってるのか?

<image>要素に1フレームの画像を読み込んでいる。これをアニメーションの全フレーム分用意する。
今回は8fpsで16フレームなので、全体の時間は2秒となる。
一つのフレームに注目して見ていく。

  <image width="768" height="432" xlink:href="img/00000.jpg">
    <animate
      attributeName="opacity"
      values="1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0" dur="2s" calcMode="discrete"
      begin="0s" repeatCount="indefinite" />
  </image>

これは16枚中の1番目に表示されるフレームに対応している。

  • まず、アニメーションさせる対象として不透明度をattributeName="opacity"として指定している。
  • アニメーションの時間は2秒なのでdur="2s"と指定している。
  • valuesは、durで指定された時間を等分し、attributeNameで指定された対象の値とする。今回は16フレーム中の1番目のフレームということで、16個の数値がセミコロンで区切られ、1番目のみ1、その他が0になっている("1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0")。1は表示、0が非表示に対応する。
  • さらに、calcMode="discrete"を指定することで透明度が中間値にならずvaluesで指定した1か0、つまり表示・非表示の切り替えになる。
  • begin="0s"ですぐに再生されるようにしておき、repeatCount="indefinite"で何度もループされるようにしておく。

フレームごとに異なるのは、valuesの値で、1がどの位置に来るかということである。2番目に表示されるフレームなら"0;1;0;0;0;0;0;0;0;0;0;0;0;0;0;0"、などなど。
後は同じ。

感想

  • できるだけシンプルにしようとしたらこうなった。
    • ほかにもっと良い方法があるかもしれない。
  • この実装では各画像を切り替えているだけであるため、<image>タグの順番は関係ない。
  • GIFでいいじゃん!

20170331追記

同様のことを考えた先人が居た。

XNGファイルはSVGファイルに、データURIスキームでBase64エンコードされたJPEG画像をならべ、それをSVGアニメーションで送っているだけということがわかりました。

ついにGIFに代わる新しいアニメーション画像フォーマットが誕生か - GIGAZINE

これも同様にSVG SMIL animationを利用しているが、アニメーションの実現の方法が異なる。
フレームの若い順に幅0の<image>要素を重ねておき、<set>で各<image>要素について、前フレームのアニメーション終了時に開始するようにbegin属性を設定し(e.g. begin="A000310.end")、33msでwidthを100%にしているという実装だった。

SMILにはInternet ExplolerやEdgeは対応していない(し、する予定もなさそう)ため、画像は表示されないこととなる。
最初の一フレームは常に表示される設定にしておいた方が、アニメーションに対応していないビューアで見ても何らかの画像が表示されるため、よいかもしれない。

SVGでアニメーションをつくる利点?

時間軸方向の圧縮をしないため(Motion JPEGみたいな…)圧縮効率が悪いが、

  • 複数要素を独立してアニメーションできる
  • エフェクト(フィルタ)がかけられる
  • クリッピングができる

など、SVGの機能をそのまま生かせるので、表現力は高いはず。簡単なアニメーション撮影もできそうである(ブラウザが対応してるか知らないが)。

20170331さらに追記

内の「画像の参照先を変える」にて、画像をつかってパラパラアニメーションを作る方法が紹介されている。

これでは、<def>内で画像を読み込み、<use>内で<animate>のvalues属性にidの参照をセミコロン;で区切って指定している。これが一番シンプルで分かりやすいかもしれない。

Chinachu録画番組一気見プレイリスト生成ブックマークレット

Chinachuで録画番組をストリーミングで連続再生したかったので、そのためのXSPFプレイリストを生成する簡易的なブックマークレットを作った。

使い方

  1. ブラウザからWUIにアクセスする。
  2. 「録画済」番組一覧を表示し、「録画番組検索」などを行い、観たい番組を一覧表示させる。(同一ページ内に表示されている番組のみが再生対象となる)
  3. 下記ブックマークレットを実行
  4. ダウンロードされるxspfファイルをVLCなどで開く

ソース

ブックマーク用リンクは下にある。

コンテナはM2TS、コーデックは映像・音声ともに無変換とした。これらパラメータは無名関数の引数として指定している。この辺りは、普通に番組をストリーミングする時に使いたい設定にしてダウンロードしたxspfファイルやAPIを参考に変更するとよいと思われる。

(function(prm){
  t='<?xml version="1.0" encoding="UTF-8"?><playlist version="1" xmlns="http://xspf.org/ns/0/"><trackList>';
  d=document.getElementsByClassName("id");
  for(i=0;i<d.length;i++){
    b=d[i].textContent.substr(1);
    s="";
    for(e of Array.from(d[i].parentNode.childNodes)){
      if(e.nodeName=="#text"){
        s+=e.nodeValue
      }else if(e.getAttribute("class")=="episode"){
        s+=e.textContent
      }
    }
    t+='<track><location>http://'+window.location.host+'/api/recorded/'+b
      +'/watch.'+prm+'&amp;prefix=http%3A%2F%2F'
      +window.location.host+'%2Fapi%2Frecorded%2F'+b+'%2F</location><title>'+s+'</title></track>';
  }
  t+='</trackList></playlist>';
  e=document.createEvent("MouseEvents");
  e.initMouseEvent("click",true,f=false,window,0,0,0,0,0,f,f,f,f,0,null);
  a=document.createElementNS("http://www.w3.org/1999/xhtml","a");
  a.href=(window.URL||window.webkitURL).createObjectURL(new Blob([t],{"type":"application/xspf+xml"}));
  a.download="playlist.xspf";
  a.dispatchEvent(e);
})('m2ts?ext=m2ts&c:v=copy&c:a=copy'.replace(/&/g,"&amp;"));

ブックマーク用リンク

Photoshop CS6で動画からアニメーションGIF作成

この記事では、 Photoshop CS6 を使って動画からアニメーションGIFを作成する方法を説明します。
f:id:nixeneko:20170327231806g:plain

こんな感じのものを出力します。


動画ファイルが比較的手軽に扱える時代になりましたが、HTML上などで画像感覚で手軽に動画を扱えるアニメーションGIFはまだまだ便利な技術です。
動画からアニメーションGIFを生成するのは、 Photoshop を使って簡単に行うことができます。ここではCS6を使いました。

手順

動画ファイルを用意

まず動画ファイルを用意します。Photoshopから読み込める形式になっている必要があります。
ここではAfterEffectsでレンダリングした圧縮なしのAVIファイルを用意しました。

動画を開く

ファイル(F) -> 読み込み(M) -> ビデオフレームからレイヤー(F) を選択します。
f:id:nixeneko:20170327224731p:plain

用意したAVIファイルを選択し、開くを選択します。
f:id:nixeneko:20170327224952p:plain

「ビデオをレイヤーに読み込む」ダイアログが開くので、「フレームアニメーションを作成」がチェックされていることを確認して今回はそのままOKを押します。
ここで読み込む範囲を指定することもできます。
f:id:nixeneko:20170327225321p:plain

すると、ビデオのフレームがレイヤーとして読み込まれ、タイムラインにフレームが表示されます。
f:id:nixeneko:20170327225759p:plain

この状態ですでにフレームの表示時間の指定がなされているため、表示時間を変更する必要がなければすぐに出力できます。

アニメーションGIFの作成

ファイル(F) -> Web用に保存... を選択します。
f:id:nixeneko:20170327230040p:plain

出力設定

するとダイアログが開くので、各種設定をします。
f:id:nixeneko:20170327230840p:plain

  • 形式に「GIF」を指定
  • 「カラー」で色数の設定(256が最大)
  • 今回の動画では透明部分を含まないので「透明部分」のチェックを外す
  • 「画像サイズ」で出力画像サイズの設定
  • アニメーションの「ループオプション」で、無限ループにするか、ループをしないかを設定可能

今回はその他はデフォルトのままにしてあります。
プレビューの下部に出力ファイルサイズが表示されるのでそれも参考に設定を変えていきます。
ちなみにTwitterにアップできるGIFのファイルサイズの制限は15MBまでだそうです(2017年3月27日現在)。

最後に「保存...」をクリックし、保存ファイル名を指定して保存します。

sshでログインした先で、ログアウトしてもコマンドの実行が継続されるようにする: screenコマンド

sshでログインしている先のLinuxで、終了まで長い時間がかかるDNNの学習などの処理を実行し、後はログアウトして終了を待つ、ということがしたかったので、どうすればいいのか調べたメモ。


バックグラウンド実行しただけではログアウトすると終了してしまう。
nohupを使用しても、ログアウト時に終了されてしまう。

screenコマンド

どうやらscreenコマンドを使用するといいらしい。
screenは仮想的な端末を作成することができるコマンドで、作成した仮想端末は必要に応じて付け替えて使え、さらにログアウトしても保持されるらしい。
詳しい使い方は以下のサイトを参照。
qiita.com

asayamakk.hatenablog.com

suでユーザを切り替えた場合

ただし、screenコマンドはsuでユーザを切り替えている場合、ターミナルが開けないとエラーを吐く。
ログインしたユーザに対し擬似ターミナルが作成されているため、ユーザを切り替えた先ではターミナルが無く、scriptコマンドを実行して擬似ターミナルを無理やり作成しないといけないとのこと。


あるいは、suによってユーザを切り替えるのではなく、初めからそのユーザでログインすればこの問題は起こらない。

sshで接続した先でmatplotlibなどがエラーを吐く

qiita.com

ssh接続した先でpythonからmatplotlibを扱うと$DISPLAY環境変数がないよって怒られる

_tkinter.TclError: no display name and no $DISPLAY environment variable

なので、

env DISPLAY=:0 python hoge.py

みたいにして実行するといいらしい。

現在時刻のglobal date and time stringを生成する

HTML5では、時間を指定するための<time>要素があり、また<del>要素などではdatetime属性によって時間を指定できる。これは編集履歴を残すのに都合がよい。

ここに指定するのは、有効なglobal date and time stringでなければならない。

例えば、

2017-03-15T20:53:45+09:00

とすると、2017年3月15日の20時53分45秒、タイムゾーン+09:00(=日本時間)ということになる。


ときどき現在時刻がこの形で欲しくなるので、このdatetimeに指定するための文字列を自動生成するようなものを作っておく:

UTC版:
タイムゾーン版: