Saturday, August 11, 2018

分解:Logicool M570

部屋の片づけをしていたところ、不要な家電製品をいくつか見つけました。
モノづくりに係るものとして、既存製品から勉強したいと思います。
製品を分解することで多くのノウハウを学ぶことができるはずです。

今回は調子が悪くなっていたLogicoolのM570を分解したいと思います。
使用する工具は、No.1のプラスドライバとT6のトルクスドライバです。
ピンセットかデザインナイフがあればシールが剥しやすいです。
私はこの分解のために、67in1 特殊精密ドライバー スマホ分解 修理 磁石付き工具 青を買いました。

M570の分解

カバーの取り外し



製品正面です。
塗装ではなくシボのようです。
ロゴはプリントのようですが、かなりの塗膜強度です。
デザインナイフでこすってもなかなか落ちません。

製品背面です。
ラバーパットと電池カバー、電源SWがあります。
ラバーパットはデザインナイフでこすっても表面がボロボロと崩れません。
質のよいもののようです。
PRONのようなものでしょうか。
http://www.poron.jp/poron/foot_pad/


背面のパッドを外すと、トルクス平頭φ2x5 タッピングビスが4本見えます。
これはT6のトルクスレンチで外せます。

また、この時点では見落としてしまい、無理やりあけましたが、
電池のマークやシリアルが印刷されているシールの下にも1本同様のねじがあります。
最終の工程でシールを貼っているようです。
これでカバーは開きます。

正面カバー側




正面カバー側にクリックの伝達用の樹脂部品と、ホイールが見えます。
クリックは材料自体のバネ性を利用しています。
左クリック・右クリックのパーツはシボ仕上げです。
戻る・進むボタンは磨き仕上げにΔマークが印刷されています。
ロゴ同様の塗膜強度です。



ホイールはバネで支えられています。
ホイールには細いスリットが切られており、光学式エンコーダーを形成しています。
外輪のゴムはホイールから外せないくらいかぶっていました。
一体整形なのかもしれません。

状態確認用のLEDはライトガラスで基板面LEDから導光されています。
ハメ合いと切り欠きで組み立て位置を保持します。

背面カバー側


背面側には電池ボックス、トラックボールの保持器、SW、基板が取付られています。


トラックボールの保持器は、CCDと一体型になっています。
ユニットは基板とFPCで接続します。
強化板は接点部のところにだけ入れてあります。
保持球は埋め込んで成型していると思われます。
材質は、アルミニウムセラミックでしょうか。




電源スイッチは囲いの中でスライドします。
赤と緑の部分はシールになっていました。
擦れる部分なので意外です。


基板は電池用の接点と一体型です。
マイクロスイッチはオムロンのD2型です[1]。
タクトスイッチはアルプスのSKQGAFE010を使用しています[2]。


戻る・進むボタンはケーブルで接続されています。


裏面にはnRF24L01という2.4GHzの無線トランシーバーが使用されています。
これは電子工作にも使用されるようです。
電源スイッチも背面にあります。

ネジについて

ネジはトルクスと十字の2種類で、外側と内部で使い分けています。
十字は2種類の径の違うねじを使っています。
十字ネジは同じものに統一できたような気がします。

鉄製のねじなので、着磁したドライバで吸着でき、組み立てやすいです。

まとめ

今回はLogicoolのM570を分解しました。
トラックボールの中堅モデルで持っている方も多いのではないでしょうか。
機構部品はしっかりと作られている印象です。

一方で電子基板はなかなか攻めているなと感じました。
基板の接続にケーブル直はんだだったり、電池の接点も直付けでした。
組み立て大変じゃないかなと思いますが、手組なのでしょうか。

もう少し電気の勉強もして、パターンなども評価できるようになりたいです。

今回は以上です。

参考文献:
[1]「Logicool Wireless Trackball M570 トラブル原因」『IT大阪.com』<https://www.itosaka.com/WordPress/2013/09/logicool-wireless-trackball-m570-%E3%83%88%E3%83%A9%E3%83%96%E3%83%AB%E5%8E%9F%E5%9B%A0/> (2018/08/12アクセス)

[2]「マウスの修理(M570)~マイクロ&タクトスイッチ交換~」『みんから』<https://minkara.carview.co.jp/userid/1075104/car/833170/4431546/note.aspx> (2018/08/12アクセス)

Saturday, July 28, 2018

ゼロから作るDeep Learningを読んで その2

前回からオライリージャパンのゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装を読み続けています。
今回はサンプルコードの実行途中で躓いた点を書いておきます。

各サンプルコードはその章のディレクトリで実行することになっています。
例えば、7章であれば以下のような場所になります。

~/deep-learning-from-scratch-master/ch07

共通のモジュールはその上層のディレクトリにあるので、以下のようにパスを通します。

import sys, os
sys.path.append(os.pardir)
from common.util import im2col

しかし、これではパスが通っていないのかim2colをimportできませんでした。
そこでいろいろ調べたところ、以下のようにやるとうまくいきました。

sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir)))

これで、ちゃんと上のディレクトリが追加されました。
sys.pathをプリントすればパスが通っているか確認できます。

print(sys.path)


以上が私が詰まった部分です。
また、他におかしいところがあればブログに書きたいと思います。

  

Tuesday, July 10, 2018

ゼロから作るDeep Learningを読んで その1

オライリージャパンのゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装を読み始めました。 
まだ途中ですが、できる限り平易な文章で書いてあるので読みやすいです。
Pythonを使って実装するので、ちょこちょこ実装しながら確認できるのがよいです。
やりながら詰まったところがあるので、メモ代わりに書いておきます。

4章 ニューラルネットワークの学習

4.2.3 ミニバッチ学習


ここで、以下のような訓練データからを読みだすスクリプトを実行します。

import sys, os
sys.path.append(os.pardir)
import numpy as np
from dataset.mnist import load_mnist

(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)

print(x_train.shape)
print(t_train.shape)
すると、以下のようにエラーが発生します。
Traceback (most recent call last):
  File "", line 1, in 
  File "../dataset/mnist.py", line 117, in load_mnist
    dataset['train_label'] = _change_ont_hot_label(dataset['train_label'])
NameError: name '_change_ont_hot_label' is not defined

これはmnist.pyの中に原因があるようです。
mnist.pyは以下の場所にあります。

/deep-learning-from-scratch-master/dataset

117行目のエラー箇所を見に行くと、_change_ont_hot_labelという関数を呼び出しています。
これが未定義なようです。
どうも単語がおかしいのですが、該当する関数が定義されてないかファイルを読んでみます。

    if one_hot_label:
        dataset['train_label'] = _change_ont_hot_label(dataset['train_label'])
        dataset['test_label'] = _change_ont_hot_label(dataset['test_label'])


すると、82行目に以下のような関数を見つけました。 やはり単純なタイポのようです。

def _change_one_hot_label(X):
    T = np.zeros((X.size, 10))
    for idx, row in enumerate(T):
        row[X[idx]] = 1

    return T

つまり、117、118行目を以下のように修正すれば無事に動きます。

    if one_hot_label:
        dataset['train_label'] = _change_one_hot_label(dataset['train_label'])
        dataset['test_label'] = _change_one_hot_label(dataset['test_label'])


以上が私が詰まった部分です。
また、他におかしいところがあればブログに書きたいと思います。

 

Monday, January 1, 2018

Pythonではじめる機械学習を読んで その1

最近、AIという言葉をどこでも耳にします。
私は、ここでいうAIは機械学習のことだと認識しています。
以前にも機械学習は勉強したことがあるのですが、すっかり忘れてしまったので再度学習してみようと思い立ちました。

色々情報を集めていると以下の本がなかなかよさそうです。
今は機械学習といえばPythonが流行りのようです。

Pythonではじめる機械学習 ―scikit-learnで学ぶ特徴量エンジニアリングと機械学習の基礎

私が購入したのは2017年11月6日発行の初版第3刷のモノです。
本通りにやって詰まった部分がちょこちょこありましたので、メモ代わりにブログに残しておきます。
同じように悩める方がスムーズに学習できることを期待しております。


それでは今回は1章 はじめについてです。
以下の章番号はPythonではじめる機械学習に準じています。
なお、私の環境はWindows10 Pro 64bitです。

1章 はじめに

1.3.1 scikit-learnのインストール

3つのやり方が示されていましたが、私は一番無難そうだったAnacondaでインストールしていました。
下記サイトにアクセスして各プラットフォームにあったインストーラをダウンロードしてください。
私が入手したのはAnaconda 5.0.1 For Windows InstallerのPython 3.6 version 64bitです。
https://www.anaconda.com/download/

後述しますが、書籍では別途mglearnをイ入手するよう指示しています。


1.4.1 Jupyter Notebook

Jupyter NotebookはWindowsのスタートメニューから起動します。
起動するとブラウザ上に下記のような画面が表示されます。
①のNewをクリックしてPython3を選択します。
下記の画面が表示されます。
②のセル内にコードを入力します。
 Ctrl+Enterを押すと選択したセル内のコードが実行され、その結果が表示されます。
Shift+Enterで新しいセルを追加できます。
③のテクストボックスに名前を入力し、④のボタンで保存できます。
保存すればまたいつでも途中から作業を続けることができます。

1.4.5 pandas

IPythonでDataFrameを表示しますが、display()を読んだところでエラーとなりました。
以下のようにimportするとうまくいきます。
from IPython.core.display import display

1.4.6 mglearn

Anacondaにはmglearnは含まれていないので別途インストールする必要があります。
(ちゃんと書籍には記してありましたがうっかり見落としていました。)
Anacondaのcoda installコマンドではインストールできないのでpipを使います。
pip install mglearn
特にパスを通すなど必要なくjupyter noteで動きました。


私がつまづいたところは以上です。





Python版OpenCVで平滑化を行う

今回は,Python版のOpenCVを使って取得した画像の平滑化を行う方法をメモしておきます.

環境は以下のとおりです.
Python 2.6
OpenCV 2.4.6


それでは早速スクリプトを見ていきます.

import cv2

if __name__=="__main__":

    capture = cv2.VideoCapture(0)
    
    if capture.isOpened() is False:

        raise("IO Error")

    cv2.namedWindow("Capture", cv2.WINDOW_AUTOSIZE)
    cv2.namedWindow("Red", cv2.WINDOW_AUTOSIZE)

    while True:

        ret, image = capture.read()
        if ret == False:
            continue

        gaussian_image = cv2.GaussianBlur(image, (5, 5),  0)
        
        red_image = []
        red_image.append(extract_color(image, 170, 5, 50, 200))
        red_image.append(extract_color(gaussian_image, 170, 5,  50, 200))
        
        gaussian_result = cv2.hconcat([image, gaussian_image])
        red_result = cv2.hconcat(red_image)
        
        cv2.imshow("Capture", gaussian_result)
        cv2.imshow("Red", red_result)
       
        if cv2.waitKey(33) >= 0:
            cv2.imwrite("capture.png", gaussian_result)
            cv2.imwrite("red_image.png", red_result)
            break

    cv2.destroyAllWindows()

Webカメラからの画像の取得と色抽出については,記事の終わりにあるリンクからそれぞれの説明をご覧ください.
なお,色抽出には以前に作成した以下のようなextract_color関数を使います.

出力画像 extract_color( 入力画像, 色相のしきい値(下), 色相のしきい値(上), 彩度のしきい値, 明度のしきい値 )



cv2.THRESH_BINARY: しきい値より大きい値は最大値,それ以外は0
cv2.THRESH_BINARY_INV: しきい値より大きい値は0,それ以外は最大値

その後,各画像のORを取ることで,2枚の画像を合わせます.
    if h_th_low > h_th_up:
        ret, h_dst_1 = cv2.threshold(h, h_th_low, 255, cv2.THRESH_BINARY) 
        ret, h_dst_2 = cv2.threshold(h, h_th_up,  255, cv2.THRESH_BINARY_INV)
        
        dst = cv2.bitwise_or(h_dst_1, h_dst_2)
次に,360°をまたがない場合を考えます.
ここでは,しきい値処理に以下のものを使用します.

cv2.THRESH_TOZERO: しきい値よりより大きい値はそのまま,それ以外は0
cv2.THRESH_TOZERO_INV: しきい値よりより大きい値は0,それ以外はそのまま

まず,しきい値(下)の値を0にし,その後,しきい値(上)より大きい値を0にすることで特定の範囲の値を抽出できます.
    else:
        ret, dst = cv2.threshold(h,   h_th_low, 255, cv2.THRESH_TOZERO) 
        ret, dst = cv2.threshold(dst, h_th_up,  255, cv2.THRESH_TOZERO_INV)

        ret, dst = cv2.threshold(dst, 0, 255, cv2.THRESH_BINARY)
色相については,ここまでで処理できたので,残りの明度と彩度についても処理を行います.
ここでは,黒に近い色を排除するだけでよいので,下限値のみのしきい値処理を行います.
    ret, s_dst = cv2.threshold(s, s_th, 255, cv2.THRESH_BINARY)
    ret, v_dst = cv2.threshold(v, v_th, 255, cv2.THRESH_BINARY)
そして最後に,各画像のANDをとり,各しきい値を満たす画素を抽出します.
    dst = cv2.bitwise_and(dst, s_dst)
    dst = cv2.bitwise_and(dst, v_dst)
これで,任意の色を抽出するextract_color関数が完成しました.

それでは,この関数を使って色抽出を行ってみます.
基本的なWebカメラの扱い使い方は,ここを見てください.
if __name__=="__main__":

    capture = cv2.VideoCapture(0)
    
    if capture.isOpened() is False:

        raise("IO Error")

    cv2.namedWindow("Capture", cv2.WINDOW_AUTOSIZE)
    cv2.namedWindow("Red",     cv2.WINDOW_AUTOSIZE)
    cv2.namedWindow("Yellow",  cv2.WINDOW_AUTOSIZE)

    while True:

        ret, image = capture.read()
        if ret == False:
            continue

        red_image    = extract_color(image, 170, 5,  190, 200)
        yellow_image = extract_color(image, 10,  25, 50,  50)

        cv2.imshow("Capture", image)
        cv2.imshow("Red",     red_image)
        cv2.imshow("Yellow",  yellow_image)
       
        if cv2.waitKey(33) >= 0:
            cv2.imwrite("image.png", image)
            cv2.imwrite("red_image.png", red_image)
            cv2.imwrite("yellow_image.png", yellow_image)
            break

    cv2.destroyAllWindows()
このスクリプトの実行結果は以下のようになります.
まずは,入力画像です.
image
次に,赤色の抽出結果です.
red_image
最後に,黄色の抽出結果です.
yellow_image

この例では,赤色と黄色をそれぞれ抽出して画像を表示しています.
各画像を見ると,それぞれの色で抽出出来ていることがわかります.
基本的な処理は既に説明していますが,OpenCVではHSVの各チャンネルは以下のような値域となっていることに注意してください.

H(色相):0~180
S(彩度):0~255
V(明度):0~255

以上でHSV色空間で色抽出をする方法の説明を終わります.  

Python版OpenCV関連の記事:
参考文献:
「Miscellaneous Image Transformations」『OpenCV 2.4.9.0 documentation』<http://docs.opencv.org/modules/imgproc/doc /miscellaneous_transformations.html> (2015/02/20アクセス)
「Basic Operations on Images」『OpenCV 3.0.0-dev documentation』<http://docs.opencv.org/trunk/doc/py_tutorials/py_core /py_basic_ops/py_basic_ops.html> (2015/02/20アクセス) 

Saturday, October 10, 2015

MOCO'Sキッチンのフォントに似せるβ版

お久しぶりです。
今回は昔に作ったまな板の話です。

2012年に料理好きの友人にMOCO'Sキッチン風のまな板を作成してプレゼントしました。
このときに使用したフォントはなんですか?という質問をもらいました。
検索してみると同じように悩んでいる人が多いようです。

結論からいうとキッチンは元ネタの画像を加工して使用していました。
英語のフォントは本当に適当です。

そのまま答えるのも申し訳ないので似たフォントを探してみました。
その結果が以下の画像です。


mocos

オリジナルの「キッチン」の部分はかなり特徴的なフォントです。
 ここには、以下のフォントを使用させていただきました。

マキナス: http://moji-waku.com/makinas/index.html

「MOCO S」の部分はSimplified Arabicで「'」はArial Unicodeを使用しました。

マジマジと見ると違う部分が目立ちます。

課題は、「チ」と「ン」のフラットさの再現でしょうか。

正直フォントで頑張って再現するより、自分でラインを引いたほうが早い気がしてきました。
今度はMOCO'Sキッチン風フォントの作成に挑戦しても面白いかもしれませんね。

それでは今回はここまでです。

Tuesday, March 10, 2015

RICHO THETAの簡単なAndroidアプリ

RICHO THETAを最初見たときは,あんまりピンと来なかったのですが使ってみると便利で面白いですね.
そんなTHETAをいじる機会があったので,簡単なAndroid用のプログラムを作ってみました.

SDKは下記のサイトからDLできます.

RICHO THETA Developpers
https://developers.theta360.com/ja/


SDKに付属しているサンプルプログラムは私にとって複雑で,何をしているのかわかりにくいものでした.
そこで,SDKのサンプルとリファレンスを頼りに,以下のようなシャッターを切るだけの簡単なサンプルを作りました.

SimpleTHETA_01

このサンプルはCONNECT Buttonを押してTHETAに接続し,SHOOT Buttonを押してシャッターを切るというプログラムです.
切断するときはDISCONNECT Buttonを押します.
THETAのステイタスからGUIを動かすサンプルとして,各操作の状況をTHETA statusというtextviewに描画しています.

それではこのプログラムの説明を行います.

まずは,以下のように,インターネットのpermissionを取ります.
この記述は,AndroidManifest.xmlに記述します.
<uses-permission android:name="android.permission.INTERNET"/>
つぎに,GUIのレイアウトを設定します.デフォルトではactivity_main.xmlに記述します.
    <LinearLayout
    android:id="@+id/linearLayout1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentLeft="true"
    android:layout_alignParentTop="true"
    android:layout_marginLeft="28dp"
    android:layout_marginTop="64dp"
    android:orientation="vertical" >
        
        <LinearLayout
        android:id="@+id/linearLayout2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >
        
         <TextView
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:text="THETA status:" />    
             
         <TextView
         android:id="@+id/status"
         android:layout_width="wrap_content"
         android:layout_height="match_parent"
         android:text="Status" />
        
        </LinearLayout>
        
        <LinearLayout
        android:id="@+id/linearLayout3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >
        
         <Button
         android:id="@+id/connect"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:text="CONNECT" />
         
         <Button
         android:id="@+id/disconnect"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:text="DISCONNECT" />
         
         <Button
         android:id="@+id/shoot"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:text="SHOOT" />
        
        </LinearLayout>
 
    </LinearLayout>

これまでで,準備が整ったのでMainActivity.javaに処理を記述します.
この処理は基本的に各ボタンのListnerを設定して,そのボタンに合わせて処理を行います.
気を付ける点は以下の2つです.
  1. THETAとのやりとりはINTERNETを使うので,AsyncTaskなどを使って別スレッドで行う.
  2. GUIを別スレッドから操作するときは,Handlerを使う.
この点だけ気を付ければ特別難しい処理はないと思います.
各ボタンを押すとConnectTask,DisConnectTask,ShootTaskが実行されます.

ConnectTaskでは,指定したIPアドレスのTHETAに接続を行い,成功したらCONNECTED,失敗したらERRORと表示します.
DisConnectTaskでは,TETAとの接続を断ち,成功したらDISCONNECTED,失敗したらエラーログを表示します.
ShootTaskでは,シャッターを切り,成功したらSHOOT,失敗したら各エラーを表示します.

public class MainActivity extends Activity {
 
 PtpipInitiator camera;
 private static final String IP_ADDRESS = "192.168.1.1";
 private TextView txt_theta_status;
 private Button btn_theta_connect;
 private Button btn_theta_disconnect;
 private Button btn_theta_shoot;
 
 private Handler handler = null;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  txt_theta_status = (TextView) findViewById(R.id.status);
  btn_theta_connect = (Button) findViewById(R.id.connect);
  btn_theta_disconnect = (Button) findViewById(R.id.disconnect);
  btn_theta_shoot = (Button) findViewById(R.id.shoot);
        
  btn_theta_connect.setOnClickListener(new View.OnClickListener() {
       @Override
       public void onClick(View v) {
        new ConnectTask().execute();
       }
  });
        
         btn_theta_disconnect.setOnClickListener(new View.OnClickListener() {
       @Override
       public void onClick(View v) {
        new DisConnectTask().execute();
       }
         });
        
         btn_theta_shoot.setOnClickListener(new View.OnClickListener() {
       @Override
       public void onClick(View v) {
        new ShootTask().execute();
   }
         });
        
         handler = new Handler();
 }
 
 
 private class ConnectTask extends AsyncTask {
     @Override
     protected String doInBackground(String... params) {
         try {
             camera = new PtpipInitiator(IP_ADDRESS);
             
             handler.post(new Runnable() {
              @Override
              public void run() {
               txt_theta_status.setText("CONNECTED");
              }
             });
             
             
         } catch (Throwable e) {
             e.printStackTrace();
             
             handler.post(new Runnable() {
              @Override
              public void run() {
               txt_theta_status.setText("ERROR");
              }
             });
         }
         return null;
     }
 }
 
 private class DisConnectTask extends AsyncTask {
  @Override
  protected Boolean doInBackground(Void... params) {

   try {
    PtpipInitiator.close();
    
    handler.post(new Runnable() {
              @Override
              public void run() {
               txt_theta_status.setText("DISCONNECTED");
              }
             });
    
    return true;

   } catch (Throwable throwable) {
    final String errorLog = Log.getStackTraceString(throwable);
    handler.post(new Runnable() {
              @Override
              public void run() {
               txt_theta_status.setText(errorLog);
              }
             });

    
    return false;
   }
  }
 }

 private static enum ShootResult {
  SUCCESS, FAIL_CAMERA_DISCONNECTED, FAIL_STORE_FULL, FAIL_DEVICE_BUSY
 }
 
 private class ShootTask extends AsyncTask {

  @Override
  protected ShootResult doInBackground(Void... params) {
   try {
    PtpipInitiator camera = new PtpipInitiator(IP_ADDRESS);
    camera.initiateCapture();
    handler.post(new Runnable() {
              @Override
              public void run() {
               txt_theta_status.setText("SHOOT");
              }
             });
    return ShootResult.SUCCESS;

   } catch (IOException e) {
    handler.post(new Runnable() {
              @Override
              public void run() {
               txt_theta_status.setText("FAIL_CAMERA_DISCONNECTED");
              }
             });
    return ShootResult.FAIL_CAMERA_DISCONNECTED;
   } catch (ThetaException e) {
    if (Response.RESPONSE_CODE_STORE_FULL == e.getStatus()) {
     handler.post(new Runnable() {
               @Override
               public void run() {
                txt_theta_status.setText("FAIL_STORE_FULL");
               }
              });
     return ShootResult.FAIL_STORE_FULL;
    } else if (Response.RESPONSE_CODE_DEVICE_BUSY == e.getStatus()) {
     handler.post(new Runnable() {
               @Override
               public void run() {
                txt_theta_status.setText("FAIL_DEVICE_BUSY");
               }
              });
     return ShootResult.FAIL_DEVICE_BUSY;
    } else {
     handler.post(new Runnable() {
               @Override
               public void run() {
                txt_theta_status.setText("FAIL_CAMERA_DISCONNECTED");
               }
              });
     return ShootResult.FAIL_CAMERA_DISCONNECTED;
    }
   }
  }

 }

以上で,AndroidアプリからTHETAのシャッターを切る方法の説明を終わります.