【Windows11】エクスプローラー

Windows11では、下図のように上部にあったリボン表示が無くなり、ツールバーのみの表示となりました。

リボン表示はノートPCなどでは、縦方向の領域をそれなりに占有してしまうので、この変更は好感触です。
最初は少し戸惑いますが、それぞれのアイコンの機能は以下の通りです。

(2021.8.2追記)ツールバーが更新されました。下図は画像ファイルを選択した時の表示状態です。

 

切り取り、コピー、貼り付け、名前の変更、削除はその名の通りの機能です。
その他の機能については以下の通りです。

新しいフォルダー

新しいフォルダー、新しいアイテムの作成ができます。

新しいアイテムでは、フォルダー、ショートカット、Word文章、Power Point文章、テキスト、Excelワークシート、圧縮(zip形式)フォルダ―の作成ができます。

Cドライブ直下ではアクセス制限がかかるので、フォルダーの作成しか表示されません。

共有

実際には試せていませんが、近距離共有、メールで送信、アプリと共有ができるようです。

並び替え

ファイルを 名前、状態、更新日時、その他(種類、サイズ、作成日時、タグ、タイトル)の昇順、降順で並び替えることができます。

表示オプション

ファイルのアイコン表示の特大、大、中、小、ファイル表示の一覧、詳細、並べて表示、コンテンツの表示切替ができます。

アイコンの表示サイズは従来のWindowsと同じようにCtrlキーを押しながら、マウスホイールを上下させることでも変更可能です。

ウィンドウ右下には、左側に詳細、右側に大アイコンのボタンがあるので、こちらを使うのも便利だと思います。

 

表示オプションの中に コンパクトビュー という項目がありますが、コンパクトビューにチェックを入れると、フォルダやファイル名の行間が狭くなります。

コンパクトビュー有効

コンパクトビュー無効

表示

表示の項目の中には、ナビゲーションウィンドウ(左側のツリー表示)、詳細ウィンドウ、プレビューウィンドウ、項目チェックボックス、ファイル名拡張子、隠しファイルの表示/非表示の設定ができるようになっています。

もっと見る(・・・)

もっと見る(…)の部分は、表示しているフォルダ、ファイルによって、表示されるメニューが異なります。

Cドライブ(C:\)を選択している状態で、もっと見る(…)をクリックすると以下のようなメニューが表示されます。

画像ファイルを選択状態で、もっと見る(…)を選択すると以下のようになります。
右に回転などが追加されています。

 

ファイルを右クリック

ファイルを右クリックすると、下図のようなメニューが表示されます。
切り取り、コピー、名前の変更、共有、削除が上部にアイコン表示になっているところがWindows10とは異なります。

 

Show more optionsをクリックすると、昔ながらのメニューが表示されます。

ファイル名以外の部分を右クリック

ファイル名以外の部分を右クリックすると、エクスプローラ上部に表示されているアイコンと同じ様なメニューが表示されます。

まとめ

リボン表示が無くなり、スッキリした印象ですが、Windows10ではパスのコピーをクイックアクセスツールバーに追加して使っており便利だったのですが、その設定ができなくなっているのが残念でした。。

【Windows11】日本語・英語キーボードの切り替え

Windows11にて日本語・英語のキーボードの種類を変更するには以下のように行います。

スタートボタンを右クリックし、設定を選択して、設定画面を表示します。

設定画面の中から、左側の時刻と言語を選択し、言語と地域をクリックします。

次に日本語の右側にある  をクリックし、言語のオプションをクリックします。

キーボードレイアウトの右側の レイアウトを変更する をクリックします。

すると、ハードウェアキーボードレイアウトの変更 のウィンドウが表示されます。

日本語(英語)キーボードと表示されているプルダウンメニューから、変更したい言語のキーボードを選択します。

キーボードの種類を変更後、PCを再起動することで、キーボードの種類の変更の設定は完了です。

【Windows11】コントロールパネルの表示

Windows11では、コントロールパネルの表示が少しわかりづらくなっています。

コントロールパネルを表示するには、スタートボタンをクリック後、スタート画面右上のすべてのアプリをクリックします。

次に表示された画面の中から Windows ツール を探してクリックします。

Windowsツールウィンドウの中にコントロールパネルが含まれているので、これをクリックするとコントロールパネルが表示されます。

表示されたコントロールパネル

 

実際には上記方法は手数が多いので、タスクバーにある検索からコントロールパネルを検索して表示する法が簡単だと思います。

タスクバーにある検索をクリックし、検索画面を表示します。

検索するには、ここに入力します の部分にコントロールパネル(control panel)の文字の一部分、例えば

コントロール

とか

cont

と入力すると、検索候補の結果であるコントロールパネルが表示されるので、これをクリックすれば、コントロールパネルが表示されます。

コントロールパネルを比較的実行するのであれば、右側に表示されているスタートにピン留めするをクリックすると、コントロールパネルがスタート画面に表示されるので、便利だと思います。

スタートにコントロールパネルをピン留めした状態↓

【Windows11】タスクマネージャーの表示

Windows10ではタスクマネージャーを表示するときは、タスクバーを右クリックして、表示されたメニューからタスクマネージャーを表示していたのですが、Windows11ではタスクバーを右クリックしてもタスクバーの設定のメニューしか表示されません。

※最近版のWindows11では、タスクバーの右ボタンクリックでタスクマネージャーが表示されるようになりました。

 

Windows11でタスクマネージャーを表示するには、スタートボタンクリックします。

すると、以下のようなメニューからタスクマネージャーを選択します。

すると見慣れた感じのタスクマネージャーが表示されます。

キー操作でタスクマネージャーを表示する方法 その1

Cntl + Shift + Esc キーを入力すると直接タスクマネージャーが起動します。

キー操作でタスクマネージャーを表示する方法 その2

Cntl + Alt + Delete キーを入力すると、メニューが表示されるので、タスクマネージャーを選択し、タスクマネージャーを表示します。

【Windows11】環境変数の設定

環境変数の設定方法は、OSがバージョンアップされるたびに、いつもどこかへ行ってしまう、もはや恒例行事になっていますが、Windows11においても、環境変数の設定方法が変更されていました。

設定方法は、スタートボタンを右クリックし、表示されたメニューの設定をクリックします。

表示された設定ウィンドウの左側のシステムを選択し、右側に表示されているメニューを下の方へスクロールし、バージョン情報をクリックします。

すると、システムの詳細設定が表示されているので、この部分をクリックします。

これでようやく見慣れた画面が表示されるので、環境変数の部分をクリックします。

あとは、従来通りに Path を編集するのであれば、Pathの部分をクリックし、編集ボタンをクリックします。

あとは、新規ボタンをクリックし、追加したいPathを追加します。

【Windows11】シャットダウン、スリープ、再起動

Windows11でシャットダウン、スリープ、再起動をする方法として、最初に見つかったやり方は

スタートボタンクリック→電源ボタンをクリック→シャットダウン、スリープ、再起動

だったのですが、スタートボタンから電源ボタンまでが、少し距離があるので面倒くさい。

そこで、スタートボタンを右クリックするとメニューが表示され、

スタートボタンのクリック→シャットダウンまたはサインアウト→シャットダウン,スリープ,再起動

の順でクリックすると、マウスのストロークが短くて済むので、こっちの方が良さげ。

【Windows11】スタートメニューの変更点

Windows11では、デザインが洗練された感じがするぐらいで、さほど変更点は感じられませんが、スタートメニューは大きく変更されています。

スタートの画面右下にシャットダウン、スリープ、再起動の電源ボタンが配置されています。

画面上側には、比較的使用頻度の高いプログラムを配置することができます。

Windows10のようなプログラム一覧は、画面右上のすべてのアプリをクリックすると表示されます。

使用頻度の高いアプリは、アプリの名前を右クリックし、スタートにピン留めするをすると良いでしょう。

スタートにピン留めすると、最初のスタート画面上部のピン留め済みの部分にアプリが表示されます。

ピン留め済みのアプリのアイコンは、ドラッグ&ドロップで自由に配置する事ができるので、自分好みで配置するとよいかと思います。

最終的に、私はこのような↓配置にしました。

ここまですると、Windows10よりも使いやすいかも?と思えてきます。

スタートボタンを左側に揃える

画面は、Preview版ですが、以下のように行います。

タスクバーを右クリックし、タスクバーの設定をクリック後、右側の画面をスクロールして、タスクバーの動作 の部分を展開し、タスクバーの配置 の部分を左揃えにします。

すると、スタートボタンが左側に配置してくれます。

ただし、タスクバーそのものは下側のみの配置となり、上や右、左への配置はできません。

電源ボタン横にフォルダへのリンクを追加

スタート画面の電源ボタンの左横に、下図のよにフォルダへのリンクボタンを追加する事もできます。

追加方法は、スタートボタン右クリックし、メニューの設定をクリックし、設定画面を開きます。

設定画面左側の個人用設定をクリックし、右側のスタートをクリックします。

次にFoldersをクリックします。※画面はPreview版のため変更になる可能性があります。

すると、設定、エクスプローラ、ドキュメントなどの表示があるので、右側のオン/オフを切り替えて、電源ボタン左側に表示する項目を選択します。

すると、最初に示したように、電源ボタンの左側に各種フォルダへのリンクが表示されます。

まとめ

2021年6月現在では、Windows11はPreview版であるため、まだ英語の表記もいくつか残っていますが、新しいスタートにピン留めする(スタート画面の上に表示する)の機能はどことなくスマホ感覚で、慣れてくると便利に感じます。デスクトップ画面にショートカットを配置するより便利な気もしています。

Windows11 Preview版が公開されました

2021年6月29日(日本時間)、Windows11のPreview版が公開されました。

Windows11をインストールするにはCPUやグラフィックなどのハード的制約が厳しめだったのですが、私の場合、メインで使用しているデスクトップPCはNGで、あまり使っていなかったSurface6がアップデート可能でした。

Preview版のインストールは、スタートボタン→設定からWindows Insider Program で Devチャンネル に設定すると、Windows Updateから行うことができます。

バージョンが10.xxなので、Windows10?かとも思いましたが、このバージョンがWindows11のPreview版でした。
ファイルのダウンロード、インストールには、私の環境で約1時間半で完了しました。

インストールか完了するのを、ひたすら待って、待望のWindows11のデスクトップ画面です。

デスクトップのアイコンは、Windows10の状態がそのまま引き継がれていました。

第一印象は、スタートボタンが真ん中に来て、なんとなくMacっぽくなったぐらいで、あとは、そんなに違和感なく操作できます。

Windows11の新機能でもある、ウィンドウのレイアウトの設定(スナップレイアウト)については、ウィンドウの従来では、ウィンドウの最大化だった部分にマウスポインタを合わせると、ウィンドウレイアウトのパターンが表示されます。

そこで、実際に配置したい場所を選択します。

すると、選択した場所にウィンドウが配置してくれます。

解像度の高いモニタの場合、横3列のレイアウトも表示されます。

ただ、個人的には、大きい画面の場合、下図のように上2/5、下3/5ぐらいで横3列の配置が欲しかった。。

 

Windows11ではスタートボタンが中央に来たのも特徴の1つですが、従来のようにスタートボタンを左側に寄せる事も可能です。

設定方法は、タスクバーを右クリックし、タスクバーの設定をクリック後、右側の画面をスクロールして、Taskbar behaviors の部分を展開し、Taskbar alignment の部分を左揃えにします。

こうすると、ほぼ、スタートボタンが変わっただけぐらいの雰囲気になります。

 

ちなみに、エクスプローラを触っている途中にアイコンが砂嵐状態になり、ハングアップする事がありました。まぁ、Preview版なので、そのくらいの事は覚悟していますが。。

【Python/Pillow(PIL)】カラー,モノクロ,HSVなどの変換

PythonのPillowでカラー画像からモノクロ画像などへの変換は convert関数を用います。

 

カラー画像からモノクロ画像への変換は以下のように行います。

from PIL import Image

img_rgb = Image.open("Parrots.bmp")
img_rgb.show()

# グレースケール("L")へ変換
img_gray = img_rgb.convert("L")
img_gray.show()

# モノクロ("1")へ変換
img_mono = img_rgb.convert("1")
img_mono.show()

実行結果

Pillowでは、”モノクロ”と言っても、0~255までの256諧調の輝度値を持ったグレースケール(“L”)と、黒(0)と白(1)の2諧調のモノクロ(“1”)とがあります。
上図の左側がグレースケール(“L”)で、右側がモノクロ(“1”)です。

モノクロ(“1”)の画像は一般にディザ(dither)と呼ばれる処理で、新聞の写真のように1色のインクしか無い印刷で使われます。このディザの処理にもいくつか手法があり、Pillowでは誤差拡散法という処理になっています。(参考書籍:ディジタル画像処理

ただし、このモノクロ(“1”)は、画像処理的には、あまり出番が無いので、私は特に断りの無い限り、グレースケール画像の事を「モノクロ画像」と呼んでいます。

今回は、カラー画像(“RGB”)からグレースケール画像(“L”)へ変換しましたが、convert関数の引数の部分を変えることで、他の色空間への変換も可能です。

どのような色空間へ変換できるか?は以下のページを参照ください。

【Python/Pillow(PIL)】画像データフォーマット(mode)

 

このconvert関数はOpenCVでいうところの cvtColor関数に相当しますが、個人的に興味があったのが、色相のように0~360°の値を持つ色空間は、どのように変換されるのか?ということ。

OpenCVでは、0~360°を8bitのデータに収まるように、0~180へ変換する COLOR_BGR2HSV と0~255へ変換する COLOR_BGR2HSV_FULL の2種類があります。

そこで、Pillowではどのように変換されるのか?確認してみました。

 

評価プログラム

from PIL import Image

img_rgb = Image.new("RGB", (3, 1))

# R, G, Bの値を設定
img_rgb.putpixel((0,0), (255, 0, 0))
img_rgb.putpixel((1,0), (0, 255, 0))
img_rgb.putpixel((2,0), (0, 0, 255))

# RGB -> HSV
img_hsv = img_rgb.convert("HSV")

# 各画素の値を取得
print("(0, 0)", img_hsv.getpixel((0, 0)))
print("(1, 0)", img_hsv.getpixel((1, 0)))
print("(2, 0)", img_hsv.getpixel((2, 0)))

実行結果

この結果から、0°は0へ、120°は85へ、240°は170へ変換されていることから、0~360°の角度は0~255へ変換されていることが分かります。

Pillowで行われる色相の角度の変換は、OpenCVの COLOR_BGR2HSV_FULL 相当で変換されている!という事でした。

内挿と外挿

内挿(Interpolation)外挿(Extrapolation)という言葉は、最近ではDeep Learning関連で目にする事が多い気がしますが、内挿・外挿とは、データを近似し、データ以外の場所を推定する際に、データの範囲内を推定することを内挿といい、データの範囲外を推定することを外挿といいます。

しかしながら、一般的に外挿で推定した値は必ずしも正しいとは限らないため、しない方が良いといわれます。以下に推定した値が正しくならない例を示します。

外挿した値が近似したモデル(近似式)とはズレる場合

下図の例は、かなり恣意的ではありますが、データを近似し、データの範囲内で他のデータを推定する内挿の場合では誤差は少ないですが、データの範囲外を推定する外挿ではデータの変化の傾向が異なり誤差が大きくなる可能性もあります。

このことはDeep Learningでも言われる事ですが、データを学習する際は、様々な状態のデータを学習させ、推論する際は学習させたデータの範囲内に留めておく必要があります。

外挿となってしまう場合は学習データを追加し、推論するデータがデータの範囲内になるようする必要があります。

 

外挿は誤差が大きくなりやすい

前項の例は、内挿と外挿のデータの傾向が異なる場合の例として示しましたが、内挿と外挿とでデータの傾向が同じ場合でも外挿の方が誤差が大きくなる場合があります。

例えば、内挿、外挿とも直線的に変化することが分かっている場合に、2点のデータを使って直線で近似する場合もよくあります。

しかしながら、近似に用いたデータにも、少なからず誤差が含まれる場合がほとんどです。

この時に2点のデータから計算した直線も、下図のように誤差が出てしまいます。

上図を見ても分かるように、近似に持ちるデータに誤差が含まれたとしても、内挿の範囲内であれば、計算した直線もデータの誤差の範囲内に収まりますが、外挿になるとデータの誤差が増幅され、計算した直線は、データの誤差以上の誤差となってしまいます。

 

以上のことから、内挿はいいけれども、外挿をする場合には注意が必要となります。

【Python/tkinter】Entry(テキストボックス)

tkinterでテキストボックスはEntryと言います。

以下に簡単なサンプルを示します。

 

(実行結果)

(サンプルプログラム)

import tkinter as tk

class Application(tk.Frame):

    def __init__(self, master = None):
        super().__init__(master)

        self.master.title("Entryの作成")    # ウィンドウタイトル
        self.master.geometry("300x100")     # ウィンドウサイズ(幅x高さ)

        # 表示する値
        self.entry_text = tk.StringVar() 
        # Entry(テクストボックス)の作成
        entry = tk.Entry(self.master,
            width = 30,         # ウィジェットの幅(文字数で指定)
            justify = tk.RIGHT, # tk.RIGHT:右寄せ、tk.LEFT:左寄せ、tk.CENTER:中央寄せ
            textvariable = self.entry_text # 表示する値
            )
        # ボタンの作成
        btn_input = tk.Button(self.master, text = "入力", command = self.btn_input_click)
        btn_clear = tk.Button(self.master, text = "クリア", command = self.btn_clear_click)

        entry.pack()
        btn_input.pack()
        btn_clear.pack()

    def btn_input_click(self):
        ''' Entryに入力された値を表示 '''
        print("Text = ", self.entry_text.get())

    def btn_clear_click(self):
        ''' Entryのクリア '''
        self.entry_text.set("")

if __name__ == "__main__":
    root = tk.Tk()
    app = Application(master = root)
    app.mainloop()

ポイント

  • Entryの値はtextvariableにStringVarクラスオブジェクトを指定し、Entryのテキストの取得はStringVarクラスオブジェクト.get()、設定はStringVarクラスオブジェクト.set()で行います。
  • 他のウィジェットには標準的にはるcommandオプションがEntryにはありません。
    その代わりにvalidatecommandでテキストが変更されたときの処理を行います。
    詳細は後半で説明します。

オプション

オプション名 説明
background 通常時(クリックされていないとき)の背景色を指定します。(bgと同じ)
bd 枠線の太さを指定します。
bg backgroundと同じ
borderwidth bdと同じ
cursor ウィジェット上にマウスポインタがある際のカーソルの種類を指定します。
(参考)https://tkdocs.com/shipman/cursors.html
disabledbackground stateオプションで無効(DISABLED)に設定している際の背景色を設定します。
disabledforeground stateオプションで無効(DISABLED)に設定している際の文字色を設定します。
exportselection
fg 表示する文字色を指定します。(foregroundと同じ)
font 表示する文字のフォントを指定します。
foreground fgと同じ
highlightbackground
highlightcolor
highlightthickness
insertbackground
insertborderwidth
insertofftime
insertontime
insertwidth テキスト挿入時のカーソルの太さを指定します。
invalidcommand
invcmd
justify 文字寄せ方向を指定します。
【設定値】左寄せ(tk.LEFT), 中央寄せ(tk.CENTER), 右寄せ(tk.RIGHT)
relief テキストボックスの枠線のスタイルを指定します。
【設定値】tk.RAISED, tk.GROOVE, tk.SUNKEN, tk.RIDGE, tk.FLAT
【初期値】tk.FLAT(枠線なし)
selectbackground
selectborderwidth
selectforeground
show テキストボックスに表示する文字列を指定します。
(使用例)パスワード入力のとき
show = “*”
state ウィジェットの有効/無効(操作できない状態)を指定します。
【設定値】tk.NORMAL, tk.DISABLED
【初期値】tk.NORMAL
takefocus
textvariable Entryの値を取得、設定するためのStringVarクラスのインスタンスを指定します。
他にもIntVar, DoubleVar, BooleanVarの指定が可能です。
これらを指定することで、入力する値の型制限が可能になります。
validate 検証の対象を指定します。
‘none’, ‘key’, ‘focus’, ‘focusin’, ‘focusout’, ‘all’ のいづれか
validatecommand 検証の登録を行います。
width テキストボックスの幅を文字数で指定します。
xscrollcommand

メソッド

オプション名 説明
delete(first, last=None) 最初と最後の文字の位置を指定してテキストを削除します。
(例)最初から最後までを削除する方法
entry.delete(0, tk.END)
get() テキストボックスの文字列を取得します。
icursor(index) 挿入するカーソルの位置を指定します。
index(index)
insert(index, s) 指定した位置に文字を挿入します。
select_adjust ( index ) カーソルの位置から指定した位置までを選択します。
select_clear() テキストの選択状態を解除します。
select_from ( index )
select_present() 文字が選択されている場合はTrue, 選択されていない場合はFalseを返します。
select_range ( start, end ) 開始位置、終了位置を指定して文字を選択します。
select_to ( index ) 指定位置からカーソルの位置までを選択します。
xview ( index ) 横方向のスクロール位置を指定します。
xview_scroll ( number, what )

 

入力検証

テキストボックスに文字を入力してから表示するまでの間に、実際に表示するかどうか?を制御することが可能です。例えば、文字数の制限や、数字のみの入力などの制限をかけることができます。

 

(サンプルプログラム)テキストボックスの入力文字数を5文字までに制限する例

import tkinter as tk

class Application(tk.Frame):

    def __init__(self, master = None):
        super().__init__(master)

        self.master.title("Entryの作成")     # ウィンドウタイトル
        self.master.geometry("300x100")       # ウィンドウサイズ(幅x高さ)

        validate_command = self.master.register(self.enty_validate)
        
        entry = tk.Entry(self.master,
            width = 30,
            validate='all', # 検証をどのタイミングで行うか?を指定します
            validatecommand = (
                validate_command, 
                '%d',   # アクションの種類 1:挿入、0:削除、-1:再検証
                '%i',   # 挿入/削除される文字の位置 挿入/削除されない場合は-1
                '%P',   # テキスト編集後の文字列
                '%s',   # テキスト編集前の文字列
                '%S',   # 挿入/削除される文字列
                '%v',   # validateオプションで指定した種類
                '%V',   # 実際に実施されたvalidateの種類
                '%W'    # Entryウィジェットの名前
                )
        )
        entry.pack()

    def enty_validate(self, action, index, prevalidation, current, test, validata_option, condition, name):
        '''入力検証'''
        print("enty_validate", action, index, prevalidation, current, test, validata_option, condition, name)

        if len(prevalidation) > 5:
            # 入力文字数を5文字までに制限
            # Falseを返すとテキストボックスに入力した文字は反映されない
            return False
        else:
            # Trueを返すとテキストボックスに入力した文字が反映される
            return True

if __name__ == "__main__":
    root = tk.Tk()
    app = Application(master = root)
    app.mainloop()

上記サンプルは、すべてのタイミング(validateオプション)で、すべての情報を取得していますが、’%P’や’%S’だけを使用しても構いません。ただし、オプションの数に合わせて、呼ばれるメソッド(上記サンプルでは entry_validate)の引数の数を調整する必要があります。

validateオプション

オプション名 説明
none 検証なし(初期値)
key キー入力時
focus フォーカス時?
focusin フォーカスを取得したとき
focusout フォーカスを失ったとき
all 全て

validatecommandオプション

オプション名 説明
‘%d’ アクションの種類 1:挿入、0:削除、-1:再検証
‘%i’ 挿入/削除される文字の位置 挿入/削除されない場合は-1
‘%P’ テキスト編集後の文字列
‘%s’ テキスト編集前の文字列
‘%S’ 挿入/削除される文字列
‘%v’ validateオプションで指定した種類
‘%V’ 実際に実施されたvalidateの種類
‘%W’ Entryウィジェットの名前

 

参考

https://tcl.tk/man/tcl8.6/TkCmd/ttk_entry.htm

単位ベクトルとその応用

単位ベクトルとは?長さが1のベクトルとなります。

例えば、ベクトルa の要素を

としたときの単位ベクトルu は

となります。

これを図示すると、

のようになります。

これを学生の時に教わったときには簡単!とだけ思っていましたが、大人になると、

で、単位ベクトルの何がいいの??

となります。

しかし、実は、単位ベクトルには教わった記憶の無い(少なくとも覚えていない)大事な特徴がありました。
それは

単位ベクトルと任意ベクトルの内積を計算すると、
単位ベクトル方向の成分(大きさ)が取得できる!

ということです。

例えば、ベクトルa の単位ベクトルu の方向の成分は、ベクトルa と単位ベクトルu の内積の絶対値で求まります。

なぜ、そうなるのか?は比較的簡単で、ベクトルa の単位ベクトルu の方向の大きさを L として、ベクトルa の単位ベクトルu の成す角をθとして、図示すると

となります。

Lは図から

となるのは、図を見れば分かると思います。

ここで、ベクトルa と単位ベクトルu の内積は

となりますが、単位ベクトルの大きさは 1 なので、

となり、先ほどの L と一致する事が分かります。

ちなみに、θの角度が 90° ~270°の範囲の場合、cosの値がマイナスとなるため、ベクトルu の方向の大きさを求める場合は、内積の絶対値を求めています。

また、あえて絶対値を取らないで、内積が正の場合は、ベクトルa は単位ベクトルu の向きの方向にあり、内積が負の場合はベクトルa は単位ベクトルu の逆向きになっていることを確認する事もあります。

これを応用すると、内積は二次元のベクトルだけではなく、3次元、4次元・・・とn次元ベクトルに拡張できるので、応用範囲は広くあります。

例えば三次元ベクトルの場合ですが、三次元平面と点の距離を求める方法について説明したいと思います。

平面の方程式は

のように表されますが、この法線ベクトルn は

となります。

この法線ベクトルの単位ベクトルは、各要素を大きさで割って

となります。

あとは、平面上の点(x0, y0, z0)と任意の点(x1, y1, z1)からなるベクトル

との内積を計算すると、

となって、この値の絶対値を取ると平面と点との距離となります。

逆に絶対値を取らないと、値が正の場合、点は法線ベクトルの方向にあり、値が負の場合は点が法線ベクトルとは逆の方向にある事が分かります。値が0の場合は、点は平面上にある事になります。

 

まとめ

・単位ベクトルと任意ベクトルとの内積を計算すると、単位ベクトル方向の大きさを求めることができる。

というのが、大事な特徴です。

私が大人になってから単位ベクトルを使った事があるのは、平面と点との距離のように、単位ベクトルの方向の大きさを求めるような使い方しかないかも?
広い意味で言うと、フーリエ変換も単位ベクトルが係わっていたりもしますが。。

 

【Python/Pillow(PIL)】画像の輝度値をCSVファイルに保存/読込

画像処理をしていると、画像の輝度値をCSVファイル保存して、輝度値そのものや、輝度値の分布などを見たくなります。

Pythonにはcsvモジュールがあり、比較的簡単に画像の輝度値をCSVファイルに保存することができます。

輝度値をCSVファイルに保存するサンプルを示します。

ただし、モノクロとカラーの画像が混在すると難しいので、モノクロ限定とします。

 

(参考)csvモジュール

https://docs.python.org/ja/3/library/csv.html

輝度値の取得はPillowのgetdata()メソッドを使用します。

【Python/Pillow(PIL)】画像の輝度値の取得/設定

 

輝度値のCSVファイル保存

import csv
from PIL import Image

# 画像読込
img = Image.open("Mandrill.bmp")

# モノクロ画像へ変換
img = img.convert("L")
width, height = img.size

########################################################
# 輝度値の取得、CSVファイルに保存

# 画像の輝度値をlistで取得
data = list(img.getdata())

# 輝度値をCSVファイルで保存
with open('image_data.csv', 'w', newline='') as csvfile:
    spamwriter  = csv.writer(csvfile)

    # 画像データを一行ごと書き込み
    x = 0
    for y in range(height):
        # 一行分のデータ
        line_data = data[x:x+width]
        # 一行分のデータを書き込み
        spamwriter.writerow(line_data)
        x += width

CSVファイルをエクセルで開くと以下のようになります。

 

CSVファイルを開き画像へ変換

CSVファイルを開くのも保存と同様にcsvモジュールを用います。

ただし、CSVファイルは前項で保存したCSVファイルのように二次元でモノクロの輝度値が配置されたファイルとします。

csvモジュールでCSVファイルを開いたとき、CSVファイルの各値は文字列のリストに格納されるので、各要素をint型に変換している部分がポイントとなります。

import csv
from PIL import Image

########################################################
# CSVファイルを開く、Pillowの画像データに変換
load_data = []
# CSVファイルを開く
with open('image_data.csv', newline='') as csvfile:
    # ファイルの読込
    spamreader = csv.reader(csvfile)

    height = 0
    # データを一行ごとにリストに追加
    for line_data in spamreader:
        # 各要素の文字列をintに変換
        row = [int(val) for val in line_data]
        # リストに行データを追加
        load_data += row
        # 行数(画像の高さ)カウント
        height += 1

# 画像の幅を計算
width = len(load_data) / height

# 画像を作成
csv_image = Image.new("L", (int(width), height))
# データを読込(輝度値が格納されたリストのデータをPillowの画像データに設定)
csv_image.putdata(load_data)

# 画像の表示
csv_image.show()

処理結果は以下のようにCSVファイルを開くと、画像が表示されます。

CSVファイルをエクセルで見やすくする

CSVファイルをエクセルで開くと、こんな感じ↓で味気ないものとなります。

これを画像らしく、少し見やすくします。

まず、セルのサイズを正方形に近くなるように列の幅を調整します。

輝度値が記載されている列を全て選択し、列の部分を右ボタンでクリックし、列の幅を選択します。

表示された設定画面で、列の幅に2.7を入力します。

するとセルのサイズがだいたい正方形になります。

さらにセルに色を付けて画像らしくします。

輝度値が記載されているセルを全て選択し、ホーム→条件付き書式→カラースケール→その他のルールと選択します。

表示されたウィンドウで、最小値、最大値の部分を以下のように設定します。

最小値 最大値
種類 数値 数値
0 255

すると、セルの背景色が画像らしくなります。

この表示を縮小すると、まさに画像になってます。

エクセルで画像の輝度値を編集

試しに保存されたCSVファイルをエクセルで開き、画像の輝度値をじかに編集してみます。

これをCSVファイルに保存するのですが、エクセルのCSVファイル形式には CSV UTF-8 と CSV があるので、何も付いていない CSV(コンマ区切り)(*.csv)の方を選択して、CSVファイルに保存します。

このCSVファイルを、先ほどのCSVファイルを CSVファイルを開き画像へ変換 のプログラムで開くと以下のようになります。

エクセルで画像を直接編集できるのは、ちょと楽しいのですが、エクセルで画像処理を本気でやろうとするのは大変なので、画像をCSVファイルに保存するときは、画像の輝度値を解析的に見る程度に留めておく事をお勧めします。

【Python/Pillow(PIL)】画像の輝度値の取得/設定

画像を開き輝度値を取得/設定するのは、画像処理を行う、はじめの一歩的な処理ですよね。

まずは、最も基本的なgetpixel/putpixelを使った方法を紹介します。

getpixel()を使った輝度値の取得

getpixel()の構文は以下の通りです。

value = Image.getpixel(xy)

xyは画像の左上を原点とするxy座標で、(x, y)のようにタプルで指定します。

戻り値が指定した画像の輝度値となり、モノクロ画像の場合は、指定した座標の輝度値が戻り、カラーの場合は(r, g, b)のように3つの要素のタプルが戻ってきます。カラー画像でも ‘RGBA’ のように透過付きの画像データの場合は(r, g, b, a)のように4つの要素のタプルが戻ります。

実行例

putpixel()を使った輝度値の設定

putpixel()の構文は以下の通りです。

Image.putpixel(xy, value)

引数はgetpixel()と同じ用に、xyには(x, y)のように座標をタプルを指定します。valueの部分には、モノクロの場合は、指定座標の輝度値を、カラーの場合は(r, g, b)もしくは(r, g, b, a)のように輝度値をタプルで指定します。

さらに輝度値の値に0~255の範囲を超えて指定した場合、値が負の場合は0、値が256以上の場合は255に修正されます。

実行例

処理の高速化の検討

C#でも似た関数は処理時間が遅いで有名でしたが、getpixel()、putpixel()も処理時間が遅いらしい。

そこでいくつかの輝度値の取得/設定方法を試して処理時間の比較を行ってみたいと思います。

 

まずは処理時間の基準となるgetpixel(), putpixel() を使った処理時間を計測します。

輝度値を取得し、コントラストを調整し、輝度値を画像に設定するサンプルです。

from PIL import Image
import numpy as np
import time

# 元画像を保持
img_original = Image.open("Mandrill.bmp")
img = img_original.copy()
width, height = img.size

############################################
# getpixel(), putpixel() を使った方法
start = time.perf_counter()

for y in range(height):
    for x in range(width):
        r, g, b = img.getpixel((x, y))
        img.putpixel((x, y), (r * 5 - 500, g * 5 - 500, b * 5 - 500))

print("getpixel(), putpixel() を使った方法\t", (time.perf_counter() - start) * 1000, "msec")

上記プログラムを実行すると、以下のようになります。

 

処理前画像

処理後画像

 

この他に以下の方法を試してみます。

  • getdata(), putdata() を使った方法
    getdata()は画像全体の輝度値を画像の左上から順に各画素の輝度値(R, G, B)の値がタプルの一次元のリストで取得します。
    putdata()は輝度値(R, G, B)のタプルの一次元のリストを指定し、画像全体の輝度値を設定します。
  • numpy を使った方法
    Pillowの画像データからNumPyの画像データへ変換し、NumPyデータを処理し、Pillowの画像データに戻しています。
  • Pillow <-> numpy の相互変換だけの時間
    PillowとNumPyの画像データの変換時間を参考に計測します。
  • point() を使った方法
    輝度値の取得/設定の処理時間の評価の趣旨から外れますが、画像処理に周辺画素の輝度値を用いない場合、LUT(Look Up Table)を用いると高速に処理が行えるため、Pillowのpoint()メソッドでLUT変換を行った処理時間を参考に計測しています。

 

使用した全プログラム

from PIL import Image
import numpy as np
import time

# 元画像を保持
img_original = Image.open("Mandrill.bmp")
img = img_original.copy()
width, height = img.size

############################################
# getpixel(), putpixel() を使った方法
start = time.perf_counter()

for y in range(height):
    for x in range(width):
        r, g, b = img.getpixel((x, y))
        img.putpixel((x, y), (r * 5 - 500, g * 5 - 500, b * 5 - 500))

print("getpixel(), putpixel() を使った方法\t", (time.perf_counter() - start) * 1000, "msec")

############################################
# getdata(), putdata() を使った方法
img = img_original.copy()
start = time.perf_counter()

data = img.getdata()
# 処理後のデータをlistで確保
data_dst = [None] * len(data)

for y in range(height):
    for x in range(width):
        r, g, b = data[x + y * width]
        data_dst[x + y * width] = (r * 5 - 500, g * 5 - 500, b * 5 - 500)

img.putdata(data_dst)

print("getdata(), putdata() を使った方法\t", (time.perf_counter() - start) * 1000, "msec")

############################################
# numpy を使った方法
img = img_original.copy()
start = time.perf_counter()
# pillow → numpyへ変換
numpy_iamge = np.array(img)

for y in range(height):
    for x in range(width):
        r = numpy_iamge[y, x, 0]
        g = numpy_iamge[y, x, 1]
        b = numpy_iamge[y, x, 2]
        r = r * 5 - 500
        g = g * 5 - 500
        b = b * 5 - 500
        if r < 0:
            r = 0
        if g < 0:
            g = 0
        if b < 0: b = 0 if r > 255:
            r = 255
        if g > 255:
            g = 255
        if b > 255:
            b = 255
        numpy_iamge[y, x, 0] = r
        numpy_iamge[y, x, 1] = g
        numpy_iamge[y, x, 2] = b

# numpy → pillowへ変換
img = Image.fromarray(numpy_iamge)

print("numpy を使った方法\t\t\t", (time.perf_counter() - start) * 1000, "msec")

############################################
# numpyらしい処理 その1
# numpyの配列(ndarray)をそのまま演算する
# 0~255に制限するのにclipを用いる
img = img_original.copy()
start = time.perf_counter()
# pillow → numpyへ変換(計算後、負になるのでint32型へ変換)
numpy_iamge = np.array(img, dtype = np.int32)

dst_img = numpy_iamge * 5 - 500

# 0~255のuint8型へ変換
dst_img = dst_img.clip(0, 255).astype(np.uint8)

# numpy → pillowへ変換
img = Image.fromarray(dst_img)

print("numpyらしい処理 その1\t\t\t", (time.perf_counter() - start) * 1000, "msec")

############################################
# numpyらしい処理 その2
# LUTを使った変換
img = img_original.copy()
start = time.perf_counter()
# pillow → numpyへ変換
numpy_iamge = np.array(img)

# LUT(Look Up Table)の作成
lut = np.empty(256, dtype = np.uint8)
for i in range(256):
    val = i * 5 - 500
    if val < 0:
        val = 0
    if val > 255:
        val = 255
    lut[i] = val

# LUTを介して変換
numpy_iamge = lut[numpy_iamge]

# numpy → pillowへ変換
img = Image.fromarray(numpy_iamge)

print("numpyらしい処理 その2\t\t\t", (time.perf_counter() - start) * 1000, "msec")

############################################
# Pillow ⇔ numpy の相互変換だけの時間
img = img_original.copy()
start = time.perf_counter()
# pillow → numpyへ変換
numpy_iamge = np.array(img)
# numpy → pillowへ変換
img = Image.fromarray(numpy_iamge)

print("Pillow ⇔ numpy の相互変換だけの時間\t", (time.perf_counter() - start) * 1000, "msec")

############################################
# point() を使った方法
img = img_original.copy()
start = time.perf_counter()

# LUT(Look Up Table)の作成
lut = []
for i in range(256):
    val = i * 5 - 500
    if val < 0:
        val = 0 
    if val > 255:
        val = 255
    lut.append(val)
# R, G, Bに同じLUTを使用
lut = lut * 3

# pointメソッドでLUT変換を行う
img = img.point(lut)

print("point() を使った方法\t\t\t", (time.perf_counter() - start) * 1000, "msec")

処理時間の比較(使用した画像は256×256の24bitカラー画像)

方法 処理時間
getpixel(), putpixel() を使った方法 99.223 msec
getdata(), putdata() を使った方法 37.833 msec
numpy を使った方法 440.229 msec
(参考)numpyらしい処理 その1 2.081 msec
(参考)numpyらしい処理 その2 1.041 msec
(参考)Pillow <-> numpy の相互変換だけの時間 0.254 msec
(参考)point() を使った方法 0.156 msec

まとめ

輝度値の取得/設定を行う処理については、getdata(), putdata() を使った方法が一番速い結果となりました。

numpyを使うと、もう少し速いかと思っていたのですが、あまりに遅かったので、Pillow <-> numpy の相互変換の処理時間を計測してみましたが、やはり画像処理している部分が遅い事が分かりました。
numpyの処理だけ、0~255に輝度値が入るようにif文で処理をしていますが、これは、numpyのデータがuint8(8bitの符号なし整数)になるため、この処理を入れないと、下図のように変な画像になってしまいます。

逆に、getpixel(), putpixel() も getdata(), putdata() も、輝度値に0~255の範囲外の値を指定しても0~255の範囲に調整してくれるので、これは便利です。

ただし、numpyに画像データを変換すると、OpenCVも使えるので、numpyで画像処理するなら、使える処理があれば極力OpenCVを使うようにするとよいでしょうね。
numpyのデータはfor文で値を参照すると、どうしても遅いようです。

また、参考にpoint()メソッドによりLUTを使った処理時間を計測してみましたが、こちらは爆速でした!

今回はpoint()メソッドを使いましたが、他にもPillowでできる画像処理のメソッドが用意されているので、おいおい紹介したいと思います。

結局、Pythonでベタな画像処理をしてはいけないということですね。
OpenCVなどに無いオリジナルの画像処理をしたい場合は、やっぱりC言語のライブラリで処理を行う必要があるんでしょうね。

【Python】リスト(配列)の繰り返しの注意点

Pythonのlistで同じ要素を繰り返して書く場合は、リストの掛け算のように

data_list = [1, 2, 3] * 5
print(data_list)

とすると、

のようにリストの要素を繰り返したリストを取得することができます。

ただ、ここで注意したいのが、各要素のオブジェクト(メモリ)を繰り返してリストが生成されています。

試しに各要素のIDも繰り返されています。

つまり、同じメモリの値が繰り返されている事になります。

そのため、例えば、リストのリスト(二次元のリスト)を作成し、1つの要素だけを変更してみると、下図のように他の要素まで変更されてしまいます。

しかし、一見同じように一次元のリストの繰り返しでは、他の要素は変更されません。

この差は何なのか?というと、Pythonの語彙力がなくてうまく説明できないのですが、値を代入した時に変数のIDが変わるか?変わらないか?の違いによって、差が出ます。

例えば、変数に値を代入すると、IDの値も変わります。

しかし、リストの要素に値を代入しても、リストの変数のIDは変わりません。

こういう事をなんと言うのか???

 

という事で、リストやクラスオブジェクトを * を使ってリストの繰り返しを作る場合は、メモリも同じメモリが繰り返されている事に注意しておかないと、1つの要素を変更したときに他の要素も変更されてしまうので、気を付けましょう!

 

と、今日、この症状のバグにハマっていたので、戒めでこの記事を書いています。