知っていることだけ

勉強していて役に立つ、理解の助けになるようなポイントを書いていきます。

パターン認識の全容を分かりやすく

このページは以下の読者を対象にしています

パターン認識とは

入力された画像が、どの概念に属するかを答えること。 例) アルファベットの文字認識:入力文字パターンがA~Zのどれかを推測する。

パターン認識の基本構成

  1. 前処理(preprocessing):ノイズ除去、正規化をして認識をしやすくする。
    (正規化:大きさ、位置、傾きの調整など)
  2. 特徴抽出(feature extraction):入力データのうち、識別に必要な情報のみを抽出する。
    主に特徴ベクトルの形で抽出。機械学習を用いることがある。
    例) 数字の認識なら、線の位置と傾きだけで十分に思える。
  3. 識別処理:特徴ベクトルと識別辞書を照合して、所属パターンを答える。 (識別辞書:この特徴ベクトルを持つものはこのクラス、といったことが書かれてある辞書。)

識別処理で用いる手法

最近傍決定法

クラスタの重心のみを記憶。入力が最も近いクラスタを答える。

C言語 ナンプレを自動でとくプログラム

このページは以下の読者を対象にしています

プログラムの説明

全体の流れ:

  1. ファイルから問題を読み込む
  2. 問題を表示する
  3. 問題を解く
  4. 結果を表示する
    データ構造: 要素数81のint型の配列に数字を入れていく。空白は0を入れる。
    typedef int NumPlace[81];でNumPlace型を作った.
    素数とマスの関係は下記の通り
    0 1 2 3 4 5 6 7 8
    9 10 11 …

各関数詳細

問題読み込み関数:引数 出力先のNumPlace型変数
標準入力でファイル名を受け取る。
ファイルがなかった場合は強制終了ではなく、再度受け付け。
引数で指定されたNumPlace型の変数に書き込む。
空白には0,数字はint型。

ナンプレ表示関数:
引数で受け取ったNumPlace型を9*9のマスで表現。
見やすくなるよう、3*3マス毎に仕切りを表示。

数字探索関数:引数 min_num :探索する最小の数, index :対象のマス目(配列要素数), 探索するNumPlace
引数indexから行、列、3*3のブロックの左上のマス、対角線上にいるかを判断。
要素数11のint型の配列out_num[11]を用意して0に。out_num[10] は番兵
同じ行、列、3*3のブロック、対角線にいれば対角線上の数字を使い、順にout_num[数字]
= 1としていく。
min_num以上でout_num[要素] = 0となる要素が求める数字。10なら番兵なので0を返す.

次の空白探索関数:引数 index:探索開始のマス目, 探索するNumPlace
返り値:次の空白のマス目。無ければ81
NumPlaceの要素をIndexから順に1つずつ上げ、値が0となった要素を返す。
空白がなければ81を返す

解答関数:引数 index:現在マス目, 答えを入れる出力先NumPlace。返り値:代入した値
再帰を用いて解く。
マスindexに入る数字を数字探索関数により取得。0ならreturn。値があれば次の空白探索
関数を用いて、次の探索先を決定。無い(=81が返ってきた)ならreturn。
あれば再帰呼び出しする。再帰で呼び出した関数の返り値が0なら、前回入れた値より大
きな数字でマスindexに入る数字を探索。これを繰り返す。

入力ファイル

  • ;で始まる行はコメント.ファイル中に複数あって良い.
  • 1行9文字で9行のデータ.データの1行につき,ナンバープレースの1行を上から順番に書く.
  • 値がないところはスペースであける.
    5  6 ;
  1    2 ;
236  1 9 ;
  214 8  ;
      9 2;
  398 6  ;
;
127  8 3 ;61
  4    8 ;
    1  4 ;

これを好きなファイル名でプログラム場所に保存しておく。後でscanfでファイル名を入力する。

使用プログラム

gcc で動作確認済み

/*対角線ナンバープレースを解くプログラムを作成
 各行,各列で1~9の数字は重複しない
 太線で区切られた9マスでも,1~9の数字は重複し
ない
 2本の対角線上も1~9の数字が入る
 空いているマスを全部埋める
 問題ファイルを各自作成し,それぞれプログラム
で読み込ませて解を表示
 複数解がある場合は最初に見つかった1つを表
示すれば良い
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define STR_SIZE 256
/* データ構造:
要素数81のint型の配列に数字を入れていく。空白は0を入れる.
typedef int NumPlace[81];でNumPlace型を作った.
要素数とマスの関係は下図参照
0 1 2 3 4 5 6 7 8
9 10 11 … */
typedef int NumPlace[81];

// 問題読み込み関数:引数 出力先のNumPlace型変数
void GetProblem(NumPlace problem);
void ShowNumPlace(NumPlace numplace); //ナンプレ表示関数
//数字探索関数:引数 min_num :探索する最小の数, index :対象のマス目(配列要素数), 探索するNumPlace
int GetPossibleNum(int min_num, int index, NumPlace numplace);
/* 次の空白探索関数:引数  index:探索開始のマス目, 探索するNumPlace
返り値:次の空白のマス目。無ければ81
 */
int NextIndex(int index, NumPlace numplace);
/* 解答発見関数:引数 index:現在マス目, 答えを入れる出力先NumPlace。
返り値:代入した値
 */
int SetPossibleNum(int index, NumPlace numplace);
//問題を解く
void SolveNumPlace(NumPlace problem, NumPlace ans);

int main(int argc, char const *argv[])
{
    NumPlace problem;
    GetProblem(problem);   //ファイルから問題を読み込む
    ShowNumPlace(problem); //問題を表示する
    NumPlace ans;
    SolveNumPlace(problem, ans); //問題を解く
    ShowNumPlace(ans);           //結果を表示する
    return 0;
}

/* 問題読み込み関数:引数 出力先のNumPlace型変数
標準入力でファイル名を受け取る。
ファイルがなかった場合は強制終了ではなく、再度受け付け。
引数で指定されたNumPlace型の変数に書き込む。
空白には0,数字はint型
 */
void GetProblem(NumPlace problem) //問題を配列に収納
{
    for (int i = 0; i < 81; i++)
        problem[i] = 0; //0クリア
    FILE *fp = NULL;
    while (fp == NULL)
    {
        printf("File name of problem:");
        char filename[STR_SIZE] = {};
        scanf("%s", filename);
        if ((fp = fopen(filename, "r")) == NULL)
            fprintf(stderr, "error: there is no file named %s\r\n", filename);
    }
    char str[STR_SIZE];
    int posi = 0;
    while (fgets(str, sizeof(str) / sizeof(str[0]), fp) != NULL)
    {
        for (size_t i = 0; i < strlen(str); i++)
        {
            if (str[i] == ';' || str[i] == '\n')
                break;
            else if (str[i] == ' ')
                ++posi;
            else
                problem[posi++] = str[i] - '0';
        }
    }
    fclose(fp);
}
/* ナンプレ表示関数:
引数で受け取ったNumPlace型を9*9のマスで表現。
見やすくなるよう、3*3マス毎に仕切りを表示。
 */
void ShowNumPlace(NumPlace numplace)
{
    for (int i = 0; i < 9; i++)
    {
        if (i % 3 == 0)
        {
            for (int m = 0; m < 3; m++)
            {
                printf("+");
                for (int k = 0; k < 3 * 3; k++)
                    printf("-");
            }
            printf("+\n");
        }
        for (int j = 0; j < 9; j++)
        {
            int target = numplace[i * 9 + j];
            if (j % 3 == 0)
                printf("|");
            if (target == 0)
                printf("   ");
            else
                printf("% d ", target);
        }
        printf("|\n");
    }
    for (int m = 0; m < 3; m++)
    {
        printf("+");
        for (int k = 0; k < 3 * 3; k++)
            printf("-");
    }
    printf("+\n");
}
/* 数字探索関数:引数 min_num :探索する最小の数, 
                index :対象のマス目(配列要素数), 探索するNumPlace
引数indexから行、列、3*3のブロックの左上のマス、対角線上にいるかを判断。
要素数11のint型の配列out_num[11]を用意して0に。out_num[10] は番兵
同じ行、列、3*3のブロック、対角線にいれば対角線上の数字を使い、
順にout_num[数字] = 1としていく。
min_num以上でout_num[要素] = 0となる要素が求める数字。
10なら番兵なので0を返す.
 */
int GetPossibleNum(int min_num, int index, NumPlace numplace)
{
    int out_num[11] = {}; //1ならその数字は使えない. out_num[10] は番兵
    int row = index / 9;
    int columon = index % 9;
    int box = (columon / 3) * 3 + (row / 3) * 3 * 9; //boxの右上のindex
    // 横にみてすでにある数字を消す(配列の該当数字の要素を1に)
    for (int i = 0; i < 9; i++)
        out_num[numplace[row * 9 + i]] = 1;
    //縦に見る
    for (int i = 0; i < 9; i++)
        out_num[numplace[columon + i * 9]] = 1;
    //box内を見る
    for (int i = 0; i < 3; i++)
    {
        for (int k = 0; k < 3; k++)
            out_num[numplace[box + k + i * 9]] = 1;
    }
    // 右下への斜めを見る
    if (row == columon)
    {
        for (int i = 0; i < 9; i++)
            out_num[numplace[i + i * 9]] = 1;
    }
    //左下への斜めを見る
    if ((row + columon) == 8)
    {
        for (int i = 0; i < 9; i++)
            out_num[numplace[8 * (i + 1)]] = 1;
    }

    //min_num以上の値で可能性のあるものを返す
    int ans = min_num;
    while (out_num[ans] == 1)
        ++ans;
    if (ans > 9)
        ans = 0;
    return ans;
}
/* 次の空白探索関数:引数  index:探索開始のマス目, 探索するNumPlace
返り値:次の空白のマス目。無ければ81
NumPlaceの要素をIndexから順に1つずつ上げ、値が0となった要素を返す。
空白がなければ81を返す
 */
int NextIndex(int index, NumPlace numplace)
{
    int i = index;
    for (; i < 81; i++)
    {
        if (numplace[i] == 0)
            break;
    }
    return i;
}
/* 解答発見関数:引数 index:現在マス目, 答えを入れる出力先NumPlace。
返り値:代入した値
再帰を用いて解く。
マスindexに入る数字を数字探索関数により取得。0ならreturn。
値があれば次の空白探索関数を用いて、次の探索先を決定。
無い(=81が返ってきた)ならreturn。あれば再帰呼び出しする。
再帰で呼び出した関数の返り値が0なら,
前回入れた値より大きな数字でマスindexに入る数字を探索.これを繰り返す。
 */
int SetPossibleNum(int index, NumPlace numplace)
{
    int num = 0;
    while (1)
    {
        num = GetPossibleNum(num + 1, index, numplace);
        numplace[index] = num;
        if (num == 0)
            break;
        int next_index = NextIndex(index + 1, numplace);
        if (next_index >= 81)
            break;
        if (SetPossibleNum(next_index, numplace) != 0)
            break;
    }
    return num;
}

void SolveNumPlace(NumPlace problem, NumPlace ans)
{
    for (int i = 0; i < 81; i++)
        ans[i] = problem[i];
    printf("\n\nans\n");
    SetPossibleNum(NextIndex(0, problem), ans);
    if (ans[0] == 0)
        printf("could not solve\n");
}

出力結果例

PS C:ディレクトリ場所> .\a.exe
File name of problem:q2.txt
+---------+---------+---------+
|         |    5    |    6    |
|       1 |         |    2    |
| 2  3  6 |       1 |    9    |
+---------+---------+---------+
|       2 | 1  4    | 8       |
|         |         | 9     2 |
|       3 | 9  8    | 6       |
+---------+---------+---------+
| 1  2  7 |       8 |    3    |
|       4 |         |    8    |
|         |    1    |    4    |
+---------+---------+---------+


ans
+---------+---------+---------+
| 9  7  8 | 2  5  4 | 3  6  1 |
| 5  4  1 | 3  6  9 | 7  2  8 |
| 2  3  6 | 8  7  1 | 4  9  5 |
+---------+---------+---------+
| 6  9  2 | 1  4  5 | 8  7  3 |
| 4  8  5 | 7  3  6 | 9  1  2 |
| 7  1  3 | 9  8  2 | 6  5  4 |
+---------+---------+---------+
| 1  2  7 | 4  9  8 | 5  3  6 |
| 3  6  4 | 5  2  7 | 1  8  9 |
| 8  5  9 | 6  1  3 | 2  4  7 |
+---------+---------+---------+

画像処理 移動平均法とメディアンフィルタの

このページは以下の読者を対象にしています

実験方法

  1. 画像を用意する。
  2. 粒状雑音を付加する。
  3. フィルタサイズを3×3,5×5,7×7,9×9に変えながら移動平均法とメディアンフィルタをかける
  4. 結果を画像ファイルとして出力する。

使用プログラム

visual studio 2017. opencv 4.10で動作確認済み

#include <opencv2/opencv.hpp>
#include <string> 
#include <iostream>
#include <random>
using namespace cv;
//粒状雑音用
std::mt19937 mt{ std::random_device{}() };

//粒状雑音作成
Mat AddNoise(const Mat & img, float ratio) {
    //乱数生成
    std::uniform_int_distribution<int> randrow(0, img.rows - 1);
    std::uniform_int_distribution<int> randcol(0, img.cols - 1);

    //点の数を計算
    int count = (int)(img.rows * img.cols * ratio);
    Mat ret = img.clone();
    //指定場所にノイズを加える
    for (int i = 0; i < count; i++) {
        int x = randcol(mt);
        int y = randrow(mt);
        ret.at<cv::Vec3b>(y, x) = cv::Vec3b(255, 255, 255);
    }
    return ret;
}


class Imgs {
public:
    static std::string  filepath;
    Imgs(std::string name, Mat img) :
        name(name), img(img) {};
    Imgs() {};
    Mat img;
    std::string name;
    void ImOutPut() {
        //imshow(name, img);//ウィンドウに表示する
        imwrite(filepath + name + ".png", img);//画像を出力
    }

};

class Filters {
public:
    static Imgs origin;
    static Imgs target;
    static int kernelsize;

    Imgs filteredimg;
    Imgs abs;
    //元画像との差分を取得
    void SetAbs() {
        abs.name = "abs_" + filteredimg.name;
        absdiff(origin.img, filteredimg.img, abs.img);//元画像との差分を取得
    };
    //フィルター後と差分画像を出力
    void ImOutPut() {
        filteredimg.ImOutPut();
        abs.ImOutPut();
    }

};
//メディアンフィルタ
class MedianImg : public Filters {
public:
    MedianImg() {
        filteredimg.name = "med_" + std::to_string(kernelsize);
        medianBlur(target.img, filteredimg.img, kernelsize);    //メディアンフィルタ
        SetAbs();   //元画像との差分を取得
    }
};
// 移動平均法
class MoveAveImg :public Filters {
public:
    MoveAveImg() {
        filteredimg.name = "ave_" + std::to_string(kernelsize);
        blur(target.img, filteredimg.img, Size(kernelsize, kernelsize));        // 移動平均法
        SetAbs();   //元画像との差分を取得
    }
};

Imgs Filters::origin;
Imgs Filters::target;
int Filters::kernelsize;
std::string Imgs::filepath;//データの保管場所path.ここを変えて複数の画像を試す
std::string files[3] = { "./data1/", "./data2/", "./data3/" };

int main(int argc, char *argv[])
{
    for (int i = 0; i < 3; i++) {
        Imgs::filepath = files[i];
        //元画像取得
        Filters::origin = Imgs("origin", imread(Imgs::filepath + "img.jpg"));
        Mat origin_img = Filters::origin.img;
        //縦横比は維持するように画像を縮小
        const float kSize = 125;
        float targetsize = kSize/ origin_img.cols;
        resize(origin_img, Filters::origin.img, Size(), targetsize, targetsize);

        //ノイズ付加
        Filters::target = Imgs("target", AddNoise(Filters::origin.img, 0.05f));
        for (int i = 3; i < 10; i += 2) {
            Filters::kernelsize = i;//カーネルサイズを変えてみる
            // 移動平均法
            MoveAveImg moveave;
            //メディアンフィルタ
            MedianImg median;
            //結果表示
            Filters::origin.ImOutPut();
            Filters::target.ImOutPut();
            moveave.ImOutPut();
            median.ImOutPut();
        }
    }
    return 0;
}

結果

f:id:protoidea:20190623175145p:plain
移動平均法とメディアンフィルタの比較

移動平均法とメディアンフィルタの違い

結論

粒状雑音除去には、メディアンフィルタの方がよい
雑音の大きさに応じてカーネルサイズを変えるべき

Effective Python ―Pythonプログラムを改良する59項目 紹介

Effective Python ―Pythonプログラムを改良する59項目

Effective Python ―Pythonプログラムを改良する59項目

この記事は次の読者を対象にしています

  • Effective PythonPythonプログラムを改良する59項目 を読むか迷っている
  • Pythonプログラムをきれいに書く方法を探している

紹介

この本はPythonプログラムをバグなくきれいに書くためのコツを紹介している参考書である。
自分はレベル的に1章までしか理解できなかったが、それでも十分役に立つ知識が得られたと思う。
特に、クラスのメンバはパブリッシュにすべきという考えは、C++に慣れている自分としては驚くべきものであった。
python 2と3の両方が説明してあり、どちらを使う人にも役立つものであると思う。

この本の総合評価:4

まとめ この本を読むべき人は

Effective Python ―Pythonプログラムを改良する59項目

Effective Python ―Pythonプログラムを改良する59項目

jupyter notebook でPDFを画像に変換

Anacondaを使ってpdf2imageを入れるときに手間取ったので紹介
方法

  1. Poppler for Windowsからpoppler-0.68.0_x86をダウンロードする。
    blog.alivate.com.au
  2. 解凍する. 解凍用ソフトが無ければ 7-Zipをダウンロード sevenzip.osdn.jp
  3. システム環境変数のPathにbinファイルのPathを追加する。
  4. PC再起動
  5. Pathが通っていることを確認.
    ターミナル(windows power shellなど)でpdfinfo.exeと打ち込みエラーが出なければOK
  6. Anaconda Navigator を開く。
  7. Environments を開きroot の右三角から Open Terminal を選択。ターミナルが開く。
  8. pip install pdf2image
    と打つ

これでインストール終了です。PDFを用意し、以下のプログラムで動作確認しましょう

from pdf2image import convert_from_path
images = convert_from_path('test.pdf')
images[0].save('test.png', 'png')

PDFがpng画像として生成されていたら成功です。

pythonで機械学習 初心者向け PCのカメラを使いリアルタイム処理

対象読者

概要

  • PCカメラの画像をリアルタイムに表示
  • 青色成分だけ取得
  • 画面に動きがあった部分を検出

機械学習

使った関数

import したもの

import cv2

カメラ取得

cap = cv2.VideoCapture(1)  

引数: 0がデフォルト。自分は0で前面,1で背面カメラが作動

カメラ破棄

cap.release()#カメラ解放

カメラの画像取得

 _, frame = cap.read()

新規ウィンドウに画像を取得

 cv2.imshow("pc camera", frame) #ウィンドウに画像を出力

引数: windowの名前, 表示する画像

ウィンドウ破棄

cv2.destroyAllWindows() # window破棄

キーボード入力を取得

k = cv2.waitKey(1)#1ms確認
if k == 13:#Enterが押されたら
    処理

画像の差分の絶対値を取得

<結果画像> = cv2.absdiff(<画像1>, <画像2>)

輪郭抽出

こちらを参照

参考書籍

protoidea.hatenablog.com

pythonで機械学習 初心者向け 輪郭抽出

対象読者

前準備

test.jpgとして好きな画像を入れておく。

機械学習

使った関数

importしたもの

import cv2
import matplotlib.pyplot as plt

グレースケールにする.

cv2.thresholdはグレースケール画像を引数にとるため、事前に変換する必要がある。

gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

グレースケール画像の表示

plt.imshow(img, cmap="gray")

画像を平滑化(白色雑音の除去)

  • 引数:
    • ksize:フィルタ窓枠の大きさ. 必ず奇数
    • sigmaX: ガウシアンの標準偏差値. 0だとカーネルのサイズから自動的に計算
gauss_img = cv2.GaussianBlur(img , ksize = (7,7),sigmaX =  0)

二値化

  • 引数:
    • thresh:閾値.
    • maxval:閾値以上(指定により閾値以下のこともある)の値を持つ画素に対して割り当てられる値,
    • type:二値化の方法。cv2.THRESH_BINARY_INVだと白黒かつ反転
  • 返り値:[1]に画像が入っている
im2 = cv2.threshold(gray_img, thresh = 140, maxval = 240, type = cv2.THRESH_BINARY_INV)[1]

輪郭抽出

  • 引数
    • image: 型が CV_8UC1 である画像。非0の画素は1とした2値画像として扱われる。
    • mode: 輪郭を検索する方法。
      cv2.RETR_EXTERNAL: 一番外側の輪郭のみ抽出する。
      cv2.RETR_LIST: すべての輪郭を抽出するが、階層構造は作成しない。
      cv2.RETR_CCOMP: すべての輪郭を抽出し、2階層の階層構造を作成する。
      cv2.RETR_TREE: すべての輪郭を抽出し、ツリーで階層構造を作成する。
      cv2.RETR_LIST、cv2.RETR_CCOMP、cv2.RETR_TREE はいずれもすべての輪郭を抽出するが、返り値の hierarchy の内容が異なる。
    • method: 輪郭を近似する方法。
  • 返り値
    • [0] : image
    • [1] : contours 抽出された輪郭のリスト。各輪郭は (NumPoints, 1, 2) の numpy 配列。
    • [2] : hierarchy (1, NumContours, 4) の numpy 配列。階層構造のリスト。
contours = cv2.findContours(im2, mode = cv2.RETR_LIST, method = cv2.CHAIN_APPROX_SIMPLE)

外接矩形を作成

返り値:長方形の左上の角のx,y座標、幅,高さ

 x,y,w,h = cv2.boundingRect(<輪郭>)

長方形を図示

引数:画像(直接追加される)。左上(pt1),右下(pt2)の角の座標、色(color)

red = (0,0,255)
cv2.rectangle(img,(x,y), (x+w,y+h), red, thickness =3)

参考書籍

protoidea.hatenablog.com