ユニファ開発者ブログ

ユニファ株式会社プロダクトデベロップメント本部メンバーによるブログです。

SupersetをECS(Docker)で導入してみる

おはこんばんちはユニファのインフラのすずきです。

今肩こりが悪化しすぎて右腕が痛くなって接骨院通っています。 右腕の痛みは治ったのですが肩のほうが痛くないのが異常だと言われ緩めるために引き続き通い続けています。 みなさんも普段の姿勢とか肩を動かす運動とかして慢性的な肩こりにならないようにしていただけたらと思います。

あとiPhoneX買いましたanimojiキモいですね!

さて本題、弊社DBの情報をグラフなどで可視化したり、一覧で取得したりなどするためにRedashを利用しています。 特定のサービスでしか利用していなかったので、先日他のサービスでも利用できるように共通の環境に移設しました(ついでにDocker化)。
ただ、Redash自体は他の候補を検討せず導入したものであり、実現したいことも増えてきたのでこれを期にRedash以外を検討してみようということになりました。
そこで、最近良く目にするようになってきたSupersetがどんなものかお試しで利用してみようとなりました。 丁度RedashがDockerで動いてるのでSupersetもDockerで動かしてみようとおもったので、その内容をブログにしてみました。

続きを読む

SORACOM Inventory によるデバイス管理(Limited Preview お試し)

 こんにちは、ユニファの赤沼です。

 今年の7月に SORACOM Inventory が発表され、 Limited Preview の受付が開始されました。ユニファでもすぐに申し込んでいたのですが、先日利用案内をいただくことができたので早速試してみました。案内をいただくまでに4か月ほどかかっていますが、それだけ申し込みが多く注目度が高いということなのかと思います。

soracom.jp

SORACOM Inventory とは

f:id:akanuma-hiroaki:20171110091212p:plain:right:w150

 詳細については公式サイトをご覧いただければと思いますが、一言で言うとデバイス管理サービスです。 SORACOM Air により回線に接続したデバイス上で LwM2M に対応したエージェントを動作させることで、デバイスの自動登録やデバイスの管理が可能になります。

 Limited Preview 中は無料で利用することができますが、本サービス利用時には下記サイトにあるような料金が発生します。

soracom.jp

SIMグループの設定

 SORACOM Inventory を Limited Preview で使うには、利用する SIM が所属するSIMグループを、あらかじめ用意された Limited Preview 専用の VPG(Virtual Private Gateway) に API を使って紐づける必要があります。SORACOM の API は Reference ページから実際に API にリクエストを投げられるようになっていますので、下記のようにSIMグループの設定変更の API にリクエストを投げます。下記画像ではグループIDと VPGID はマスクしていますが、実際には対象のグループIDと SORACOM から案内のあった VPGID を指定します。

f:id:akanuma-hiroaki:20171109211634p:plain

エージェントのインストール

 次に各デバイスで動作させるエージェントをインストールします。 Limited Preview ではエージェント実装のサンプルとして、 Eclipse Wkaama ベースの C エージェント、 Java エージェント、 Android エージェントが配布されます。今回はまず Raspberry Pi 上で C エージェントを動かしてみます。

 まずはエージェントのビルドに必要な cmake をインストールします。

$ sudo apt-get install cmake

 次に Eclipse Wakaama を clone し、 submodule をセットアップします。

pi@raspberrypi:~/tmp $ git clone https://github.com/eclipse/wakaama
Cloning into 'wakaama'...
remote: Counting objects: 5924, done.
remote: Total 5924 (delta 0), reused 0 (delta 0), pack-reused 5924
Receiving objects: 100% (5924/5924), 2.13 MiB | 444.00 KiB/s, done.
Resolving deltas: 100% (4194/4194), done.
Checking connectivity... done.
pi@raspberrypi:~/tmp $ 
pi@raspberrypi:~/tmp $ cd wakaama/
pi@raspberrypi:~/tmp/wakaama $ 
pi@raspberrypi:~/tmp/wakaama $ git submodule init
Submodule 'platforms/Linux/tinydtls' (https://git.eclipse.org/r/tinydtls/org.eclipse.tinydtls) registered for path 'examples/shared/tinydtls'
pi@raspberrypi:~/tmp/wakaama $ 
pi@raspberrypi:~/tmp/wakaama $ git submodule update
Cloning into 'examples/shared/tinydtls'...
remote: Total 343 (delta 0), reused 343 (delta 0)
Receiving objects: 100% (343/343), 443.92 KiB | 211.00 KiB/s, done.
Resolving deltas: 100% (110/110), done.
Checking connectivity... done.
Submodule path 'examples/shared/tinydtls': checked out '0016138fe3998552eee3987a1c09da43a23c9fb5'

 続いて、 wakaama に SORACOM Inventory 用のパッチを適用します。 Limited Preview の案内と同時に配布されたパッチの圧縮ファイルを適当なディレクトリに展開してビルドします。(出力は省略)

pi@raspberrypi:~/tmp/wakaama $ tar zxf wakaama_patch.tgz
pi@raspberrypi:~/tmp/wakaama $ cd ../
pi@raspberrypi:~/tmp $ mkdir build
pi@raspberrypi:~/tmp $ cd build/
pi@raspberrypi:~/tmp/build $ cmake -DDTLS=1 ../wakaama/examples/client
pi@raspberrypi:~/tmp/build $ make

デバイスの登録

 それではエージェントを起動してデバイス情報を登録してみたいと思います。 SORACOM Air でネットワークに接続した上で、下記のようにエージェントを起動します。 State が STATE_READY になれば成功です。

pi@raspberrypi:~/tmp/build $ ./lwm2mclient -b -n raspberry_pi -h bootstrap.soracom.io
Using MAC address b827ebb3dcad as endpoint name
Trying to bind LWM2M Client to port 56830
LWM2M Client "raspberry_pi" started on port 56830
>  -> State: STATE_BOOTSTRAPPING
 -> State: STATE_BOOTSTRAPPING
 -> State: STATE_BOOTSTRAPPING
 -> State: STATE_BOOTSTRAPPING
 -> State: STATE_BOOTSTRAPPING
 -> State: STATE_BOOTSTRAPPING
 -> State: STATE_BOOTSTRAPPING
 -> State: STATE_BOOTSTRAPPING
 -> State: STATE_REGISTERING
 -> State: STATE_REGISTERING
 -> State: STATE_REGISTERING
decrypt_verify(): found 24 bytes cleartext
 -> State: STATE_REGISTERING
decrypt_verify(): found 22 bytes cleartext
 -> State: STATE_READY
decrypt_verify(): found 19 bytes cleartext
 -> State: STATE_READY
decrypt_verify(): found 19 bytes cleartext
 -> State: STATE_READY
decrypt_verify(): found 19 bytes cleartext
 -> State: STATE_READY
decrypt_verify(): found 19 bytes cleartext
 -> State: STATE_READY
decrypt_verify(): found 19 bytes cleartext
 -> State: STATE_READY
decrypt_verify(): found 19 bytes cleartext
 -> State: STATE_READY

データ確認

 エージェントから登録されたデバイス情報は SORACOM コンソールから確認できます。

f:id:akanuma-hiroaki:20171110064638p:plain

f:id:akanuma-hiroaki:20171110063814p:plain

 上記は一部ですが、他にも LwM2M の仕様に沿って多くの項目が表示されていて、バッテリーレベル、通信強度、緯度、経度などの項目が確認できます。ただし、それぞれの情報が取得できるか、どんな内容が取得できているかはエージェントの実装に依存します。 Limited Preview の案内で配布されているエージェントは検証用のサンプルなので、実際に本番サービスに使用する場合は自前で使用するデバイスに対応したエージェントを実装する必要があります。

Java エージェントによる登録・確認

 Java版のエージェントでも動作を確認してみます。手元の Raspberry Pi には Java がインストールされていなかったので、まずは Java をインストールします。

pi@raspberrypi:~ $ sudo apt-get install oracle-java8-jdk

 Limited Preview ではビルド済みの Java エージェントが配布されていますので、解凍すれば実行できます。

pi@raspberrypi:~/tmp $ unzip soracom-inventory-agent-example-0.0.1.zip 

 SORACOM Air でネットワークに接続した上で、下記のようにエージェントを実行します。

pi@raspberrypi:~/tmp $ cd soracom-inventory-agent-example-0.0.1/bin/
pi@raspberrypi:~/tmp/soracom-inventory-agent-example-0.0.1/bin $ ./soracom-inventory-agent-example-start -e raspberry_pi
2017-11-06 23:21:36,348 INFO SORACOMInventoryAgentExample - using endpoint [raspberry_pi]
2017-11-06 23:21:37,757 INFO InventoryAgentInitializer - set bootstrap security object.
2017-11-06 23:21:37,771 INFO InventoryAgentInitializer - set instance to initializer.objectId:3 instance num:1
2017-11-06 23:21:37,772 INFO InventoryAgentInitializer - set instance to initializer.objectId:14 instance num:1
2017-11-06 23:21:39,148 INFO LeshanClient - Starting Leshan client ...
Nov 06, 2017 11:21:39 PM org.eclipse.californium.core.CoapServer start
INFO: Starting server
Nov 06, 2017 11:21:39 PM org.eclipse.californium.core.network.CoapEndpoint start
INFO: Starting endpoint at coaps://0.0.0.0:0
Nov 06, 2017 11:21:39 PM org.eclipse.californium.scandium.DTLSConnector start
INFO: DTLS connector listening on [0.0.0.0/0.0.0.0:48720] with MTU [1,280] using (inbound) datagram buffer size [16,474 bytes]
Nov 06, 2017 11:21:39 PM org.eclipse.californium.core.network.CoapEndpoint start
INFO: Started endpoint at coaps://0.0.0.0:48720
Nov 06, 2017 11:21:39 PM org.eclipse.californium.core.network.CoapEndpoint start
INFO: Starting endpoint at coap://0.0.0.0:0
Nov 06, 2017 11:21:39 PM org.eclipse.californium.core.network.CoapEndpoint start
INFO: Started endpoint at coap://0.0.0.0:50733
2017-11-06 23:21:39,319 INFO LeshanClient - Leshan client started [endpoint:raspberry_pi].
2017-11-06 23:21:39,353 INFO RegistrationEngine - Trying to start bootstrap session to coap://100.127.127.100:5683 ...
2017-11-06 23:21:39,834 INFO RegistrationEngine - Bootstrap started
2017-11-06 23:21:40,242 INFO RegistrationEngine - Bootstrap finished ServersInfo [bootstrap=Bootstrap Server [uri=coaps://beam.soracom.io:5684], deviceMangements={123=DM Server [uri=coaps://jp.inventory.soracom.io:5684, lifetime=60, binding=U]}].
2017-11-06 23:21:40,244 INFO BootstrapObserver - Bootstrap success: coap://100.127.127.100:5683
2017-11-06 23:21:40,247 INFO BootstrapObserver - Bootstrap server: Bootstrap Server [uri=coaps://beam.soracom.io:5684], Device management servers: {123=DM Server [uri=coaps://jp.inventory.soracom.io:5684, lifetime=60, binding=U]}
2017-11-06 23:21:40,287 INFO FileCredentialStore - save credential to .soracom-inventory-credentials.dat
2017-11-06 23:21:40,291 INFO RegistrationEngine - Trying to register to coaps://jp.inventory.soracom.io:5684 ...
2017-11-06 23:21:42,966 INFO RegistrationEngine - Next registration update in 54.0s...
2017-11-06 23:21:42,969 INFO RegistrationEngine - Registered with location '/rd/lWFi3sMHIp'.
Nov 06, 2017 11:21:43 PM org.eclipse.californium.core.network.config.NetworkConfig store
INFO: writing properties to file /home/pi/tmp/soracom-inventory-agent-example-0.0.1/bin/Californium.properties
2017-11-06 23:21:43,378 INFO ObservableInventoryObjectEnabler - observe start. observeStartDelayMillis:10000 observeInternalMillis:60000
2017-11-06 23:21:43,378 INFO ObservableInventoryObjectEnabler - observe start. observeStartDelayMillis:10000 observeInternalMillis:60000
Nov 06, 2017 11:21:43 PM org.eclipse.californium.core.CoapResource addObserveRelation
INFO: Successfully established observe relation between jp.inventory.soracom.io/52.198.95.62:5684#7b184646f5c054fb and resource /3
Nov 06, 2017 11:21:43 PM org.eclipse.californium.core.CoapResource addObserveRelation
INFO: Successfully established observe relation between jp.inventory.soracom.io/52.198.95.62:5684#48f72e0076f29416 and resource /3
Nov 06, 2017 11:21:43 PM org.eclipse.californium.core.CoapResource addObserveRelation
INFO: Successfully established observe relation between jp.inventory.soracom.io/52.198.95.62:5684#086126447a85b86a and resource /1
Nov 06, 2017 11:21:43 PM org.eclipse.californium.core.CoapResource addObserveRelation
INFO: Successfully established observe relation between jp.inventory.soracom.io/52.198.95.62:5684#97c1ac3d635941e2 and resource /3
Nov 06, 2017 11:21:43 PM org.eclipse.californium.core.server.ServerMessageDeliverer deliverRequest
INFO: Did not find resource [6, 0, 5] requested by jp.inventory.soracom.io/52.198.95.62:5,684
2017-11-06 23:21:53,376 INFO ObservableInventoryObjectEnabler - fire resource change for observation.
2017-11-06 23:21:53,377 INFO ObservableInventoryObjectEnabler - fire resource change for observation.
2017-11-06 23:22:36,970 INFO RegistrationEngine - Trying to update registration to coaps://jp.inventory.soracom.io:5684 ...
2017-11-06 23:22:37,896 INFO RegistrationEngine - Next registration update in 54.0s...
2017-11-06 23:22:37,897 INFO RegistrationEngine - Registration update succeed.
2017-11-06 23:22:53,376 INFO ObservableInventoryObjectEnabler - fire resource change for observation.
2017-11-06 23:22:53,377 INFO ObservableInventoryObjectEnabler - fire resource change for observation.

 登録された情報をコンソールから確認してみます。

f:id:akanuma-hiroaki:20171110065601p:plain

 Endpoint はエージェントの起動コマンドで同一の値を渡しているので同じになっていますが、そのほかの項目では C のエージェントで登録した場合と比べて、取得できている項目や内容に違いがあります。これはそれぞれのエージェントの実装で取得している内容が異なっているためと思われます。

Wi-Fi 接続でのデータ取得

 SORACOM Inventory では大きく分けて、下記の2つのステップがあります。

  • デバイスの登録

  • デバイスの管理(データ取得、コマンド実行)

 デバイスの登録については SORACOM Air で回線に接続して行う必要がありますが、登録時にそれ以降の通信用の鍵の交換などが行われますので、一度登録が完了すればデバイスの管理は Wi-Fi 等、 SORACOM Air 以外の接続方法でも行うことが可能です。例えば Limited Preview で配布されている Java エージェントでは、デバイス登録時に下記のような認証用ファイルが作成されます。

pi@raspberrypi:~/tmp/soracom-inventory-agent-example-0.0.1/bin $ ls -l .soracom-inventory-credentials.dat 
-rw-r--r-- 1 pi pi 460 Nov  6 23:21 .soracom-inventory-credentials.dat

 上記ファイルがある状態でエージェントを実行すると、デバイスの登録のステップは実行されず、データの更新のステップから実行され、 Wi-Fi 接続でも下記のように実行可能です。

pi@raspberrypi:~/tmp/soracom-inventory-agent-example-0.0.1/bin $ ./soracom-inventory-agent-example-start -e raspberry_pi
2017-11-06 23:27:25,390 INFO SORACOMInventoryAgentExample - using endpoint [raspberry_pi]
2017-11-06 23:27:26,161 INFO FileCredentialStore - load credential from .soracom-inventory-credentials.dat
2017-11-06 23:27:26,162 INFO InventoryAgentInitializer - set security object from credential.
2017-11-06 23:27:26,171 INFO FileCredentialStore - load credential from .soracom-inventory-credentials.dat
2017-11-06 23:27:26,174 INFO InventoryAgentInitializer - set instance to initializer.objectId:3 instance num:1
2017-11-06 23:27:26,174 INFO InventoryAgentInitializer - set instance to initializer.objectId:14 instance num:1
2017-11-06 23:27:26,912 INFO LeshanClient - Starting Leshan client ...
Nov 06, 2017 11:27:26 PM org.eclipse.californium.core.CoapServer start
INFO: Starting server
Nov 06, 2017 11:27:26 PM org.eclipse.californium.core.network.CoapEndpoint start
INFO: Starting endpoint at coaps://0.0.0.0:0
Nov 06, 2017 11:27:26 PM org.eclipse.californium.scandium.DTLSConnector start
INFO: DTLS connector listening on [0.0.0.0/0.0.0.0:48720] with MTU [1,280] using (inbound) datagram buffer size [16,474 bytes]
Nov 06, 2017 11:27:26 PM org.eclipse.californium.core.network.CoapEndpoint start
INFO: Started endpoint at coaps://0.0.0.0:48720
Nov 06, 2017 11:27:26 PM org.eclipse.californium.core.network.CoapEndpoint start
INFO: Starting endpoint at coap://0.0.0.0:0
Nov 06, 2017 11:27:26 PM org.eclipse.californium.core.network.CoapEndpoint start
INFO: Started endpoint at coap://0.0.0.0:52646
2017-11-06 23:27:27,006 INFO LeshanClient - Leshan client started [endpoint:raspberry_pi].
2017-11-06 23:27:27,043 INFO RegistrationEngine - Trying to register to coaps://jp.inventory.soracom.io:5684 ...
2017-11-06 23:27:29,016 INFO RegistrationEngine - Next registration update in 54.0s...
2017-11-06 23:27:29,021 INFO RegistrationEngine - Registered with location '/rd/H12GIitHqU'.
Nov 06, 2017 11:27:29 PM org.eclipse.californium.core.network.config.NetworkConfig load
INFO: loading properties from file /home/pi/tmp/soracom-inventory-agent-example-0.0.1/bin/Californium.properties
2017-11-06 23:27:29,225 INFO ObservableInventoryObjectEnabler - observe start. observeStartDelayMillis:10000 observeInternalMillis:60000
2017-11-06 23:27:29,238 INFO ObservableInventoryObjectEnabler - observe start. observeStartDelayMillis:10000 observeInternalMillis:60000
Nov 06, 2017 11:27:29 PM org.eclipse.californium.core.CoapResource addObserveRelation
INFO: Successfully established observe relation between jp.inventory.soracom.io/52.198.95.62:5684#11e177e05af7f852 and resource /3
Nov 06, 2017 11:27:29 PM org.eclipse.californium.core.CoapResource addObserveRelation
INFO: Successfully established observe relation between jp.inventory.soracom.io/52.198.95.62:5684#654af6b769a78c4b and resource /3
Nov 06, 2017 11:27:29 PM org.eclipse.californium.core.CoapResource addObserveRelation
INFO: Successfully established observe relation between jp.inventory.soracom.io/52.198.95.62:5684#28587f4d02931bb6 and resource /3
Nov 06, 2017 11:27:29 PM org.eclipse.californium.core.server.ServerMessageDeliverer deliverRequest
INFO: Did not find resource [6, 0, 5] requested by jp.inventory.soracom.io/52.198.95.62:5,684
Nov 06, 2017 11:27:29 PM org.eclipse.californium.core.CoapResource addObserveRelation
INFO: Successfully established observe relation between jp.inventory.soracom.io/52.198.95.62:5684#8801847f7f072340 and resource /1
2017-11-06 23:27:39,220 INFO ObservableInventoryObjectEnabler - fire resource change for observation.
2017-11-06 23:27:39,239 INFO ObservableInventoryObjectEnabler - fire resource change for observation.

 ちなみに Limited Preview で配布されている C のエージェントでは、 SORACOM Air 以外での接続での管理はサポートされていません。

コンソールからのコマンド実行

 SORACOM Inventory ではデバイスのデータを確認するだけでなく、デバイスに対してコマンドを実行することができます。これも実際にどんなコマンドが実行できるかはエージェントの実装に依存しますが、 Limited Preview の C エージェントではデバイスの再起動ができます。SORACOM Air で回線に接続して C エージェントを起動した上で、コンソールのデバイス詳細画面の reboot の項目の右側にあるボタンをクリックします。

f:id:akanuma-hiroaki:20171110081518p:plain

 下記のような確認ダイアログが表示されますので、再起動して問題なければ コマンド実行 をクリックします。

f:id:akanuma-hiroaki:20171110082020p:plain

 すると C エージェントの出力が下記のように続き、 Raspberry Pi が再起動されます。

decrypt_verify(): found 9 bytes cleartext
 -> State: STATE_READY
decrypt_verify(): found 18 bytes cleartext

         REBOOT

 -> State: STATE_READY
 -> State: STATE_READY
〜〜〜中略〜〜〜
-> State: STATE_READY
 -> State: STATE_READY
reboot time expired, rebooting ...Connection to raspberrypi.local closed by remote host.
Connection to raspberrypi.local closed.

まとめ

 IoT デバイスを用いたサービスを展開しようとした場合、デバイスの管理は大きな課題であり、デバイスの状態を詳しく把握できるかどうかが問い合わせ対応時等のユーザ満足度にも大きく影響します。自前でデバイス管理の仕組みを作ろうと思うと大変ですが、 SORACOM Inventory を使うことでデバイスの管理はかなりやりやすくなりそうに思えます。デバイスが IoT Gateway などを介してネットに接続している場合でも、 Gateway が SORACOM Air でネットに接続していればデバイスの登録は可能な点や、一度登録してしまえば Wi-Fi 等の接続でも運用できるのも嬉しいところです。エージェントを自前で実装する必要がある点や、デバイス上でエージェントを動作させる必要がある点はサービス設計時に考慮が必要ですが、それ以外の対応が大幅に減らせることを考えれば、 SORACOM Inventory を利用するメリットは大きいと思います。

Vision.framework を使って QR コードを読む

こんにちは、iOSエンジニアのしだです。
急に寒くなってきて秋をすっ飛ばしていきなり冬になってしまった感じがします。

iOS11 で追加された Vision.framework を使ってQRコードを読み込みしたいと思います。(n番煎じ感あります。)

QR コード

iOSで QRコード を読む場合2種類あります。

  • AVFoundation.framework (AVCaptureMetadataOutputObjectsDelegate)
  • Vision.framework

AVCaptureMetadataOutputObjectsDelegate は AVFoundation 内でキャプチャ中に検出するのに対して、Vision.framework ではキャプチャした画像に対して検出する違いがあります。
あと、AVCamBarcode サンプルコードを動かした感じ一度に4つのQRコードが検出されましたが、Vision.framework の方は時間はかかりますが 4つ以上は検出できました。

できたもの

用意したQRコードは、1から16の文字列をQRコードにしたものです。そして検出されたQRコードに緑枠を表示するシンプルなものです。
iPad mini 2 で試しましたが、16個検出するのに1分以上かかりました。

f:id:unifa_tech:20171031140022j:plain

Vision.framework 使い方

Requests を作って、RequestHandlerで実行して、Observations で結果を受け取るようになっています。

Requests

  • VNDetectFaceRectanglesRequest: 顔検出
  • VNDetectBarcodesRequest: QRコードなどのバーコード検出
  • VNDetectTextRectanglesRequest: テキスト検出
  • VNTrackObjectRequest: オブジェクト検出
  • など...

これらの Requests を作成して、 RequestHandler で実行します。そうすると Requests と対になる Observation が返ってきます。
例えば、 VNDetectFaceRectanglesRequest の場合 VNFaceObservation が結果として受け取れます。

バーコード検出

AVCaptureVideoDataOutput から受け取った CVPixelBuffer からQRコードを検出してます。検出したQRコードに緑枠を描画するために結果を AVCaptureVideoPreviewLayer のビューに渡しています。

extension ViewController: AVCaptureVideoDataOutputSampleBufferDelegate {
    func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
        guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
            return
        }

        let handler = VNSequenceRequestHandler()
        let barcodesDetectionRequest = VNDetectBarcodesRequest { [weak self] (request, error) in
            if let error = error {
                NSLog("%@, %@", #function, error.localizedDescription)
            }
            guard let results = request.results else { return }
            
            ...

            DispatchQueue.main.async {
                let barcodes: [VNBarcodeObservation] = results.flatMap { $0 as? VNBarcodeObservation }
                self?.previewView.barcodes = barcodes
            }
        }
        try? handler.perform([barcodesDetectionRequest], on: pixelBuffer)
    }
}

検出したQRコードに緑枠を付ける

VNBarcodeObservation には、検出した文字列(payloadStringValue)や CIBarcodeDescriptor を持っています。 また矩形の座標をもっているので描画します。

class PreviewView: UIView {
    let targetLayerName = "box"

    /// バーコードを設定したら画面更新
    var barcodes: [VNBarcodeObservation] = [] {
        didSet {
            self.setNeedsDisplay()
        }
    }

    ...

    override func draw(_ rect: CGRect) {
        super.draw(rect)

        /// 緑枠クリア
        self.layer.sublayers?
            .filter { $0.name == targetLayerName }
            .forEach { $0.removeFromSuperlayer() }

        guard let previewLayer = self.layer as? AVCaptureVideoPreviewLayer else {
            return
        }

        for barcode in barcodes {
            /// VNRectangleObservation が持っている座標がY軸反転しているのでアフィン変換する
            let t = CGAffineTransform(translationX: 0, y: 1)
                .scaledBy(x: 1, y: -1)
            let tl = previewLayer.layerPointConverted(fromCaptureDevicePoint: barcode.topLeft.applying(t))
            let tr = previewLayer.layerPointConverted(fromCaptureDevicePoint: barcode.topRight.applying(t))
            let bl = previewLayer.layerPointConverted(fromCaptureDevicePoint: barcode.bottomLeft.applying(t))
            let br = previewLayer.layerPointConverted(fromCaptureDevicePoint: barcode.bottomRight.applying(t))
            
            /// 緑枠を描画
            let l = rectangle(UIColor.green, topLeft: tl, topRight: tr, bottomLeft: bl, bottomRight: br)
            self.layer.addSublayer(l)
        }
    }

    func rectangle(_ color: UIColor, topLeft: CGPoint, topRight: CGPoint, bottomLeft: CGPoint, bottomRight: CGPoint) -> CALayer {
        let lineWidth: CGFloat = 2
        let path: CGMutablePath = CGMutablePath()
        path.move(to: topLeft)
        path.addLine(to: topRight)
        path.addLine(to: bottomRight)
        path.addLine(to: bottomLeft)
        path.addLine(to: topLeft)
        let layer = CAShapeLayer()
        layer.name = targetLayerName
        layer.fillColor = UIColor.clear.cgColor
        layer.strokeColor = color.cgColor
        layer.lineWidth = lineWidth
        layer.lineJoin = kCALineJoinMiter
        layer.path = path
        return layer
    }
}

おわりに

Vision.framework であれば複数のQRコードを検出できるのですが、検出数によって処理の時間が変わってきます。 4つ検出するだけであれば1秒くらいで検出できましたが、9つQRコード検出には10秒以上かかっていました。
同時にQRコードを検出することはないと思いますがなにかあれば参考にしたいと思います。

コードはこちらにあります。

bitbucket.org

参考

Zoomの面白機能あれこれ

こんにちは。エンジニアの田渕です。

急に寒くなった影響で、我が家の防寒装備が間に合っていません。。。>_<

周囲にも風邪をひく方が増えてきて、気づけば巷はハロウィン、もう今年も終わりに近づいているのだなぁと感慨深く。。。

さて、今回のエンジニアブログは、Web会議システムであるZoomの、あまり知られていない機能についてお話したいと思います。

Zoomって?

ZoomはWeb会議システムです。

zoom.us

ユニファはオフィスが名古屋と東京にある、また、エンジニアは自宅勤務が可能となっている環境であるため、 遠隔地に居る方と会議をする機会が多くあります。 オンライン英会話等ではSkypeを利用している例が多いですが、ユニファではZoomも利用しています。 Zoomに関する一般的な使い方の説明は、既に色々な方が記事を書いていらっしゃいますので、今回は割愛。

Zoom developers

最近ではWeb会議システムのソフトも色々とあり、無料で利用できるものから有料のものまで、選択肢が色々とあります。 選ぶ際には各ソフトの機能などを見て考えることが多いかと思いますが、Zoomは既に用意されているアプリを利用して会議を行うだけでなく、サービスを利用しているエンジニアが独自にZoomのアプリを開発するためのライブラリやSDKが用意されています。

Zoom Developers – Power Up Your Apps with Video, Voice, and Screen Sharing

今回はその仕組みを利用し、ちょっと変わったものを作ってみたいと思います。

Zoom Webclient

Zoom Webclientは、少々変わった用途で用意されています。

Webclient – Zoom Developers

f:id:unifa_tech:20171027120045p:plain
Zoom developersのトップページ。
ZoomはPCで利用する際に専用のソフトをインストールする必要がありますが、環境によっては

  • PCの管理者権限を持っていなかったりしてソフトがインストール出来ない場合

  • ネットワーク上のセキュリティの制限等で、PCから音声を送ることが出来ない場合

なんかもありますよね。 そんな場面を想定し、相手からの画面共有はPCで受け、音声通話は電話回線を通して行うことができるというのが、このWebclientです。 なんだそれ?とお思いの方も多いでしょう……。百聞は一見に如かずということで、さっそくSampleコードを動かしてみましょう。

Sampleを動かす!

Zoomの各SDK,ライブラリには、サンプルコードが提供されており、基本的にはdevelopersサイトに書かれている手順に従い動かせば動くようになっています。 WebclientのサンプルはHTMLとjs,cssから構成されていて、これらのコードをサーバー上で動かし、そのページを閲覧することで機能を利用することができます。 動かし方については公式ページのチュートリアルを見て頂くとして、今回は動かしたあとのページを見て頂こうと思います。

f:id:unifa_tech:20171027163421p:plain

↑は、Firefoxでページを見てみたところです。ローカルのサーバー上で動いています。 (画面上の文言や色などは、サンプルのオリジナルの画面から加工して変えています。) Zoomは開催するミーティングにそれぞれ固有の番号が振られますが、この画面で自分の名前と、そのミーティングの番号を入力します。 すると…… f:id:unifa_tech:20171027164946p:plain こんな具合に、会議に参加することが出来ます。 ここで特徴的なのが、画面中央の「Join by phone」です。前述の通り、この機能は何らかの理由でPCからの音声送信が出来ない環境向けに作られた機能なので、画面の指示通りに実行すると、電話から「こずえ」として参加することが出来ます。 キャプチャは「米国」になっていますが、プルダウンで「日本」を選択し、表示された電話番号に電話→英語のアナウンスのあと、ミーティングIDと自分に割り振られた参加者IDを入力で、参加可能です。

また、自分から画面共有をすることは出来ませんが、相手が共有してくれたものを見ることが出来ます。 f:id:unifa_tech:20171027165646p:plain

まとめ

今回は、Web会議システムのZoomの拡張機能でこんなことができる!という一例を見て頂きました。 本当はサンプルを動かすまでの手順や、裏側の仕組みも説明しようかと思っていましたが、あまりにも長くなる為断念。。。マイナー機能であるということもあり、今回はご紹介という形にとどめました。 他にも、Zoomはリアルタイム背景合成が出来たり、カメラ画像が美肌モードになったり(!)という面白機能がありますので、機会がありましたら是非使ってみてください。

それでは。

Reactで機械学習用データラベリングアプリを作る

こんにちは、田中です。

弊社サービス、るくみーでは保育士のみなさんやプロカメラマンが撮影した写真が日々たくさんアップロードされています。 定期的にこの子ども達の様子が保護者のみなさんに公開・販売されますが、販売が終了したあとの写真は基本的にデータサーバーで眠るのみです。。><

この膨大に蓄積されている園写真を機械学習の力で価値につなげていきたい... !

ということで、その最初の一歩 兼 React勉強の一環として機械学習用データラベリングアプリを作ってみました。 ƪ(•◡•ƪ)"

できたもの

こんな感じです。

f:id:sanshonoki:20171024062117g:plain

写真の枚数が大きいと見込まれるので右側の写真リストは無限スクロールとなるように実装しました。

一番下の写真までスクロールしたときにデータが読み込まれてブラウザのスクロールバーが伸びることが分かると思います。

無限スクロール

FacebookやTwitter、Instagramといった人気のソーシャルメディアでのコンテンツ表示のUIとして使われています。 ユーザが下へスクロールするにつれてページのコンテンツが継続的にロードされるというアレです。

無限スクロール以外の選択肢としてはページネーションがあり、それぞれ長所短所があります

上の記事は主にビジネスコンテンツ目線での長所短所ですが今回はキーボードのショートカット操作だけでも楽に作業できることを重視し無限スクロールを採用しました。 (「進む」「戻る」の操作だけでよい)

react-infinite-scrollerを使った無限スクロール

react-infinite-scroller というパッケージを使いました。 他にも何個かありましたがこれが最も分かりやすく使いやすかったです。

github.com

create-react-appで作ったアプリをもとに無限スクロールを実現するコードを紹介します。

一番シンプルな例

f:id:sanshonoki:20171020104602g:plain

App.js

import React, { Component } from 'react';
import './App.css';
import InfiniteScroll from 'react-infinite-scroller'

class App extends Component {
  constructor(props) {
    super(props)

    this.state = {
      items: [],
      hasMoreItems: true
    }

    this.loadItems = this.loadItems.bind(this)
  }

  loadItems() {
    const current_item_count = this.state.items.length
    const max_items = 300
    const page_item_size = 50

    if (current_item_count < max_items) {
      const new_ids = Array.from(Array(page_item_size).keys()).map((num) => {return num+current_item_count})
      const new_items = new_ids.map((id) => {return {'id': id}})

      setTimeout(() => {
        this.setState({items: this.state.items.concat(new_items)})
      }, 500) // add some delay for demo purpose
    } else {
      this.setState({hasMoreItems: false})
    }
  }

  render() {
    const items = this.state.items.map((item) => {
      return <div>{item.id}</div>
    })

    return (
      <div className="App">
        <p className="App-intro">
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>

        <InfiniteScroll
          pageStart={0}
          loadMore={this.loadItems}
          hasMore={this.state.hasMoreItems}
          loader={<div>Loading...</div>}
          initialLoad={true}
        >
          {items}
        </InfiniteScroll>
      </div>
    );
  }
}

export default App;
  • <InfiniteScroll></InfiniteScroll> でスクロールさせる要素を囲む
  • loadMoreで新しいデータを読み込み、配列に追加する
  • 読み込むデータがなくなったら hasMorefalse にする

基本これだけです。簡単ですね :-)

フッター固定の例

続いてフッターを固定で表示する場合です。

f:id:sanshonoki:20171020104628g:plain

App.css

html, body {
  height: 100%;
}

.App {
  text-align: center;
  height: 100vh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

.scrollContainer {
  height: 100%;
  overflow: auto;
}

.footer {
  height: 30px;
  margin: 10px;
}

App.js

import React, { Component } from 'react';
import './App.css';
import InfiniteScroll from 'react-infinite-scroller'

class App extends Component {
  constructor(props) {
    super(props)

    this.state = {
      items: [],
      hasMoreItems: true
    }

    this.loadItems = this.loadItems.bind(this)
  }

  loadItems() {
    const current_item_count = this.state.items.length
    const max_items = 300
    const page_item_size = 50

    if (current_item_count < max_items) {
      const new_ids = Array.from(Array(page_item_size).keys()).map((num) => {return num+current_item_count})
      const new_items = new_ids.map((id) => {return {'id': id}})

      setTimeout(() => {
        this.setState({items: this.state.items.concat(new_items)})
      }, 500) // add some delay for demo purpose
    } else {
      this.setState({hasMoreItems: false})
    }
  }

  render() {
    const items = this.state.items.map((item) => {
      return <div>{item.id}</div>
    })

    return (
      <div className="App">
        <p className="App-intro">
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>
        <div className="scrollContainer">
          <InfiniteScroll
            pageStart={0}
            loadMore={this.loadItems}
            hasMore={this.state.hasMoreItems}
            loader={<div>Loading...</div>}
            initialLoad={true}
            useWindow={false}
          >
            {items}
          </InfiniteScroll>
        </div>
        <div className="footer">Footer</div>
      </div>
    );
  }
}

export default App;

こちらはスタイルを少し工夫する必要があります。

  • ウインドウ高さをheight: 100vh;で固定し、 overflow:hidden でスクロールバーを見せない
  • <InfiniteScroll></InfiniteScroll> をラップする<div>要素を作り、height: 100%; overflow: auto; にする
  • useWindow={false}のパラメータを追加する

create-react-appで適当なアプリを作ってApp.jsとApp.cssを上書きすればすぐにできるので興味あれば試してみてください

おわりに

ラベリングツールは一応の形になりました!

が、これからチマチマとラベリング作業していくのかと思うとちょっと憂鬱。w

でも、頑張りたいと思います。。 (•̀ᴗ•́)و

Actions on Google と AWS Lambda で Google Home から Slack にポストする

f:id:akanuma-hiroaki:20171013013717j:plain:w300:left

 みなさまこんにちは、ユニファCTOの赤沼です。先日やっと日本でも Google Home が発売されましたね。このブログを読んでいる方の中には買われた方もそれなりに多いのではないでしょうか。ユニファでも Google Home でどのぐらいのことができるのか検証してみるために、オフィス近くのビックカメラで購入してきました。普通に使っているだけでも天気やニュースを教えてくれたりと便利ではありますが、やはりエンジニアとしてはアプリを開発したり他のものと繋げてみたくなるものです。そこで今回は Google Home から使えるオリジナルのアプリを作ってみたいと思います。公式のドキュメントで紹介されているチュートリアルでは、バックエンドとして Firebase を使っていますが、それをそのままやっても面白くないので、今回は AWS Lambda でバックエンドの処理を実装してみたいと思います。

Google Home で使うアプリの構成

 Google Home の中で実際に音声アシスタントとして動いているのは Google Assistant で、Apple の Siri や Amazon の Alexa に当たるものです。なので Google Home で使うアプリを作るということは、 Google Assistant 用のアプリを作るということになります。ということは Google Assistant が動いていれば、 Google Home だけでなく、 Android 端末や iOS 端末でも使うことができるということになります。

 Google Assistant のアプリは Actions on Google というプラットフォーム上で開発します。主に下記のような要素から構成されています。

  • Actions on Google developer project
     アプリのプロジェクトを管理するベースとなるもので、アプリの分析や、テストのためのシミュレータ等の機能を持っています。

  • Action package
     Google Assistant がアプリをどのように起動するかや、どのように Fulfillment を呼び出すかなどのメタデータを定義しています。

  • Fulfillment
     HTTP Web サービスとしてホストされる、アプリの実機能を提供するファンクションです。JSONベースの Actions Protocol で Google Assistant からのリクエストを受け付け、処理結果を返します。

アプリ開発方法の選択肢

 Actions on Google でアプリを開発する際の開発方法の選択肢としては、下記の3つがあります。

  • Templates
     あらかじめ用意されているテンプレートを使う方法で、クイズやフラッシュカードなどのアプリのテンプレートが用意されています。コードを書く必要がなく、 Google Spreadsheet でデータの用意だけすれば使えるので、アプリの種類がマッチするならこの方法が一番お手軽です。

  • Dialogflow
     以前は API.AI という名前だった、自然言語で会話できる bot を作るためのAPIを提供しているサービスで、2016年9月に Google に買収されました。そして最近 Dialogflow という名前に変わりました。(Introducing Dialogflow, the new name for API.AI)Dialogflow は Actions SDK を Web IDE でラップしたもので、Action package を簡単に作成、デプロイできるようになっています。また、NLUエンジンを含んでいるので、自前で自然言語解析をする必要がなくなります。

  • Actions SDK
     限られたユーザとシンプルなやりとりを行うだけの場合に選択される方法で、既に自前のNLUエンジンを持っていてそれを使いたい場合などは Actions SDK を用いて開発する必要があります。また、 Actions SDK はIDEを提供しないので、テキストエディタなどで Action package を作成し、CLIから Google Developer project へデプロイする必要があります。

 今回は上記の3つの中から、Dialogflowを使う方法で実装します。

Actions on Google と Dialogflow での設定

 それではアプリを作成していきます。 Actions on Google と Dialogflow での設定は、基本的にはチュートリアルで紹介されている内容を踏襲していきます。

Overview  |  Actions on Google  |  Google Developers

 まず Actions on Google のコンソールにアクセスすると、プロジェクトの作成画面になりますので、+マークと Add/Import project と書かれているところをクリックします。

f:id:akanuma-hiroaki:20171013015622p:plain:w450

 するとプロジェクトの作成ダイアログが表示されますので、プロジェクト名と国・地域を選択して、 CREATE PROJECT をクリックします。

f:id:akanuma-hiroaki:20171013015913p:plain:w450

 プロジェクトが作成され、プロジェクトのOverviewの画面が表示されます。先ほど紹介した3つの開発方法のパネルが表示されますので、今回は Dialogflow のパネルの BUILD をクリックします。

f:id:akanuma-hiroaki:20171013020107p:plain:w450

 Action を追加するためのダイアログが表示されますので、 CREATE ACTIONS ON DIALOGFLOW をクリックして、Dialogflow のコンソールへ移動します。

f:id:akanuma-hiroaki:20171013020154p:plain:w450

 Dialogflow のコンソールが表示されたら、 Description にActionの内容を入力し、 DEFAULT LANGUAGE を選択して SAVE をクリックして保存します。

f:id:akanuma-hiroaki:20171013020251p:plain:w450

 すると Intents の画面が表示されます。ここでまず Fulfillment を使うためのとりあえずの設定をするため、左メニューから Fulfillment をクリックします。

f:id:akanuma-hiroaki:20171013020318p:plain:w450

 Fulfillment の画面が表示されたら、右上のトグルが DISABLED になっていますのでクリックして ENABLED に変更し、URL にとりあえずのダミーのURLを設定して、画面下部の SAVE をクリックします。

 余談ですが、 Dialogflow に名前が変更になったのと同時にこの画面に Inline Editor が用意されたので、例えばチュートリアルで紹介されていたような、テキストエディタで Function を書いて CLI で Firebase にデプロイするというようなことは、 Inline Editor のみで完結できるようになっているようです。

 保存したら左メニューから Intents をクリックして Intents の画面に移動します。

f:id:akanuma-hiroaki:20171013020503p:plain:w450

 作成済みの Intent の一覧が表示されます。最初から Default Fallback Intent と Default Welcome Intent が用意されています。まずは Default Welcome Intent をクリックします。

f:id:akanuma-hiroaki:20171013020605p:plain:w450

 Default Welcome Intent はユーザが Action を呼び出した際の WELCOME イベントによって最初に起動されます。各項目の設定方法の詳細はチュートリアルで紹介されているので割愛しますが、今回は下記のような内容で設定しています。保存したら再度左メニューから Intents をクリックして Intent の一覧画面に戻ります。

f:id:akanuma-hiroaki:20171013020634p:plain:w450

 次にユーザが Slack にポストしたい内容を受け取るための Intent を作成するため、画面上部の CREATE INTENT をクリックします。

f:id:akanuma-hiroaki:20171013020706p:plain:w450

 新しい Intent の作成画面が表示されたら、下記のような内容で設定します。この Intent ではユーザが話したことをそのまま Slack にポストするため、パラメータとしては @sys.any で全文を使用するようにしています。リクエストを受け取ったら Slack へ投稿するための処理を Fulfillment にリクエストするため、 Fulfillment の設定は Use webhook を選択し、Slack にポストして処理は完結なので、 Google Assistant の設定で End conversation にチェックを入れています。

f:id:akanuma-hiroaki:20171013020751p:plain:w450

 ここまでで一旦 Actions on Google と Dialogflow コンソールでの設定は終了し、 AWS Lambda と API Gateway の設定を行います。

AWS Lambda Function の作成と API Gateway の設定

 今回は AWS Lambda Function と API Gateway、 Slack の Webhook の詳しい作成方法は主題ではないので割愛しますが、まず Lambda の Function は下記のような内容で作成し、postToSlackFromGoogleHome という名前で保存しました。 SLACK_POST_URL には実際の Slack の Webhook のURLを設定してください。

#-*- coding:utf-8 -*-
from urlparse import urljoin
from urllib import urlencode
import urllib2 as urlrequest
import json
import random

SLACK_POST_URL = "https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXX"

def build_payload(message):
    payload = {
        "username": "Google Home",
        "text": message
    }
    return payload
    
def post(payload):
    payload_json = json.dumps(payload)
    data = urlencode({"payload": payload_json})
    req = urlrequest.Request(SLACK_POST_URL)
    response = urlrequest.build_opener(urlrequest.HTTPHandler()).open(req, data.encode('utf-8')).read()
    return response.decode('utf-8')

def post_to_slack(message):
    payload = build_payload(message)
    return post(payload)

def lambda_handler(event, context):
    return post_to_slack(event['result']['parameters']['any'])

 チュートリアルで紹介されているように node.js で Fulfillment を実装する場合には Actions on Google の SDK が利用できるので、SDKが提供するメソッドでリクエスト内容を取り出したりできますが、今回は Lambda で Python による実装なので、送信されてくる JSON から目的のパラメータを取り出しています。送信されてくる JSON で使用するパラメータに関する部分を抜粋すると下記のような内容になります。 Intent の設定でパラメータとして @sys.any を指定したので、 parameters の中で any として目的の内容が送信されてきています。今回はとりあえずユーザが話した内容だけ取れれば良いので、他の部分についてはあまり調べていません。

{
  "id": "7a27e8ff-4e18-4e80-89a0-141dbb3965a5",
  "timestamp": "2017-10-12T17:17:21.169Z",
  "lang": "ja",
  "result": {
    "source": "agent",
    "resolvedQuery": "投稿テスト",
    "action": "",
    "actionIncomplete": false,
    "parameters": {
      "any": "投稿テスト"
    }
  }
}

 作成した Lambda Function を API Gateway と紐づけ、API をデプロイしたら、そのURLを Dialogflow のコンソールの Fulfillment の画面で URL の項目に設定して保存します。

f:id:akanuma-hiroaki:20171013083215p:plain:w450

アップデートとテスト

 ここまでで一通りの設定は終了なので、テストをしてみます。まずは Dialogflow コンソールの左メニューから Integrations をクリックします。 Integration の一覧画面に移動したら、 Google Assistant のパネルをクリックします。

f:id:akanuma-hiroaki:20171013021018p:plain:w450

 初回は下記のようなダイアログが表示されるかと思いますので、そのまま DONE をクリックします。

f:id:akanuma-hiroaki:20171013021143p:plain:w450

 Google Assistant の Integration の設定ダイアログが表示されたら、下部の TEST をクリックすると、下記の画面のように Test now active と表示され、シミュレータでのテストができるようになりますので、 VIEW をクリックして Actions on Google のシミュレータに移動します。

f:id:akanuma-hiroaki:20171013021202p:plain:w450

 シミュレータに移動すると、 Input フォームにはあらかじめ「スラックポストにつないで」というテキストが入っているかと思いますので、Input フォームを選択して Enter します。

f:id:akanuma-hiroaki:20171013085449p:plain:w450

 すると Action が起動し、右側にはリクエストの JSON の内容が表示されます。続けてユーザが話した想定の文言を Input フォームから入力して Enter します。

f:id:akanuma-hiroaki:20171013085507p:plain:w450

 追加で作成した Intent の設定内容に従って処理が行われ、レスポンスが表示されます。

f:id:akanuma-hiroaki:20171013085524p:plain:w450

 実際に Slack の Webhook へのリクエストも行われ、下記のように Slack にメッセージが表示されます。

f:id:akanuma-hiroaki:20171013085544p:plain:w450

 これでとりあえず Google Home に話した内容を Slack にポストするアプリを作ることができました。

まとめ

 今回はお試しということで、ごくシンプルな内容でテスト段階までということでしたが、 Lambda 等と連携できればそこから先は自由度高く色々なことができると思います。また、一般的に使ってもらえるようなアプリを作って公開すれば、多くのユーザに使ってもらうこともできます。会話で処理を進めるというUIは画面を使うアプリと比べて用途が絞られると思いますが、もうすぐ Google Home mini も発売されますし、色々試して新しい使い方を模索してみたいと思います。

線形計画とシンプレックス法

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

最近秋の基本情報技術者試験(FE)を目標にして勉強しています。FEといえば、いろんな分野の問題が出題されます。その中に、OR・IEにおける線形計画問題は個人にとって一番難しかったです。

例えば、平成25年秋、問75

製品X及びYを生産するために2種類の原料A,Bが必要である。製品1個の生産に必要となる原料の量と調達可能量は表に示すとおりである。製品XとYの1個当たりの販売利益が,それぞれ100円,150円であるとき,最大利益は何円か。

原料 製品Xの一個当たりの必要量 製品Yの一個当たりの必要量 調達可能量
A 2 1 100
B 1 2 80

線形計画を分からなくても、問題を読んだら制約条件や目的関数が理解できると思います。

製品Xの生産量を x 、製品Yの生産量を y とする。

制約条件

2x + y <= 100
x + 2y <= 80

最大利益 max の関数

max = 100x + 150y

一見二変数の方程式ですが、不等式のゆえ、そのまま解けてはならないです。でも現実と繋がる生産の問題なので、生産量は0とそれ以上、つまり負数にならないことが分かります。

x, y >= 0

不等式と合わせて、生産量x, yの範囲は計算できそうですが、 xy お互い関連して、それといつ最大利益になるかも分からなくて、範囲を計算しても意味がないかもしれません。

ほかに方法がないでしょうか。

続きを読む