せっかくのグリーンスクリーンなので、らしい遊びをしてみた (4)

まだちょっとだけ。

少なくとも紹介したこの検索の動画を実際に見てみた人は気付いたかもしれないが、たとえばこれ:

動画の最初と最後に余分なものが入っている。また、(3)では音声は background 側からしか取ってない。green screen 側動画から音声を取りたいこともある。

というわけで:

  1 #
  2 import sys
  3 import argparse
  4 import logging
  5 import json
  6 
  7 import numpy as np
  8 import av
  9 from PIL import Image
 10 
 11 if __name__ == '__main__':
 12     logging.basicConfig(stream=sys.stderr, level=logging.INFO)
 13     parser = argparse.ArgumentParser()
 14     parser.add_argument("background")
 15     parser.add_argument("greenback")
 16     parser.add_argument("outfile")
 17     parser.add_argument("--pick", type=str, default="[0, 0]")
 18     parser.add_argument("--atol", type=int, default=30)
 19     parser.add_argument("--bg-start", help="in secs", type=float, default=0)
 20     parser.add_argument("--gb-start", help="in secs", type=float, default=0)
 21     parser.add_argument("--gb-end", help="in secs", type=float, default=0)
 22     parser.add_argument("--audio-from-gb", action="store_true")
 23     parser.add_argument("--gb-base-alpha", type=int, default=0)
 24     args = parser.parse_args()
 25     pick = tuple(json.loads(args.pick))
 26     #
 27 
 28     icontbg = av.open(args.background)
 29     icontgb = av.open(args.greenback)
 30     ivstrbg = next(s for s in icontbg.streams if s.type == b'video')
 31     ivstrgb = next(s for s in icontgb.streams if s.type == b'video')
 32     iastrbg = next(s for s in icontbg.streams if s.type == b'audio')
 33     iastrgb = next(s for s in icontbg.streams if s.type == b'audio')
 34     ocont = av.open(args.outfile, "w")
 35     ovstr = ocont.add_stream(codec_name="h264", rate=ivstrbg.rate)
 36     oastr = ocont.add_stream(codec_name="aac", rate=iastrbg.rate)
 37     ovstr.width = ivstrbg.width
 38     ovstr.height = ivstrbg.height
 39     #
 40     if args.bg_start:
 41         bg_start_pts_v = int(args.bg_start / float(ivstrbg.time_base) + ivstrbg.start_time)
 42         bg_start_pts_a = int(args.bg_start / float(iastrbg.time_base) + iastrbg.start_time)
 43         ivstrbg.seek(bg_start_pts_v)
 44         iastrbg.seek(bg_start_pts_a)
 45     if args.gb_start:
 46         gb_start_pts_v = int(args.gb_start / float(ivstrgb.time_base) + ivstrgb.start_time)
 47         gb_start_pts_a = int(args.gb_start / float(iastrgb.time_base) + iastrgb.start_time)
 48         ivstrgb.seek(gb_start_pts_v)
 49         iastrgb.seek(gb_start_pts_a)
 50     if args.gb_end:
 51         gb_end_pts_v = int(args.gb_end / float(ivstrgb.time_base) + ivstrgb.start_time)
 52     else:
 53         gb_end_pts_v = 0
 54     #
 55 
 56     if not args.audio_from_gb:
 57         avbothpackets = icontbg.demux()
 58         vidonlypackets = icontgb.demux(ivstrgb)
 59     else:
 60         avbothpackets = icontgb.demux()
 61         vidonlypackets = icontbg.demux(ivstrbg)
 62     done = False
 63     while not done:
 64         try:
 65             avbothpacket = next(avbothpackets)
 66             if avbothpacket.stream.type == 'video':
 67                 if not args.audio_from_gb:
 68                     iframebgs = avbothpacket.decode()
 69                     iframegbs = next(vidonlypackets).decode()
 70                 else:
 71                     iframebgs = next(vidonlypackets).decode()
 72                     iframegbs = avbothpacket.decode()
 73                 for iframes in zip(iframebgs, iframegbs):
 74                     logging.info(iframes)
 75                     imgbg = iframes[0].to_image()
 76                     imggb = iframes[1].to_image().resize(imgbg.size)
 77                     keyclr = imggb.getpixel(pick)
 78                     r = np.array(imggb.getdata(0))
 79                     g = np.array(imggb.getdata(1))
 80                     b = np.array(imggb.getdata(2))
 81                     a = np.ones(r.shape) * args.gb_base_alpha
 82                     #
 83                     a[np.logical_and(
 84                             np.isclose(r, keyclr[0], atol=args.atol),
 85                             np.isclose(g, keyclr[1], atol=args.atol),
 86                             np.isclose(b, keyclr[2], atol=args.atol))] = 255
 87                     alpha = Image.new("L", imggb.size)
 88                     alpha.putdata(a.flatten())
 89                     mask = imggb.copy()
 90                     mask.putalpha(alpha)
 91         
 92                     dimg = Image.composite(imgbg, imggb, mask)
 93         
 94                     ofr = av.VideoFrame.from_image(dimg)
 95                     for p in ovstr.encode(ofr):
 96                         ocont.mux(p)
 97                     if gb_end_pts_v and iframes[1].pts >= gb_end_pts_v:
 98                         done = True
 99             else:
100                 for iframe in avbothpacket.decode():
101                     logging.info(iframe)
102                     iframe.pts = None
103                     for p in oastr.encode(iframe):
104                         ocont.mux(p)
105         except StopIteration:
106             break
107 
108     ocont.close()

シークで飛ばせるようにし、指定時間で打ち切れるようにし、音声をどちらから取るかを選べるようにしている。なおついでなので --gb-base-alpha も追加しているがこれは green screen 側の「抜かない」部分のアルファ値。ベースとして若干透かしたい場合もある。で、以下:

これとさっきのビール缶と合成したのがこれ:

ま…こういう単純なものはなんてことないんだけどね、わかってる人には自明なのかもしれんけれど、ずっと誤魔化してきたことがある。「期待のものと違う」ものについてはやっぱ言っといた方がいいよね。こういう処理の基礎についてあんまよくわかってないんで、相手がこういうのだとちょっとね:

実際にやってみればわかる。これは本当にやりたいことと違うだろう:

これは「カラーキー(?)」を完全透過にするだけの単純な処理のみではどうしようもなくて、多分残った「透けた緑」そのものを色加工しないといけないんだと思うんだけれど、素養がないのでどうすりゃいいのか良くわかんない。

この対応をしようと頑張るかどうかはわかんない。少なくともワタシ的に「本題」じゃないからね、このお遊び。所詮アタシの「エクササイズ」に過ぎない。悔しい気持ちはないではないので、気が向いて、対応出来たら書くかも。