ユニファ開発者ブログ

ユニファ株式会社システム開発部メンバーによるブログです。

Swift 4 で UserDefaults を簡単に扱う

はじめに

iOS エンジニアのしだです。年始から喉が痛くずっとガラガラ声だったり左目が腫れたり2018年はなんだか嫌な予感がします。

最近、Kotlin を勉強しています。もう ことりん という響きだけでいとおしいく感じてます。 Kotlin 勉強しているときに以下の記事を見まして便利だなぁと思ったので、iOS の UserDefaults を同じように扱いたいなと思って書いてみました。

medium.com

今回は iOS の UserDefaults の小ネタです。

準備

  • Xcode 9.1 (Swift 4)
続きを読む

AWS Elemental MediaConvertあれこれ

みなさま、あけましておめでとうございます。
エンジニアの田渕です。

なんだか年末ギリギリまでバタバタしていたので、まったく年末感のないまま年末年始休暇に入ってしまいました。 そのせいか、今ひとつ年始感もありません……。年賀状もちゃんと出したし、お年玉もあげたりしたんですがね。。。 (ありますよね、そういうこと。)

さて、私のこれまでの記事では動画関連のネタを扱うことが多かったですが、今回も多分にもれず、動画関連です。 昨年開催されたAWS re:Invent 2017。この中ではたくさんの新サービスが発表されましたが、そこには新しいメディアサービスも含まれていました。

AWS re:Invent 2017 で発表された新サービスと機能 | AWS

今回は、そのうちの一つ、「AWS Elemental MediaConvert」のマイナー機能をご紹介します。

AWS Elemental MediaConvertとは

詳細については、公式の発表をご確認頂くのが一番わかりやすいかと思います。

AWS Elemental MediaConvert

これまでも、AWSには動画変換サービスとして「Amazon Elastic Transcoder」がありましたが、それよりも大規模かつ高解像度な動画の配信/メディアサポートを意識して作られているのがAWS Elemental MediaConvert。(と、私は解釈しています。) Elastic Transcoderでは基本的に「動画を変換するのみ」でしたが、Elemental MediaConvertにはそれよりも一歩進んだ機能が色々と搭載されています。

単純に動画の変換をするのみならば、公式ガイドの手順に従うだけで、かなりあっさりと動画の変換ができます。

docs.aws.amazon.com

毎度毎度、マイナーどころをついて恐縮ですが……。

メジャー(?)な使い方は、すでに他サイト等で情報がありますので、私が色々と触っている中で「へー」と思った機能をご紹介したいと思います。実際に触った方はおわかりかと思いますが、かなり機能が盛りだくさん!盛りだくさんすぎて、公式ドキュメント内でも事細かに全ては記載されておらず、メニューを触りながら各メニューのInfoを参照、それを参考にトライアンドエラーで発掘していく感じです。 (ちなみに、2018/1/4現在、Elemental MediaConvertの公式document等は英語のみの提供です。)

f:id:unifa_tech:20180104104213p:plain
Elemental MediaConvertのJOB設定画面

英語メニュー&ドキュメントと格闘しつつ、「これは今後使いたいかも?」と思った機能をご紹介します。

その1:動画の結合

地味ですが、意外に一番使いそうなのがこれ。複数の動画を結合した動画を作ることが出来ます。 やり方は至って簡単で、JOB作成時に指定するInputの動画を複数にするだけ。 あとは、通常の動画変換時と同じ設定で出来上がります。 更に言うと、その複数の動画の一部分だけを切り取って、結合させることも出来ます。

その2:別動画の音に差し替える

以下の公式ドキュメントにもある通り、今回のこの機能では、既存の動画の映像と音声を必要な部分だけ選んで新しい動画を作ることができます。 docs.aws.amazon.com この機能なのですが、音声の入力としてS3内にある別動画を指定することが出来るため、映像はこっちの動画、音声はこっちの動画、なんていう動画を作ることが出来ます。 動画の形式によっては、複数言語対応の動画を作成することも可能です。

その3:動画のノイズをちょっと消す

動画の出力設定内に「Noise reducer」というものがあり、そこでプルダウンから、あらかじめ用意されたフィルターを選ぶことで、動画のノイズをちょっとだけ消したり、逆に輪郭を強くしたりすることが出来ます。

使ってみましたが、気持ち変わったかな〜?というくらいの、自然な仕上がりでした。

まとめ

記事中に貼り付けたElemental MediaConvertのJOB設定画面をご覧頂くと分かる通り、とにかく設定出来る項目が多い!デフォルトのまま、必要な箇所だけを入力/選択していくことで通常の動画変換は行えますが、まだまだ細かく項目を見ていくと出来ることはたくさんありそうです。 また、実際に使った時に変換エラーになって初めて分かる仕様などもあり、よりよく使うにはとにかくトライしてみるのが良さそうです。 引き続き、面白い使い方が出来ないか、見ていきたいと思います。

では。

Amazon Rekognitionの顔検出が本当に良くなっていた

こんにちは、田中です。

AWSの開発者会議 re:Invent2017 で 画像認識サービスの Amazon Rekognition の顔検出の機能強化が発表されていました。

aws.amazon.com

機能強化の内容は

  • リアルタイム顔認識(何千万という顔のコレクションに対してリアルタイムの顔検索)
  • イメージ内のテキスト認識のサポート
  • 機能強化された顔検出(人混みモードの顔検出)

です。

このうち、ユニファの写真事業でもっとも関連の高い機能となる、「人混みモードの顔検出」の改善度合いを調べてみました。

人混みモードとは

以下の画像は上記のWebページからの画像引用になります

https://d2908q01vomqb2.cloudfront.net/f1f836cb4ea6efb2a0b1b99f41ad8b103eff4b59/2017/11/21/rekognition-face-detection-3.jpg https://aws.amazon.com/jp/blogs/news/amazon-rekognition-announces-real-time-face-recognition-support-for-recognition-of-text-in-image-and-improved-face-detection/

このように群衆が写っている写真からの顔検出になります。

集合写真は園イベントに欠かせないものであり、保護者のニーズも強く実際によく売れています。^^

で、集合写真での顔検出はどれくらい改善したの??

以前、ユーザー購入写真5000枚を対象にRekognitionを使って顔検出したことがありました。

そのとき、集合写真もしくは集合写真に準ずる写真のうち顔が1つも検出できなかった写真は 111 枚ありました。

これらの写真を対象にもう一度、顔検出してみました。

すると、

顔検出できなかった写真は なんと 10 枚 だけでした

以前は写真が少しでもボケていたりすると集合写真ではまったくと言っていいほど顔が検出できなかったのですが新しくなったRekognition では相当数が検出できるようになってました。

驚きあるのみです

動画からの顔検出(Amazon Rekognition Video)

画像認識機能の進化の目玉はこちらかもしれません。

クラスメソッドさんが早速記事を書いてくれています。

dev.classmethod.jp

私もコンソール画面から試してみました。

まず、サンプル動画で試す

動画中に検出された顔が左に表示され、選択した顔の出現区間がオレンジのバーで表示されます。 f:id:sanshonoki:20171220160350j:plain

出現しなかった区間はオレンジのバーからちゃんと外れています。 f:id:sanshonoki:20171220160042j:plain

うーん、すごいです。

オフィス内動画で試す!

続いてオフィス内で動画を撮影し試してみました。15-30秒の動画だと約3分ほどで分析が終了しました。

まず、一番驚いたのが、、

顔がまだ写ってない区間も出現区間として検出されている!

f:id:sanshonoki:20171222205914j:plain

これはすごい。

検出した顔を元に時間方向にオブジェクト追跡しているのでしょうか。

顔でないものが検出されていたり、明らかに顔なのにそれが検出されていなかったり、顔検出が微妙なところがあるのですが顔検出できた顔に関して出現区間はおおよそ納得がいく結果でした。

<顔検出がうまくできなかった例>

f:id:sanshonoki:20171222212944j:plain

最後に園内動画でも試した

最後に、園内動画をいくつかピックアップし試してみました。(園の許可なく公開できないのでキャプチャ画像はありません..)

オフィス内動画の結果に比べると残念な結果でした。。

  • 顔の数が多い
  • 子どもの顔は大人の顔より区別がつきにくい

ためでしょうか。

ただ、Rekognitionの登場からわずか1年で動画対応できたそのスピードを考えると来年にはどこまで進化しているのか期待を寄せずにはいられません。

デモでアップロードできる動画は以下の制限がありますがこの機能は面白いのでぜひ使ってみてください。 (自分の手持ちの動画を使うときはS3バケットを作成する必要があります)

  • movはだめ。h.264でエンコードされたmp4のみ
  • 動画は1分以内、ファイルサイズ30MB以下(仕様は8GBまでいける)

るくみーには動画撮影機能もあるので引き続きRekognitionをウォッチしていきたいと思います!

BLE Nano + ホールセンサー + Raspberry Pi でドアセンサーを作る

 こんにちは、ユニファの赤沼です。みなさんのオフィスではセキュリティはどうされてますでしょうか?ユニファではエントランスから会議室エリアもしくは執務室へ入る場合にはICカードによるドアロックを設置しています。ただ、会議室エリアと執務室を隔てる扉は、いずれのエリアもセキュリティ通過後であるため特に何もしていないのですが、開きっぱなしになっているとお客様が会議室エリアを通られる際に執務室の中も見えてしまうことになり、あまりよろしくありません。そこで今回はドアセンサーを自作して、ひとまずは開きっぱなしになっていることを検知できるようにしてみたいと思います。

構成

 全体の構成としては今回はひとまずシンプルに、インターネットへのアクセスは行わず、Raspberry Pi を Central、 ドアセンサーを Peripheral として BLE で接続し、一定時間以上ドアが開きっぱなしの状態だったら Raspberry Pi にログを出力してみます。

 ドアセンサーの構成としては、今回はホールセンサーを使用し、ドア側に貼り付けた磁石と壁側に設置したホールセンサーの距離が離れた場合をドアが開いているとみなします。ホールセンサーとは磁石や電流が発する磁界を電気信号に変えるセンサーで、下記サイトに詳しい解説が掲載されていました。

www.fujiele.co.jp

 今回使用したホールセンサーは下記製品で、ユニポーラのスイッチタイプでS極を近づけると出力がLowになります。

www.aitendo.com

 今回はBLEモジュール搭載の開発ボードとして BLE Nano を使用します。ホールセンサーをブレッドボードで BLE Nano と下記の図のように接続します。左上の黒い三本足のパーツがホールセンサーです。LED は動作確認用で、ドアが開いているときは赤く点灯させます。また、今回のホールセンサーは動作電圧が4.5V〜24Vで、 BLE Nano の出力の 3.3V では電圧が不足するため、単三電池4本を電源としてホールセンサーと BLE Nano に電力を供給します。

f:id:akanuma-hiroaki:20171219064313p:plain:w300

BLE Nano のファームウェア実装

 BLE Nano は mbed OS に対応しているので、今回は mbed でファームウェアを実装します。ただ mbed OS 5 には対応していないので、 mbed OS 2 での実装になります。開発環境の構築やプロジェクトの作成については今回は割愛させていただきます。

 まずホールセンサーの値を扱うための BLE のサービスを下記のように hall_service.h として実装します。今回はとりあえずの社内利用だけなので、 Characteristic としてはホールセンサーの値だけで、 UUID も適当にしてしまっています。

#ifndef __HALL_SERVICE_H__
#define __HALL_SERVICE_H__

class HallService {
public:
  const static uint16_t HALL_SERVICE_UUID = 0xA000;
  const static uint16_t HALL_VALUE_CHARACTERISTIC_UUID = 0xA001;

  HallService(BLE &_ble, uint16_t initialValueForHallCharacteristic) :
    ble(_ble), hallValue(HALL_VALUE_CHARACTERISTIC_UUID, &initialValueForHallCharacteristic, GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY)
  {
    GattCharacteristic *charTable[] = {&hallValue};
    GattService hallService(HALL_SERVICE_UUID, charTable, sizeof(charTable) / sizeof(GattCharacteristic *));
    ble.gattServer().addService(hallService);
  }

  void updateHallValue(unsigned short newValue) {
    ble.gattServer().write(hallValue.getValueHandle(), (uint8_t *)&newValue, sizeof(bool));
  }

private:
  BLE &ble;
  ReadOnlyGattCharacteristic<uint16_t> hallValue;
};

#endif

 そして main.cpp を下記のように実装します。

#include "mbed.h"
#include "ble/BLE.h"
#include "hall_service.h"

Serial pc(USBTX, USBRX);
DigitalOut led(P0_4, 0);
DigitalIn hallSensor(P0_5, PullUp);
BLE ble;
Ticker ticker;

const static char DEVICE_NAME[] = "DoorSensor";
static const uint16_t uuid16_list[] = {HallService::HALL_SERVICE_UUID};

static volatile bool triggerSensorPolling = false;

unsigned short hallValue;

HallService *hallServicePtr;

void disconnectionCallback(const Gap::DisconnectionCallbackParams_t *params)
{
  BLE::Instance().gap().startAdvertising();
}

void periodicCallback(void)
{
  if(hallValue != hallSensor.read()) {
    hallValue = hallSensor.read();
    led = hallValue;

    triggerSensorPolling = true;
  }
}

void onBleInitError(BLE &ble, ble_error_t error)
{
}

void bleInitComplete(BLE::InitializationCompleteCallbackContext *params)
{
  BLE& ble = params->ble;
  ble_error_t error = params->error;

  if (error != BLE_ERROR_NONE) {
    onBleInitError(ble, error);
    return;
  }

  if (ble.getInstanceID() != BLE::DEFAULT_INSTANCE) {
    return;
  }

  ble.gap().setDeviceName((const uint8_t *) DEVICE_NAME);
  ble.gap().onDisconnection(disconnectionCallback);

  uint16_t initialValueForHallCharacteristic = 0;
  hallServicePtr = new HallService(ble, initialValueForHallCharacteristic);

  ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
  ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, (uint8_t *)uuid16_list, sizeof(uuid16_list));
  ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *)DEVICE_NAME, sizeof(DEVICE_NAME));
  ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
  ble.gap().setAdvertisingInterval(1000);
  ble.gap().startAdvertising();
}


int main(void)
{
  pc.printf("Starting door_sensor...\r\n");

  ticker.attach(periodicCallback, 1);

  ble.init(bleInitComplete);

  while (ble.hasInitialized() == false) { /* spin loop */ }

  while(true) {
    if (triggerSensorPolling && ble.getGapState().connected) {
      triggerSensorPolling = false;
      pc.printf("%u\r\n", hallValue);
      hallServicePtr->updateHallValue(hallValue);
    } else {
      ble.waitForEvent();
    }
  }
}

 詳細な説明は割愛しますが、毎秒ホールセンサーの値を読み取り、値が変わっていたら BLE の Characteristic が持つ値を更新し、LEDの点灯状態も更新しています。ここまでの内容でビルドして BLE Nano に書き込むとひとまず Peripheral 側としては動作するようになり、下記動画のようにドアの開閉でLEDが点灯/消灯します。

Raspberry Pi 側のアプリ実装

 では次に Central 側になる Raspberry Pi のアプリケーション実装です。ユニファのサービスではサーバ側は Ruby で書かれているということもあり、Ruby で実装してみます。 まずは BLE デバイスをある程度汎用的に扱えるように以前に実装していた BLE クラスがあるのでこれを使用します。 BlueZ による BLE サービスへのアクセスをラップしたもので、BLE デバイスへアクセスするときに共通的に使用する処理を実装してあります。

require 'bundler/setup'
require 'dbus'

class BLE
  attr_reader :bus

  SERVICE_NAME = 'org.bluez'
  SERVICE_PATH = '/org/bluez'
  ADAPTER      = 'hci0'

  DEVICE_IF          = 'org.bluez.Device1'
  SERVICE_IF         = 'org.bluez.GattService1'
  CHARACTERISTIC_IF  = 'org.bluez.GattCharacteristic1'
  DBUS_PROPERTIES_IF = 'org.freedesktop.DBus.Properties'

  SERVICE_RESOLVED_PROPERTY = 'ServicesResolved'
  UUID_PROPERTY             = 'UUID'

  PROPERTIES_CHANGED_SIGNAL = 'PropertiesChanged'

  SERVICE_RESOLVE_CHECK_INTERVAL = 0.1
  DISCOVERY_WAITING_SECOND       = 20

  module UUID
    GENERIC_ATTRIBUTE_SERVICE  = '00001801-0000-1000-8000-00805f9b34fb'
    DEVICE_INFORMATION_SERVICE = '0000180a-0000-1000-8000-00805f9b34fb'
    BATTERY_SERVICE            = '0000180f-0000-1000-8000-00805f9b34fb'

    BATTERY_DATA = '00002a19-0000-1000-8000-00805f9b34fb'
  end

  class Device
    attr_reader :bluez, :name, :address

    def initialize(bluez, bluez_device, name, address, rssi = nil)
      @bluez        = bluez
      @bluez_device = bluez_device
      @name         = name
      @address      = address
      @rssi         = rssi
    end

    def connect
      @bluez_device.introspect
      @bluez_device.Connect
      @bluez_device.introspect

      while !properties[SERVICE_RESOLVED_PROPERTY] do
        sleep(SERVICE_RESOLVE_CHECK_INTERVAL)
      end

      @name = properties['Name']
    end

    def disconnect
      @bluez_device.Disconnect
    end

    def properties
      @bluez_device.introspect
      @bluez_device.GetAll(DEVICE_IF).first
    end

    def services
      services = []
      @bluez_device.subnodes.each do |node|
        service = @bluez.object("#{@bluez_device.path}/#{node}")
        service.introspect
        properties = service.GetAll(SERVICE_IF).first
        services << Service.new(@bluez, service, properties[UUID_PROPERTY])
      end

      services
    end

    def service_by_uuid(uuid)
      services.each do |service|
        return service if service.uuid == uuid
      end

      raise 'Service not found.'
    end

    def read_battery_level_once
      service = service_by_uuid(BLE::UUID::BATTERY_SERVICE)
      characteristic = service.characteristic_by_uuid(BLE::UUID::BATTERY_DATA)
      characteristic.read.first
    end

    def read_battery_level
      service = service_by_uuid(BLE::UUID::BATTERY_SERVICE)
      characteristic = service.characteristic_by_uuid(BLE::UUID::BATTERY_DATA)
      yield(characteristic.read.first)
      characteristic.start_notify do |v|
        yield(v.first)
      end
    end
  end

  class Service
    attr_reader :uuid

    def initialize(bluez, bluez_service, uuid)
      @bluez         = bluez
      @bluez_service = bluez_service
      @uuid          = uuid
    end

    def properties
      @bluez_service.introspect
      @bluez_service.GetAll(SERVICE_IF).first
    end

    def characteristics
      characteristics = []
      @bluez_service.subnodes.each do |node|
        characteristic = @bluez.object("#{@bluez_service.path}/#{node}")
        characteristic.introspect
        properties = characteristic.GetAll(CHARACTERISTIC_IF).first
        characteristics << Characteristic.new(characteristic, properties[UUID_PROPERTY])
      end

      characteristics
    end

    def characteristic_by_uuid(uuid)
      characteristics.each do |characteristic|
        return characteristic if characteristic.uuid == uuid
      end

      raise 'Characteristic not found.'
    end
  end

  class Characteristic
    attr_reader :uuid

    def initialize(bluez_characteristic, uuid)
      @bluez_characteristic = bluez_characteristic
      @uuid = uuid
    end

    def properties
      @bluez_characteristic.introspect
      @bluez_characteristic.GetAll(CHARACTERISTIC_IF).first
    end

    def start_notify
      @bluez_characteristic.StartNotify
      @bluez_characteristic.default_iface = DBUS_PROPERTIES_IF
      @bluez_characteristic.on_signal(PROPERTIES_CHANGED_SIGNAL) do |_, v|
        yield(v['Value'])
      end
    end

    def write(value)
      @bluez_characteristic.WriteValue(value, {})
    end

    def read
      @bluez_characteristic.ReadValue({}).first
    end

    def inspect
      @bluez_characteristic.inspect
    end
  end

  def initialize
    @bus = DBus::system_bus
    @bluez = @bus.service(SERVICE_NAME)

    @adapter = @bluez.object("#{SERVICE_PATH}/#{ADAPTER}")
    @adapter.introspect
  end

  def devices
    @adapter.StartDiscovery
    sleep(DISCOVERY_WAITING_SECOND)

    devices = []
    @adapter.introspect
    @adapter.subnodes.each do |node|
      device = @bluez.object("#{SERVICE_PATH}/#{ADAPTER}/#{node}")
      device.introspect

      next unless device.respond_to?(:GetAll)

      properties = device.GetAll(DEVICE_IF).first
      name    = properties['Name']
      address = properties['Address']
      rssi    = properties['RSSI']

      next if name.nil? || rssi.nil?

      devices << Device.new(@bluez, device, name, address, rssi)
    end

    @adapter.StopDiscovery
    devices
  end

  def device_by_name(name)
    devices.each do |device|
      return device if device.name.downcase.include?(name.downcase)
    end

    raise 'No devices found.'
  end

  def devices_by_name(name)
    devices.select do |device|
      device.name.downcase.include?(name.downcase)
    end
  end
end

 上記の BLE クラスを利用する形で、今回のドアセンサーを実現するための DoorSensor クラスを下記のように実装します。クラス設計や定数の持ち方などは正直かなり適当ですが、とりあえず動くので良しとします。

require 'bundler/setup'
require './ble.rb'

class DoorSensor
  attr_accessor :opend_at

  DEVICE_NAME = 'DoorSensor'

  module UUID
    DOOR_SENSOR_SERVICE = '0000a000-0000-1000-8000-00805f9b34fb'
    DOOR_SENSOR_VALUE   = '0000a001-0000-1000-8000-00805f9b34fb'
  end

  def self.find_all
    ble = BLE.new
    devices = ble.devices_by_name(DEVICE_NAME)
    devices.map do |device|
      device.services
      DoorSensor.new(ble, device)
    end
  end

  def initialize(ble, device)
    @ble    = ble
    @device = device
    @status = 0
    @opend_at = nil
  end

  def name
    @device.name
  end

  def address
    @device.address
  end

  def connect
    @device.connect
  end

  def read_door_sensor_value
    service = @device.service_by_uuid(DoorSensor::UUID::DOOR_SENSOR_SERVICE)
    characteristic = service.characteristic_by_uuid(DoorSensor::UUID::DOOR_SENSOR_VALUE)
    characteristic.start_notify do |door_sensor_value|
      yield(door_sensor_value.first)
    end
  end

  def run
    main = DBus::Main.new
    main << @ble.bus

    main.run
  end

  def monitor(log)
    Thread.new do
      begin
        loop do
          if !@opend_at.nil? && Time.now - @opend_at > 30
            log.warn('The door is still opening!')
          end
          sleep(5)
        end

      rescue => e
        log.error(e.backtrace.join("\n"))
      end
    end
  end

  def disconnect
    @device.disconnect
  end
end
if $0 == __FILE__
  log = Logger.new('logs/door_sensor.log')

  log.debug('Finding DoorSensor...')
  door_sensors = DoorSensor.find_all
  log.debug("#{door_sensors.size} DoorSensors found.")

  begin
    door_sensor = door_sensors.first
    log.debug("Connecting to #{door_sensor.name}: #{door_sensor.address}")
    door_sensor.connect
    log.debug("Connected. #{door_sensor.name}")

    door_sensor.read_door_sensor_value do |door_sensor_value|
      if door_sensor_value == 1
        door_sensor.opend_at = Time.now
      else
        door_sensor.opend_at = nil
      end
      @status = door_sensor_value
      log.info("#{door_sensor.address}: #{door_sensor_value}")
    end

    door_sensor.monitor(log)
    door_sensor.run
  rescue => e
    log.error(e.backtrace.join("\n"))
    puts e
  ensure
    door_sensor.disconnect
  end
end

 Peripheral(BLE Nano)側でセンサーの値が変わるとシグナルが送られてくるので、 read_door_sensor_value メソッド内でそれを待ち受け、ドアが開いたときにはその時間を記録しておきます。また別スレッドでは記録された時間を5秒ごとに監視し、30秒以上経っている場合にはログにWARNを出力します。これを $ bundle exec ruby door_sensor.rb & という感じで実行すると、下記のようにログが出力されていきます。

D, [2017-12-18T23:16:31.459778 #11409] DEBUG -- : Finding DoorSensor...
D, [2017-12-18T23:16:57.241822 #11409] DEBUG -- : 1 DoorSensors found.
D, [2017-12-18T23:16:57.242144 #11409] DEBUG -- : Connecting to DoorSensor: F4:CB:D1:35:5E:BA
D, [2017-12-18T23:16:59.618002 #11409] DEBUG -- : Connected. DoorSensor
I, [2017-12-18T23:20:05.662297 #11409]  INFO -- : F4:CB:D1:35:5E:BA: 1
I, [2017-12-18T23:20:11.669245 #11409]  INFO -- : F4:CB:D1:35:5E:BA: 0
I, [2017-12-18T23:20:17.676412 #11409]  INFO -- : F4:CB:D1:35:5E:BA: 1
I, [2017-12-18T23:20:27.666568 #11409]  INFO -- : F4:CB:D1:35:5E:BA: 0
I, [2017-12-18T23:20:31.649165 #11409]  INFO -- : F4:CB:D1:35:5E:BA: 1
W, [2017-12-18T23:21:04.905865 #11409]  WARN -- : The door is still opening!
W, [2017-12-18T23:21:09.906403 #11409]  WARN -- : The door is still opening!
W, [2017-12-18T23:21:14.906943 #11409]  WARN -- : The door is still opening!
W, [2017-12-18T23:21:19.907616 #11409]  WARN -- : The door is still opening!
I, [2017-12-18T23:21:22.679355 #11409]  INFO -- : F4:CB:D1:35:5E:BA: 0

まとめ

 今回はとりあえずログに出力しているだけですが、 Raspberry Pi が Wi-Fi に繋がっていれば色々なことができるので、 Slack へのメッセージ通知などをできるようにしてみたいと思います。あと、今は回路もブレッドボードにさしてるだけですが、抜けやすかったりで心配な面もあるので、ある程度確認できたら基盤に半田付けして実装してみたいと思います。

スマホで使えるPhotoShopで写真を加工

こんにちは。10月後半に入社しました山岸です。システム開発部でQA業務を担当しております。 今回はそんなQAのことでも開発のこともまったく関係のない写真の加工についてご紹介したいと思います。

今回紹介させていただくのは言わずとしれたPhotoShopです。

スマホのアプリは無料で使用できるって知っていますか??

この記事ではPhotoShopFixというPhotoShopCCの一部機能を使用できるアプリを紹介します。

www.adobe.com

iOS

Adobe Photoshop Fix

Adobe Photoshop Fix

  • Adobe
  • 写真/ビデオ
  • 無料
Android play.google.com

肝心の利用方法ですがAdobeのアカウントを作るだけで使用出来ます。 なにが出来るかというと・・・

能面がこうなります。
f:id:unifa_tech:20171207155838j:plainf:id:unifa_tech:20171207155844p:plain

◆このアプリでは顔のパーツ認識機能を使って部分ごとに顔を修正することが可能です。
まずこの下部の「ゆがみ」を選択します
f:id:unifa_tech:20171207160052p:plain

顔タブで部分が認証されていますので、それぞれ部位ごとに修正していきます。
f:id:unifa_tech:20171207160125p:plain

額の高さの調整、顔全体の大きさの調整、顔の幅の調整
f:id:unifa_tech:20171207160248p:plain

目の距離、サイズ、傾き、高さ、幅の調整
f:id:unifa_tech:20171207160322p:plain

鼻の幅、高さの調整
f:id:unifa_tech:20171207160519p:plain

顎の輪郭、顎の長さ、頬のくぼみの調整
f:id:unifa_tech:20171207160541p:plain

上唇の幅、下唇の幅、口角、唇の幅、高さの調整
f:id:unifa_tech:20171207160558p:plain


◆この調整で出来ない部分はワープツールを使用して調整していきます。
(小鼻の調整だったり、顎の形だったり、目尻/目頭の調整などなど)
f:id:unifa_tech:20171207160628p:plain

◆各部位の調整が終わったら最初の画面に戻って修復タブからスポット修正を行います。
今回のモデルの能面はキレイなので必要ありませんが、実際に人を修正する場合、
シミだったり、ほうれい線、ほくろなどはこれで消しちゃいます。
f:id:unifa_tech:20171207160651p:plain

◆修復タブの作業が終わったらスムーズタブからお肌を滑らかにしていきます。
女性の場合、ファンデーションが浮いていたりしたらだいたいこれでキレイに出来ます。
f:id:unifa_tech:20171207160711p:plain

◆明るさタブで明るさを調整していきます。
目のクマや前髪の影等々はこれで消します。(能面にはないのですが…)
f:id:unifa_tech:20171207160734p:plain

◆カラータブで彩度を部分的に調整していきます。
これはカラコンをしていたら目立つようにしたり、チークを強調させたりに使ってます。
f:id:unifa_tech:20171207160811p:plain

あとはペイントツールで少し手直しして完成です。
f:id:unifa_tech:20171207160830p:plain

以上になります。慣れるとだいたい10分かからずに出来るようになると思います。 今回は能面さんをモデルにしましたが、人の写真でやる場合がほとんどです。

ここまでご覧いただきありがとうございました。

【引用先】

能面「小姫」 #白竹創業祭 / norio_nomura

もしデータベースのトランザクションが使えなかったら

こんにちは、システム開発部のちょうです。

毎日バックエンドの開発に一番馴染みのあるものがデータベース、そしていろんな機能はトランザクションベースで開発されたのです。トランザクションはデータベース基本の機能で、トランザクションなしにはデータベースだと言えないぐらい重要です。でももしトランザクションが使えなくなったら、みんな開発できなくなるでしょうか。答えはNoです。実際、

  • トランザクションだけで対応できない
  • データベースにトランザクション機能がない
  • 分散システムでトランザクションが使えない

ようなケースは珍しくありません。

続きを読む

Perceptual Hashを使って画像の類似度を計算してみる

最近、引越しをしたWebエンジニアの本間です。 引越しの作業は大変面倒でしたが、新しい街に来た時のワクワク感がやっぱりいいなーと感じております。

さて、弊社のサービスである「写真サービス るくみー」では、毎日たくさんの写真をアップロードしていただいているのですが、中には内容がほとんど同じ写真が入ってしまうことがあります。 これらの写真がそのまま販売されてしまうと、写真を選ぶ際に邪魔になったり、間違って複数枚購入してしまうことがあるため、可能な限り避けたい事象です。 「同じ内容」の写真を自動で判別する方法がないか調査していたところ「Perceptual Hash」という手法を見つけました。 Pythonでの画像処理の勉強も兼ねて、今回この手法を紹介してみようと思います。

続きを読む