ikarosの作業場

飛行機の設計もできる系のプログラマー。なおこの記事は個人的見解であり、所属する組織の意見とは一切関係がありません

puppeteerについて

使用した技術について

Google Japan社に勤務するPramook Khungurn氏が開発した「Talking Head Anime from a Single Image」を元にしています。
pkhungurn.github.io
pkhungurn.github.io
www.nicovideo.jp

「Talking Head Anime from a Single Image」はMITライセンス下にて公開されています。
github.com

また顔認識をするモジュールは KwanHua Lee氏の「head-pose-estimation」を用いています。
こちらも同様にMITライセンス下にて公開されています。
github.com


私はWindows10環境において開発環境が整っていないPCで扱えるようにGUIの改修および実行ファイル化を施しました。
こちらの元のソースファイルはこちらに公開しています。
github.com

動作環境

必須

推奨

FAQ

puppeteerが起動しない

原因として考えられるのは二つあります

- CUDAがインストールされていない
NVIDIAから最新のCUDAhttps://developer.nvidia.com/cuda-downloadsをインストールをしてください
developer.nvidia.com
https://developer.nvidia.com/cuda-downloads

  • VRAMが足りない

→ タスクマネージャーから「専用GPUメモリ使用量」を確認してみてください
f:id:ikarostech:20200928163627p:plain
puppeteerはVRAMを2GB以上消費するプログラムなのでその分のVRAM空きがないとプログラムの起動に失敗します
使用していないアプリ(ゲームなど)を閉じるか、グラフィックボードをVRAMの容量が大きいものに換装してください

動画中「2.画像を加工する」を参考に、画像サイズを256×256サイズに合わせてください

puppeteerがカクつく

グラフィックボードのリソースに余裕がない状態です。
使用していないアプリ(ゲームなど)を閉じるか、グラフィックボードをVRAMの容量が大きいものに換装してください。

にじんだ感じになる

f:id:ikarostech:20200928185430p:plain
このような症状が出た場合は、背景がきちんと透過されていないときに発生します。
動画中「2.画像を加工する」を参考に、キャラクターの部分以外が透過されている画像を作成してください

UWPのItemsSourceでGroupingをかけているときにCollectionをいじると子要素が反映されなくなる問題とその解決法

タイトルだけだとよくわからないと思いますので、まずはこの動画をご覧ください。

最初はグループ化されていて子要素を折りたたんで表示することができるDataGridでしたが、Addボタンをクリックして要素を追加すると、その中に入っている子要素が表示されなくなるという現象に悩まされていました。
ItemsSourceとCollectionViewSourceを用いたグルーピングについてはMicrosoft公式による記事が存在しています。
docs.microsoft.com
この記事を参考にViewModel.cs、MainView.xamlを下のように構成していました。

//ViewModel.cs
using Reactive.Bindings;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI.Xaml.Data;

namespace DataGridGrouping
{
    public class ViewModel
    {
        public CollectionViewSource CollectionView { get; set; } = new CollectionViewSource();
        public ReactiveCollection<User> Users { get; set; } = new ReactiveCollection<User>();
        public ViewModel()
        {
            // 初期データを作成
            Todo changePassword = new Todo();
            changePassword.Name.Value = "Change Password";
            changePassword.Content.Value = "change your password. init password is \"hack me\"";
            User user1 = new User();
            user1.Todos.Add(changePassword);
            User user2 = new User();
            user2.Todos.Add(changePassword);

            Users.Add(user1);
            Users.Add(user2);

            //CollectionViewSourceを作成
            CollectionView.IsSourceGrouped = true;
            CollectionView.Source = Users;
            CollectionView.ItemsPath = new Windows.UI.Xaml.PropertyPath("Todos");
        }
        public void AddUser()
        {
            Todo changePassword = new Todo();
            changePassword.Name.Value = "Change Password";
            changePassword.Content.Value = "change your password. init password is \"hack me\"";

            User user = new User();
            user.Todos.Add(changePassword);
            Users.Add(user);
        }
    }
}
<!-- MainView.xaml -->
<Page
    x:Class="DataGridGrouping.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:DataGridGrouping"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
    mc:Ignorable="d"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    <Grid>
        <Button Content="Add" Width="100" Height="50" Margin="450,225,0,0" VerticalAlignment="Top" HorizontalAlignment="Left" Click="{x:Bind viewModel.AddUser}" />
        <controls:DataGrid Width="800" Height="400" Margin="450,280,450,0" VerticalAlignment="Top" HorizontalAlignment="Left" ItemsSource="{x:Bind viewModel.CollectionView.View}" />
    </Grid>
</Page>

公式のとおりCollectionViewSourceを用いてGroupingをかけていましたが、このままではItemsSourceを更新した際に子要素が追加されないというわけです。


さて、ItemsSourceにBindingで追加したCollectionがどのようにそれぞれのUWP Controlに組み込まれるのかを考えてみます。

まずはGroupingがない場合、
f:id:ikarostech:20200608162039p:plain

次にGroupingがかかっている場合、
f:id:ikarostech:20200608162054p:plain

上の2図からわかるようにGroupingをかける場合にはSourceにIGroupingインターフェースを実装することが必要になります。
docs.microsoft.com

上述したMicrosoftの公式のデモではCollectionViewSourceにIsGroupedをtrueにすることで、
IGroupingインターフェースを実装した別のCollectionViewSourceを生成することになります。(内部でLINQのGroupBy句を使って新しいコレクションを作っているイメージです)

ただしIsGroupedをtrueにした実装ではCollectionが変更され通知された際に、Groupを新しく作ることがうまくいかないようです(ごめんなさい。ここら辺まだ検証できていません)

そこで、この現象を解消するためにあらかじめ自前でIGroupingを実装した要素を持つIEnumerableの実装クラスをItemsSourceに流し込むことで、CollectionViewSourceでのGroupingを避ける実装を行います。
f:id:ikarostech:20200608171218p:plain

/* GroupedCollection.cs */
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DataGridGrouping
{
    public class Grouping<TKey, TElement> : ObservableCollection<TElement>, IGrouping<TKey, TElement>
    {
        public Grouping(TKey key)
        {
            this.Key = key;
        }

        public Grouping(TKey key, IEnumerable<TElement> items)
            : this(key)
        {
            foreach (var item in items)
            {
                this.Add(item);
            }
        }

        public TKey Key { get; }
    }
    public class GroupedCollection<TKey,TElement> : ObservableCollection<Grouping<TKey,TElement>>
    {
        public void Add(TKey key,TElement element)
        {
            FindOrCreateGroup(key).Add(element);
        }
        private Grouping<TKey, TElement> FindOrCreateGroup(TKey key)
        {
            var match = this.Select((group, index) => new { group, index }).FirstOrDefault(i => i.group.Key.Equals(key));
            Grouping<TKey, TElement> result;
            if (match == null)
            {
                // Group doesn't exist and the new group needs to go at the end
                result = new Grouping<TKey, TElement>(key);
                this.Add(result);
            }
            else
            {
                result = match.group;
            }
            return result;
        }
        public bool Remove(TKey key, TElement element)
        {
            var group = this.FirstOrDefault(i => i.Key.Equals(key));
            var success = group != null && group.Remove(element);

            if (group != null && group.Count == 0)
            {
                Remove(group);
            }

            return success;
        }
    }
}
/* ViewModel.cs */
using Reactive.Bindings;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI.Xaml.Data;

namespace DataGridGrouping
{
    public class ViewModel
    {
        public CollectionViewSource CollectionView { get; set; } = new CollectionViewSource();
        public ReactiveCollection<User> Users { get; set; } = new ReactiveCollection<User>();
        public GroupedCollection<User, Todo> Todos { get; set; } = new GroupedCollection<User, Todo>();
        public ViewModel()
        {
            // 初期データを作成
            Todo changePassword = new Todo();
            changePassword.Name.Value = "Change Password";
            changePassword.Content.Value = "change your password. init password is \"hack me\"";
            User user1 = new User();
            user1.Todos.Add(changePassword);
            User user2 = new User();
            user2.Todos.Add(changePassword);

            Users.Add(user1);
            Users.Add(user2);
            Todos.Add(user1,changePassword);
            Todos.Add(user2,changePassword);

            //CollectionViewSourceを作成
            CollectionView.IsSourceGrouped = true;
            CollectionView.Source = Todos;
            
        }
        public void AddUser()
        {
            Todo changePassword = new Todo();
            changePassword.Name.Value = "Change Password";
            changePassword.Content.Value = "change your password. init password is \"hack me\"";

            User user = new User();
            user.Todos.Add(changePassword);
            Users.Add(user);
            Todos.Add(user,changePassword);
        }
    }
}
<!-- MainView.xaml -->
<Page
    x:Class="DataGridGrouping.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:DataGridGrouping"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
    mc:Ignorable="d"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    <Grid>
        <Button Content="Add" Width="100" Height="50" Margin="450,225,0,0" VerticalAlignment="Top" HorizontalAlignment="Left" Click="{x:Bind viewModel.AddUser}"/>
        <controls:DataGrid Width="800" Height="400" Margin="450,280,450,0" VerticalAlignment="Top" HorizontalAlignment="Left" ItemsSource="{x:Bind viewModel.CollectionView.View}" />
    </Grid>
</Page>

手動でGroupを管理するCollectionを作ってあげることで、Collectionに新しい要素が追加された際にも画面に反映されるようになりました。

このGroupedCollectionの実装については mikegoatlyさんのGroupedObservableCollectionを大分に参考にさせていただきました。
github.com

当サイトのソースコード及びその他の情報は個人・商用問わず自由に使っていただいてかかまいませんが、当サイトの情報が元で発生したいかなる結果・不利益については責任を負いかねますのでご了承ください