webエンジニア が
ニコニコ超会議 で
Vtuber を
サポートした話
@hey_cube
I am へいきゅーぶ
- @hey_cube
- オプトテクノロジーズ
- webエンジニア
- TypeScript + React
- Ruby on Rails
- ボカロクラスタ
今日話すこと
- 茨ひよりと一緒に写真が撮れるアプリを作った
- アプリの概要
- 利用技術
- ニコニコ超会議のブースの一部として出展した
- ブースの成果
茨ひよりとは
- 茨城県公認のVtuber
- いばキラTVのアナウンサー
- 愛称は「ひよりん」
- かわいい
ニコニコ超会議とは
- ニコニコ超会議実行委員会が主催するイベント
- 過去数年に渡り開催されている
- ニコニコ超会議2019ではひよりんもブースを構えた
アプリの概要
「ひよりんと一緒」とは
- 「ひよりんと一緒に茨城県を観光した時の記念撮影」っぽい画像を吐き出すwebアプリ
- やってることはシンプルな画像合成
- 合成するひよりんと茨城県の画像はランダム
デモ
開発の経緯
- 社内ハッカソン開催にあたり、エンジニア以外からもアイデアを募った
- ひよりんのサポートをしている部署から提案があった
- エンジニア数人がハッカソンでベースを作った
- マネージャー陣が調整した結果、業務として開発を継続できた
利用技術
ざっくり
- Ruby on Rails
- AWS
- サーバーサイドレンダリング
- 写真撮影はJavaScript
もうちょっと細かく
- フロントエンド
- Spectre.css
- WebcamJS
- バックエンド
- remove.bg
- MiniMagick
- CarrierWave / Fog::Aws
- RQRCode / ChunkyPNG
Spectre.css
- 軽量・レスポンシブ・モダンなCSSフレームワーク
- 欲しいUIコンポーネントが大体そろっている(個人の感想)
WebcamJS
- カメラからの情報をブラウザ上で良い感じにするライブラリ
- HTML5上で動作する
- 2017年2月以降アクティブな開発はしてないっぽい
WebcamJS
<div id="web-camera-container">
<div id="web-camera"></div>
</div>
#web-camera {
margin: auto;
}
#result-image {
margin-top: 20px;
}
#web-camera-container {
position: relative;
img {
transform: scale(-1, 1);
}
}
WebcamJS
// 初期設定
const params = new URLSearchParams(location.search);
const webCamera = document.getElementById('web-camera');
Webcam.set({
width: params.get('width') || 1200,
height: params.get('height') || 900,
image_format: 'png',
flip_horiz: true,
});
Webcam.attach('#web-camera');
// 写真の撮影
const takeSnapshotButton = document.getElementById('take-snapshot-button');
takeSnapshotButton.onclick = () => {
Webcam.snap((dataUri) => {
webCamera.innerHTML = '<img id="image" src="' + dataUri + '"/>';
const nextButton = document.getElementById('next-button');
nextButton.classList.remove('disabled');
nextButton.onclick = () => {
const blob = dataURItoBlob(dataUri);
const form = new FormData(document.forms[0]);
form.append("photo[image]", blob);
const request = new XMLHttpRequest();
request.onreadystatechange = () => {
if (request.readyState === XMLHttpRequest.DONE) {
const url = JSON.parse(request.responseText).url;
window.location = url;
}
}
request.open("POST", "/photos");
request.send(form);
}
});
}
remove.bg
- 人物を撮影した画像の背景を切り取ってくれるAPI
- グリーンバックじゃなくても大丈夫
- 無料でも使える
remove.bg
def remove
api_key = ENV['REMOVE_BG_KEY']
conn = Faraday.new('https://api.remove.bg', headers: {
"X-Api-Key" => api_key
}) do |f|
f.request :multipart
f.request :url_encoded
f.adapter :net_http
end
response = conn.post('/v1.0/removebg',
image_file: Faraday::UploadIO.new(current_path, 'image/png'),
size: 'auto'
)
if response.success?
File.binwrite(current_path, response.body)
else
puts "Error: #{response.status} #{response.body}"
end
end
MiniMagick
- 画像を加工してくれるgem
- 実体はImageMagickのRubyラッパー
MiniMagick
def compose
manipulate! do |person|
ibaraki = MiniMagick::Image.open("#{Rails.root}/public#{model.ibaraki_path}")
hiyorin = MiniMagick::Image.open("#{Rails.root}/public#{model.hiyorin_path}")
hiyorin.geometry "80%"
image = ibaraki.composite(hiyorin) do |c|
c.channel "RGBA"
c.compose "Over"
c.geometry "-200-0"
c.gravity "Southwest"
end
person.format "png"
image = image.composite(person) do |c|
c.compose "Over"
c.geometry "-0-0"
c.gravity "Southeast"
end
image
end
end
CarrierWave / Fog::Aws
- CarrierWave
- 画像などを指定した場所にアップロードしてくれるgem
- Fog::Aws
- CarrierWaveと併用することでS3に画像をアップロードできる
CarrierWave / Fog::Aws
class ImageUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
storage Rails.env.production? ? :fog : :file
def prefix
Rails.env.production? ? '' : 'uploads/'
end
def store_dir
"#{prefix}p/#{model.digest}"
end
def full_filename(for_file)
"original.png"
end
version :removed do
process :remove
def remove
# 人物画像の背景切り取り
end
def full_filename(for_file)
'removed.png'
end
end
version :composed, from_version: :removed do
process resize_to_fit: [1600, 900]
process :compose
def compose
# 画像合成
end
def full_filename(for_file)
"hiyorin.png"
end
end
end
RQRCode / ChunkyPNG
- RQRCode
- QRコードを生成してくれるgem
- ChunkyPNG
- pngファイルを読み書きするためのgem
- これを使うことで画像を保存せずに表示させることができる
RQRCode / ChunkyPNG
def gen_qrcode(text, options = {})
qr = RQRCode::QRCode.new(text, options)
return ChunkyPNG::Image.from_datastream(qr.as_png.resize(500,500).to_datastream).to_data_url
end
<div class="content">
<%= image_tag gen_qrcode @photo.image.composed.url %>
</div>
ブースの成果
ひよりんのブース全体
- 延べ1000人以上がコンテンツを体験したりツイートしたりしてくれました
- 会場の様子はハッシュタグ「#超・茨ひより」で確認できます
「ひよりんと一緒」
- 約150人が「ひよりんと一緒」を体験してくれました🎉
- 画像をツイートしてくれた人も
まとめ
感想とか
- 普段開発しているtoBのアプリとは毛色が違って面白かった
- 開催前日の超会議会場に潜り込めたのはかなりテンションが上がった
- ひよりんかわいい
かかったコストとか
- 10営業日ちょいでできた
- 約2人で制作
- 4時間/日・人くらい
- 普通のwebエンジニアが作った
僕が伝えられること
- xR関連の技術を直接使わなくてもできることはある
- プロダクトを立ち上げる時に考えることは色々ある
- 誰が、どれくらいの期間で、どんなツールを使って開発をするか
- 実現したいことは何か
おしまい
webエンジニアがニコニコ超会議でVtuberをサポートした話
By hey_cube
webエンジニアがニコニコ超会議でVtuberをサポートした話
茨城県公認 Vtuber の茨ひよりと一緒に写真を撮れるアプリケーションを作ったのでそれの解説 / https://vrtokyo.connpass.com/event/137007/
- 3,091