P4D」タグアーカイブ

DelphiのObject Pascalで記述したコード内に埋め込まれたPython Scriptを実行。また、実行結果の取得。

Mark Sheet Reader (Basic version)

「マークシートリーダーをつくる(基礎編)」

DelphiでGUIを作成、マークシート画像はPythonにインストールしたOpenCVとNumpyで読み取り&計算処理して、結果をMemoに表示するマークシートリーダーの練習プログラム。

0.準備
1.使用するプログラムとマークシート画像について
2.マークシート画像を読み込む
3.マークシート読み取り処理のアルゴリズム
4.マークシート読み取り処理の実際(Object Pascalのコード)
5.さらに進化
6.著作権表示の記載方法
7.お願いとお断り

ここで紹介している練習用プログラムを、実際の採点業務で使用できるようにした拙作マークシートリーダーです。

0.準備

マークシートリーダー作成にあたって、以下の事前準備が必要です。

・PythonForDelphiのインストール
・Embeddable Pythonのダウンロードと必要なライブラリのインストール
(作業後、このプログラムへの埋め込み用にフォルダ名を「Python39-32」に変えて、このプログラム(マークシートリーダー)のexeがある場所へコピーする)
・アプリケーションの表示画面のリサイズ対応(縦編)

(いずれも、当Blogの記事で過去に紹介)

重要 上の記事の手順で、OpenCVとNumpyをインストールしたEmbeddable Pythonが入ったフォルダを「Python39-32」という名前で、以下のフォルダ内にコピーする。

C:\Users\ xxx \ Project1.dprojファイルのあるフォルダ \Win32\Debug\

1.使用するプログラムとマークシート画像について

当Blogの過去記事『~主として「高さ」の変更に関する覚書~』で作成したDelphiのGUIをそのまま使用します。

必要なVCLとその構造(親子関係)

画面サイズの変更に対応できるよう、以下のコードを記述。

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
  System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
  Vcl.ExtCtrls, Vcl.Grids, Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    Panel2: TPanel;
    Panel3: TPanel;
    Splitter1: TSplitter;
    ScrollBox1: TScrollBox;
    Image1: TImage;
    Memo1: TMemo;
    procedure FormCreate(Sender: TObject);
    procedure FormResize(Sender: TObject);
    procedure Splitter1Moved(Sender: TObject);
  private
    { Private 宣言 }
    //Panel1の幅とFormの高さを記憶する変数
    intPH, intFH:integer;
    //Formの表示終了イベントを取得
    procedure CMShowingChanged(var Msg:TMessage); message CM_SHOWINGCHANGED;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

{ TForm1 }

procedure TForm1.CMShowingChanged(var Msg: TMessage);
begin
  inherited; {通常の CMShowingChagenedをまず実行}
  if Visible then
  begin
    Update; {完全に描画}
    //Formの表示終了時に以下を実行
    Panel1.Height:=intPH;
    intPH:=Panel1.Height;
    intFH:=Form1.Height;
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  //Panel1とFormの高さを記憶する変数を初期化
  intPH:=200;
  intFH:=480;
end;

procedure TForm1.FormResize(Sender: TObject);
begin
  //比率を維持してPanel1の高さを変更
  Panel1.Height:=Trunc(Form1.Height * intPH/intFH);
end;

procedure TForm1.Splitter1Moved(Sender: TObject);
begin
  //Panel1とFormの高さを取得
  intPH:=Panel1.Height;
  intFH:=Form1.Height;
end;

end.

マークシート画像は、以下の画像を使用。

「ms01.Jpg」

マークシート画像は、以下の場所に「MarkSheet」という名前のフォルダを作成して、その中に保存。

C:\Users\ xxx \ Project1.dprojファイルのあるフォルダ \Win32\Debug\Marksheet

2.マークシート画像を読み込む

Delphiを起動して、Project1.dproj(マークシート読み取り用GUIの保存してあるフォルダ内のDelphiのプロジェクトファイル)を開き、Panel3をクリックして選択しておいて、Panel3上にButton1を作成。Button1のNameプロパティはButton1のまま、Captionプロパティを「画像を表示」に変更。Button1の位置は下図を参照。

Captionプロパティを「画像を表示」に変更
Button1の位置は画面下・Panel3の左に寄せる

OpenDialog1をForm上に置く。

OpenDialogをダブルクリック
Form上のOpenDialog1

次に、Form上のButton1をダブルクリックして、procedure TForm1.Button1Click(Sender: TObject);を作成。

procedure TForm1.Button1Click(Sender: TObject);
begin

end;

作成した手続きではJpeg画像を扱うので、画面を上にスクロールして、implementation部の下に Vcl.Imaging.Jpeg を uses する。

implementation

uses
  Vcl.Imaging.Jpeg; //Jpeg画像を読み込む

{$R *.dfm}

Button1Clickプロシージャにvar宣言を追加して、Jpeg画像読み込み用の変数jpgを宣言。

procedure TForm1.Button1Click(Sender: TObject);
var
  jpg: TJPEGImage;
begin

end;

beginとend;の間に、以下のコードを記述。

  //OpenDialogのプロパティはExecuteする前に設定
  With OpenDialog1 do begin
    //表示するファイルの種類を設定
    Filter:='JPEG Files (*.jpg, *.jpeg)|*.jpg;*.jpeg';
    //データの読込先フォルダを指定
    InitialDir:=ExtractFilePath(Application.ExeName)+'MarkSheet';
  end;

  if not OpenDialog1.Execute then Exit;  //キャンセルに対応
  //オブジェクトを生成
  jpg := TJPEGImage.Create;
  try
    //読み込み
    jpg.LoadFromFile(OpenDialog1.FileName);
    //Image1に表示
    Image1.Picture.Assign(jpg);
  finally
    //オブジェクトを破棄
    jpg.Free;
  end;

上書き保存(Ctrl+S)して、実行(F9)。データの読み込み先を指定しておくと、目的のフォルダが一発で開くので便利。

マークシート画像が表示される。が、ごく一部しか見えない。

これはImage1のAutoSizeプロパティがデフォルトFalseに設定されているため。 Image1 のAutoSizeプロパティをTrueにするコードを追加(オブジェクトインスペクタで Image1 のAutoSizeプロパティを 直接指定してもOK)。

  try

    //読み込み
    jpg.LoadFromFile(OpenDialog1.FileName);
    //Image1に表示
    Image1.Picture.Assign(jpg);

    //追加
    Image1.AutoSize:=True;

  finally

上書き保存(Ctrl+S)して、実行(F9) 。画像の表示を確認する。

うまくいったように見える。Formを最大化してSplitterを下げて、さらに確認。
画像の表示位置を修正する必要がありそうだ

画像が表示される位置を、画面の左側へ移動するコードを手続きの先頭に追加する。

begin

  //Imageの表示位置を指定
  Image1.Top := 25;
  Image1.Left := 40;

  //OpenDialogのプロパティはExecuteする前に設定しておくこと
  With OpenDialog1 do begin

上書き保存(Ctrl+S)して、実行(F9) 。画像の表示を再度確認する。

ほぼイメージに近い出来栄え?

参考:画像読み込みのコード(全体)

implementation

uses
  Vcl.Imaging.Jpeg; //Jpeg画像を読み込む

{$R *.dfm}

{ TForm1 }

procedure TForm1.Button1Click(Sender: TObject);
var
  jpg: TJPEGImage;
begin

  //Imageの表示位置を指定
  Image1.Top := 25;
  Image1.Left := 40;

  //OpenDialogのプロパティはExecuteする前に設定しておく
  With OpenDialog1 do begin
    //表示するファイルの種類を設定
    Filter:='JPEG Files (*.jpg, *.jpeg)|*.jpg;*.jpeg';
    //データの読込先フォルダを指定
    InitialDir:=ExtractFilePath(Application.ExeName)+'MarkSheet';
  end;

  if not OpenDialog1.Execute then Exit;  //キャンセルに対応
  //オブジェクトを生成
  jpg := TJPEGImage.Create;
  try

    //読み込み
    jpg.LoadFromFile(OpenDialog1.FileName);
    //Image1に表示
    Image1.Picture.Assign(jpg);

    //追加
    Image1.AutoSize:=True;

  finally
    //オブジェクトを破棄
    jpg.Free;
  end;

end;

3.マークシート読み取り処理のアルゴリズム

まず最初にマークシートの左上にある特徴点(マーカー)画像: ■■■(トリプルドット)をOpenCVのテンプレートマッチングで探す。

特徴点(マーカー)画像が見つかったら、 特徴点(マーカー)画像左上位置を基準にして、「マークシートの周囲の枠部分のみ」を矩形選択して切り出し。

参考①:あらかじめ測定しておいた特徴点(マーカー)画像の位置(単位はピクセル)
左上のX座標=65
左上のY座標=28
右下のX座標=121(マークシート矩形の座標計算には使用しない)
右下のY座標=43(マークシート矩形の座標計算には使用しない)

参考②:あらかじめ測定しておいたマークシート矩形の座標 (単位はピクセル)
左上の X座標=65
左上の Y座標=61
右下の X 座標=419
右下の Y 座標=497

参考 上記の各座標をマークシート画像から計測し、テンプレートとして用意したマークシートごとに登録(座標値を保存)するプログラムを別途作成した。なお、座標原点(0,0)は画像の左上である(使い慣れた数学の座標系とちょっと違うことに注意!)。

赤が左上、青が右下の座標で、緑がマークシート枠の矩形

この座標を元にして、 特徴点(マーカー)画像からの距離で、マークシート矩形を切り出す。

マークシート矩形において、(W1、H1)が左上位置を、(W2、H2)が右下位置を示す座標となる。

上の例では、マークシートの列数は「1」、行数は「10」と数えることにする。列数が「1」の場合、W1は「ほぼ0(ゼロ)」になり、値としての意味がないように思われるが、このプログラムを実用化した場合は、下の例のように、複数の列があるマークシートを用いることになるので、2列めのマークシート矩形の座標は、左上が(W3,H3)、右下が(W4,H4)、3列めのマークシート矩形の座標は左上が (W5,H5)、右下が(W6,H6)のように指定でき、W値が0ではない場合が生じる。

マークシート用紙の作成に、私はWordを用いたが、Wordのバージョンによっては、あろうことか、上書き保存時に、マーカー画像(■■■)の位置が数ミリ程度、勝手に左へ移動するという予期しないトラブル(Wordの仕様?)が発生。このような点も考慮して、W1の座標は敢えて(0として)定数化していない。

マークシートの作成例(実験用に使用)
列数3、1列あたりの行数25、1行あたりの選択肢の数は16
この用紙の場合、総マーク数は3×25×16=1200個/枚となる
つまり用紙1枚につき、1200回マークの有無の判定が必要

実際の作業では、マークシート画像をスキャナーで読み取って、グレースケールのJpeg画像としてデータ化するので、マークシート(用紙)に「しわ」があったり、状況によっては「折られ」ていたりする関係上、読み取り画像を1枚ずつ比較すると、その上下・左右にどうしても微妙なブレ・ズレが生じてしまう。しかし、同じ印刷機で、同時に印刷したマークシートであれば、特徴点(マーカー)画像とマークシートの行列位置の関係は絶対であり、これが1枚ごとに変化することはありえない。つまり、スキャンした画像が余程大きく傾きでもしていない限り、テンプレートマッチングで、特徴点(マーカー)画像さえ発見できれば、予め測定・記録しておいた座標の相対的位置関係からマークシート矩形は容易に切り出せる。

次の画像は、別データとして保存してある特徴点(マーカー)画像を元に、OpenCVのテンプレートマッチングをマークシート画像に対して行ったもの。類似度の高い部分を赤枠で囲んで示すようプログラミングしている。

マーカー
テンプレートマッチングを行った画像

次に、上に述べた方法で計算したマークシート矩形を列単位で切り出す。切り出した画像は、マークの(=列)数・行数の整数倍のサイズになるようリサイズする(これは、このあと画像を細かく分割して処理するので、切り出す行や列の計算を簡単にするための工夫 → 整数倍にリサイズすれば、列数分&行数分廻すLoop処理の中で処理しやすい)。

列単位で切り出したマークシート矩形

マークシート用紙は、一般的なマークシート用紙のような厚みのある(高級感あふれる)専用紙でなく、ホームセンターでも「売ってない!」ような見た目が灰色の再生紙を用いている。このためか、あちらこちらにゴミのような黒い点や、細いすじが入っていることがある。これらの黒点やすじを判定プログラムが「マークあり」と誤認しないようにするため、次に「平滑化(ボカシ)処理」を行う。

平滑化(ボカシ)処理には「ガウシアンフィルタ」を用いた。これは、正規(ガウス)分布を利用して「注目画素からの距離に応じて近傍の画素値に重みをかける」という処理を行うもので、自然な平滑化が実現できるとのこと。次の画像は、上の切り出したマークシート矩形に対して、この平滑化処理を行ったもの。

img = cv2.GaussianBlur(img,(35,35),0) ※引数は奇数を指定する必要がある

引数の値が大きいほど正規分布のピークが低く、広がりは広くなる(=より均一に、より全体にボカシがかかる)。ここでは引数をかなり大きめにとり「35」としている。こうすることで、ゴミやシミを画像からほぼ完全に除去できる。

ガウシアンフィルタ処理を行い、ゴミやシミを除去する

さらに、この画像を「ある閾値」を元に白と黒に二値化処理する。この処理で枠線やマークされていないマーク部分が「すべて白」になり、鉛筆で濃くマークされている部分だけが「黒」になった白黒画像が得られる。当初は、以下のように引数を指定して二値化画像を作成した。

ret, img = cv2.threshold(img, 140, 255, cv2.THRESH_BINARY)

現在は、次のように閾値の設定を自動で行う「大津の二値化」を利用している。

ret, img = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)

式中の第2引数は閾値だが、大津の二値化では自動計算させるので0(ゼロ)を指定。第3引数は0-255の256段階でグレースケール化しているから、最大値の255を指定する。これによって、次の画像が得られる。

大津の二値化で作成した白黒画像

さらに、これを白黒反転させた画像を作成する。式は以下の通り。

img = 255 - img

これにより、次の画像が得られる。

マーク部分を「白」に変換した画像

次に、この画像を「行」単位に分割して切り出す。

1行目を切り出した画像

次に、選択肢の数で、均等に分割する。ここでは選択肢の数が「8」なので、上の画像を等幅で8個に分割する。下は、その1個目の切り出し画像である。

このように細かく分割して切り出した画像1つ1つについて、画素が白なら値を255・黒なら0として面積あたりの合計値を計算し、マークされている部分の面積の中央値を算出、これを閾値として、下の式では、マークされている(白い部分の)面積が他より3倍以上あるものを「マークあり!」と判定している。この数値が大きいほど、判定はきびしくなる。

result.append(area_sum > np.median(area_sum) * 3)

このマークシート読み取り処理のアルゴリズムの主要部分は全て、GitHubの次の記事に紹介されていたものです。素晴らしい記事を投稿してくださった作成者の方に、心から感謝申し上げます。

PythonとOpenCVで簡易OMR(マークシートリーダ)を作る

URL:https://qiita.com/sbtseiji/items/6438ec2bf970d63817b8

参考 列が複数あるマークシートの読み取り処理について

上記記事では、特徴点(マーカー)画像をマークシートの上下に複数個用意し、テンプレートマッチングを行っています。確かに、マークシートの左上と右下に特徴点(マーカー)画像を用意すれば、より簡単にマークシート矩形の切り出しが可能でした。これは素晴らしいアイデアです。

私も当初は特徴点(マーカー)画像を複数個用意してマークシートを作成していたのですが、列数を2列、3列と増やすと、さまざまな問題が生じることに気が付きました。

第一に、特徴点(マーカー)画像を変えないと、列ごとの切り出しが困難だということです。つまり、3列あるマークシートでは、最も左の列用の特徴点を■■■、真ん中の列用の特徴点を■□■、最も右側の列用の特徴点を■□□として、Loop処理の中でテンプレートマッチングに使用する特徴点(マーカー)画像を切り替えて、目的とするマークシート矩形を切り出せるようにしてみた(□□■や□□□も含めればさらに多くの列が作成可能)のですが、この方法では、うまく特徴点(マーカー)画像を認識してくれないことがあり、安定感に欠ける気がしました。

第二に、万一、回答者が特徴点(マーカー)画像に意図的に変更を加える(例: ■□□ → ■■□)等の暴挙に出た場合、対応が難しいこと。

第三に、マーカー画像が多いと、マークシートの見た目もなんだか騒がしくて、個人的にはマーカー画像を複数個用意する方法はなるべく避けたいと考えたこと。

これらの理由から、「なんとか特徴点(マーカー)画像が1個で済まないか」と、私なりに工夫して、当ブログで紹介した方法を考えました。

創意工夫の過程で一時は、回答者が意図的に変更できるようなマーカー(例: □ )がなければOKかとも思い、別の特徴点(マーカー)画像も使ってみたのですが、それはそれでまた別の問題を起こすことがわかりました。

例えば、下のように、ヒトなら簡単に両者の違いを判別できる画像を用意します。

用意した特徴点(マーカー)画像

これに対して、左側の画像でテンプレートマッチングを行うと・・・

機械はヒトと違うモノの見方をしていることが、大変良くわかりました。

4.マークシート読み取り処理の実際(Object Pascalのコード)

Form上に、Buttonを1つ、PythonForDelphi関連のVCLコンポーネントを3つ配置する。Button2は、Panel3の中央付近に置き、Nameプロパティはそのまま、Captionプロパティを「読み取り」に変更する。PythonForDelphi関連のVCLコンポーネントは、すべて非ビジュアルコンポーネントなので、位置はどこでもよく、Nameプロパティもデフォルトのままとする。 PythonForDelphi関連で配置するコンポーネントは以下の通り。

以下のように、PythonForDelphi関連のコンポーネントのプロパティとイベントを設定

・PythonEngine1のAutoLoadプロパティはFalseに設定。

・PythonEngine1のDllNameプロパティはpython39.dllを指定(埋め込みPythonのバージョンに合わせて設定する)。ここでは3.9.9以下のバージョンのPythonでないとNumpyが非対応(2021年12月現在)であり、用意した埋め込みPythonのバージョンは3.9.9なのでpython39.dllに変更する。

・PythonEngine1のIOにはPythonGUIInputOutput1を指定。

・PythonGUIInputOutput1は他で利用するならプロパティのOutPutに「Memo1」などとするところだけれど、ここでは何も設定しない。

・PythonDelphiVar1のVarNameはプログラムコードの記述に合わせて「var1」とする。var1と入力後、Enterで確定すること!(青く反転表示されるのを確認する)

Formが生成される時、PythonEngine1を初期化する。Formのタイトルバーの上をクリックして選択し、オブジェクトインスペクタのイベントタブをクリックしてOnCreateイベントの右に表示されている「FormCreate」をダブルクリックして、コードの入力に切り替える。

参考:エラー対応方法(20220724追加)

P4D使用時にImageコントロールの bsClear を使うとエラーが発生します。

[dcc32 エラー] Unit02_MSReader.pas(1199): E2010 'TBrushStyle' と 'Enumeration' には互換性がありません

これはPythonEngine.pasの中で bsClear が定義(使用)されているためです。次に示す例のように、Image1の方のbsClearを明示的に Vcl.Graphics.bsClear として対応します。

  //矩形を描画
  with Image1 do
  begin
    //Canvas.Brush.Style:=bsClear;
    Canvas.Brush.Style:=Vcl.Graphics.bsClear;
  end;

以上、エラー対応でした。解説を続けます。

表示は次のようになっている(はず)。ここにコードを追加する。

procedure TForm1.FormCreate(Sender: TObject);
begin

  //Panel1とFormの高さを記憶する変数を初期化
  intPH:=200;
  intFH:=480;

end;

追加するコード

procedure TForm1.FormCreate(Sender: TObject);
var
  //Python39-32へのPath(追加)
  AppDataDir:string;
begin

  //Panel1とFormの高さを記憶する変数を初期化
  intPH:=200;
  intFH:=480;

  //以下のコードを追加
  //embPythonの存在の有無を調査
  AppDataDir:=ExtractFilePath(Application.ExeName)+'Python39-32';

  if DirectoryExists(AppDataDir) then
  begin
    //フォルダが存在したときの処理
    MessageDlg('Embeddable Pythonが利用可能です。',
      mtInformation, [mbOk] , 0);
    PythonEngine1.AutoLoad:=True;
    PythonEngine1.IO:=PythonGUIInputOutput1;
    PythonEngine1.DllPath:=AppDataDir;
    PythonEngine1.SetPythonHome(PythonEngine1.DllPath);
    PythonEngine1.LoadDll;
    //PythonDelphiVar1のOnSeDataイベントを利用する
    PythonDelphiVar1.Engine:=PythonEngine1;
    PythonDelphiVar1.VarName:=AnsiString('var1');  //プロパティで直接指定済み
    //初期化
    PythonEngine1.Py_Initialize;
  end else begin
    MessageDlg('Embeddable Pythonが見つかりません!',
      mtInformation, [mbOk] , 0);
    PythonEngine1.AutoLoad:=False;
  end;

end;

ここでMessageDlgを使用しているので、以下のように System.UITypes を uses に追加する。

implementation

uses
  Vcl.Imaging.Jpeg, System.UITypes;  // <-追加

  //Jpeg:Jpeg画像を読み込む
  //System.UITypesはMessageDlgの表示に必要

{$R *.dfm}

プライベートメンバー変数 intCnt(カウンタとして利用する)と strAnsList(Pythonから返された計算結果を保存する) を2つ、Private宣言で新しく宣言する。

  private
    { Private 宣言 }

    //for Python(追加)
    //Counter
    intCnt:integer;
    //Pythonから送られたデータを保存
    strAnsList:TStringList;

    //Panel1の幅とFormの高さを記憶する変数
    intPH, intFH:integer;
    //Formの表示終了イベントを取得
    procedure CMShowingChanged(var Msg:TMessage); message CM_SHOWINGCHANGED;

  public
    { Public 宣言 }
  end;

Form上のButton2(読み取りボタン)をダブルクリックして、手続きを作成し、以下の内容を入力する。

procedure TForm1.Button2Click(Sender: TObject);
var
  StrList:TStringList;
  strJCnt,strColCnt,strRowCnt,strSelCnt:String;
  TopLX, TopLY, TLX1, TLY1, BRX1, BRY1:integer;
  strPicName:string;
begin

  //初期化
  Memo1.Clear;
  intCnt:=1;

  //座標
  TopLX:=65;
  TopLY:=28;
  //BtmRX:=121;
  //BtmRY:=43;
  TLX1:=65;
  TLY1:=61;
  BRX1:=419;
  BRY1:=497;

  //マークシート数Check(+1することを忘れない)
  strJCnt:=IntToStr(2);

  //列数Check(+1することを忘れない)
  strColCnt:=IntToStr(2);

  //1列あたりの行数Check
  strRowCnt:=IntToStr(10);

  //選択肢数Check
  strSelCnt:=IntToStr(8);

  //マークシート名
  strPicName:='ms';

  //結果を保存するStringList
  strAnsList := TStringList.Create;

  //Scriptを入れるStringList
  StrList := TStringList.Create;

  try

    //Python Script
    StrList.Add('import cv2');
    StrList.Add('import numpy as np');

    //for JPN(日本語に対応)
    StrList.Add('def imread(filename, flags=cv2.IMREAD_GRAYSCALE, dtype=np.uint8):');
    StrList.Add('    try:');
    StrList.Add('        n = np.fromfile(filename, dtype)');
    StrList.Add('        img = cv2.imdecode(n, flags)');
    StrList.Add('        return img');
    StrList.Add('    except Exception as e:');
    StrList.Add('        return None');

    //マーカー画像を読み込む
    StrList.Add('template = imread("marker.png", cv2.IMREAD_GRAYSCALE)');

    //マークシートの枚数
    StrList.Add('for j in range(1,'+strJCnt+'):');

    //列数
    StrList.Add('    for i in range(1,'+strColCnt+'):');

    //マークシートへのパスを取得
    StrList.Add('        if j < 10:');
    StrList.Add('            MS_Name = r".\Marksheet\'+ strPicName +'0"+ str(j) +".jpg"');
    StrList.Add('        else:');
    StrList.Add('            MS_Name = r".\Marksheet\'+ strPicName +'"+ str(j) +".jpg"');

    //画像を読み込む
    StrList.Add('        img = imread(MS_Name)');
    //画像をグレースケールで読み込む
    StrList.Add('        img_gray = imread(MS_Name, 0)');

    //テンプレートマッチングの実行(比較方法cv2.TM_CCORR_NORMED)
    StrList.Add('        result = cv2.matchTemplate(img, template, cv2.TM_CCORR_NORMED)');

    //類似度が最小,最大となる画素の類似度、位置を調べ代入する
    StrList.Add('        min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)');
    //最も似ている領域の左上の座標を取得
    StrList.Add('        top_left = max_loc');
    StrList.Add('        if i == 1:');

    //補正値を取得(高さ)
    StrList.Add('            h1 = ' + IntToStr(TLY1 - TopLY));
    StrList.Add('            h2 = ' + IntToStr(BRY1 - TopLY));
    //補正値を取得(幅)
    StrList.Add('            w1 = ' + IntToStr(TLX1 - TopLX));
    StrList.Add('            w2 = ' + IntToStr(BRX1 - TopLX));

    //矩形の左上の座標を計算 [0]-> X, [1]-> Y
    StrList.Add('        TL = (top_left[0] + w1, top_left[1] + h1)');
    //矩形の右下の座標を計算
    StrList.Add('        BR = (top_left[0] + w2, top_left[1] + h2)');
    //画像を切り出し img[top_Y : bottom_Y, left_X : right_X]
    StrList.Add('        img = img_gray[TL[1] : BR[1], TL[0] : BR[0]]');

    //選択肢数
    StrList.Add('        n_col = '+ strSelCnt);

    //解答欄1列あたりの行数
    StrList.Add('        n_row = '+ strRowCnt);
    StrList.Add('        margin_top = 0');
    StrList.Add('        margin_bottom = 0');
    StrList.Add('        n_row = n_row + margin_top + margin_bottom');

    //マークの列数・行数の整数倍のサイズになるようリサイズ
    StrList.Add('        img = cv2.resize(img, (n_col*100, n_row*100))');

    //保存して確認
    //StrList.Add('        cv2.imwrite("01_ReSize.png", img)');

    //平滑化の度合い
    StrList.Add('        img = cv2.GaussianBlur(img,(35,35),0)');

    //保存して確認
    //StrList.Add('        cv2.imwrite("02_GaussianBlur.png", img)');

    //二値化の閾値
    //50を閾値として2値化
    //imgはグレースケール画像でなければならない
    //第2引数はしきい値で,
    //画素値を識別するために使用(指定)
    //第3引数は最大値でしきい値以上
    //(指定するフラグ次第では以下)の値を持つ
    //画素に対して割り当てられる値
    //StrList.Add('        ret, img = cv2.threshold(img, 140, 255, cv2.THRESH_BINARY)');

    //大津の二値化で閾値の設定を自動化
    //第1引数には画像データを設定
    //(グレースケール画像でなければならない)
    //第2引数はしきいだが自動計算させるので0(ゼロ)を指定
    //第3引数は0-255の256段階でグレースケール化しているから
    //最大値の255を指定
    StrList.Add('        ret, img = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)');

    //保存して確認
    //StrList.Add('        cv2.imwrite("03_threshold.png", img)');

    //白黒を反転
    StrList.Add('        img = 255 - img');

    //保存して確認(追加)
    StrList.Add('        cv2.imwrite("04_threshold.png", img)');

    //全マークを判定
    StrList.Add('        result = []');
    StrList.Add('        for row in range(margin_top, n_row - margin_bottom):');
    StrList.Add('            tmp_img = img [row*100:(row+1)*100,]');
    StrList.Add('            area_sum = []');
    StrList.Add('            for col in range(n_col):');
    StrList.Add('                area_sum.append(np.sum(tmp_img[:,col*100:(col+1)*100]))');
    StrList.Add('            result.append(area_sum > np.median(area_sum) * 3)');

    //判定結果を出力
    StrList.Add('        for x in range(len(result)):');
    StrList.Add('            res = np.where(result[x]==True)[0]+1');
    StrList.Add('            if len(res)>1:');
    StrList.Add('                var1.Value = "99"');
    StrList.Add('            elif len(res)==1:');
    StrList.Add('                s = str(res)');
    StrList.Add('                var1.Value = s[1]');
    StrList.Add('            else:');
    StrList.Add('                var1.Value = "999"');

    //Execute
    PythonEngine1.ExecStrings(StrList);

    //結果を表示
    Memo1.Lines.Assign(strAnsList);

    //Userへ案内
    MessageDlg('読み取り完了!', mtInformation, [mbOk] , 0);

  finally
    //解放
    StrList.Free;
    strAnsList.Free;
  end;

end;

Pythonから返された計算結果を受け取るため、PythonDelphiVar1のOnSetDataイベントの手続きを作成する。Form上のPythonDelphiVar1をクリックして選択し、オブジェクトインスペクタのOnSetDataイベントの右側をダブルクリックして、コード入力画面で以下の内容を入力する。

procedure TForm1.PythonDelphiVar1SetData(Sender: TObject; Data: Variant);
begin
  //値がセットされたら動的配列に値を追加
  strAnsList.Add(Data);
  intCnt:=intCnt+1;
  Application.ProcessMessages;
end;
表示の「999」は空欄、「99」は複数マークであることを意味する。

上書き保存(Ctrl+S)して、実行(F9)。次の画像のように、マークシートが正しく読み取り処理されることを確認する。

複数マークを許可する場合には、判定結果を出力する部分のコードを次のように変更する。マークシートの読み取り結果をCSVファイルに出力したり、Excelに書き出したりして利用する場合には、複数回答は99、未回答は999のように処理した方が、後々の処理がラクになる(・・・と思う)。

    //判定結果を出力(複数回答は99、未回答は999で表示)
    {コメント化ここから
    StrList.Add('        for x in range(len(result)):');
    StrList.Add('            res = np.where(result[x]==True)[0]+1');
    StrList.Add('            if len(res)>1:');
    StrList.Add('                var1.Value = "99"');
    StrList.Add('            elif len(res)==1:');
    StrList.Add('                s = str(res)');
    StrList.Add('                var1.Value = s[1]');
    StrList.Add('            else:');
    StrList.Add('                var1.Value = "999"');
    ここまで}

    //判定結果を出力(複数回答の詳細を表示)
    StrList.Add('        for x in range(len(result)):');
    StrList.Add('            res = np.where(result[x]==True)[0]+1');
    StrList.Add('            if len(res)>1:');
    StrList.Add('                var1.Value = str(res)+ '+'"!複数回答!"');
    StrList.Add('            elif len(res)==1:');
    StrList.Add('                s = str(res)');
    StrList.Add('                var1.Value = s[1]');
    StrList.Add('            else:');
    StrList.Add('                var1.Value = " *未回答*"');

PythonEngineが正しく初期化され、Embeddable Pythonが利用できることが確認できたら、このメッセージは必要ないのでコメント化しておく。

procedure TForm1.FormCreate(Sender: TObject);
var
  //Python39-32へのPath
  AppDataDir:string;
begin
  ・・・
  if DirectoryExists(AppDataDir) then
  begin
    //フォルダが存在したときの処理(コメント化)
    //MessageDlg('Embeddable Pythonが利用可能です。',
    //  mtInformation, [mbOk] , 0);
    PythonEngine1.AutoLoad:=True;

5.さらに進化

さまざまな機能を追加したマークシートリーダー
(ファイルの名称を連番で変更/画像の回転/グリッド指示位置と画像の連動/グリッド指示位置を画像上で矩形選択/閾値等各種パラメータの調整と保存機能/音声読み上げ関連機能の搭載/回答チェック機能(空欄&複数回答対応)/CSV形式でのデータ出力/ExcelBookへのデータ出力/様式の異なるマークシートをテンプレートとして登録して利用可能/抱き合わせ採点の実施機能/共通テスト(数学の様式)に対応等、考えつく限りの機能を搭載/さらに進化します!)

このプログラムでは、「マークシート画像の表示」と、「読み取り処理」の間に何も関連がないが、このプログラムをさらに発展させて、複数枚数の処理を可能にし、読み取り結果を画面上で確認するような機能を追加する際には、マークシート画像の表示はどうしても必要な機能になる。

さらに、画面の左側などに読み込んだマークシートがリスト形式で表示されるようにして、ここから任意のマークシート画像を選んで表示できるような機能も追加するとよいと思う。

読み取り結果も、ここではMemoに表示しているが、CSVやExcelへ出力して利用することを考えると、ここはGridコントロールに変更したい。

Gridコントロール上で選択したデータの該当回答欄に相当する画像が自動的に画面上に表示され、かつ、表示されたマークシート画像上の該当回答欄が矩形で選択され、ユーザーがチェックしやすいGUIにするとなお良いだろう。

また、チェック時にはユーザーがマークシート画像を見ながら確認作業が行えるよう、Gridコントロールの数字をアナウンスしてくれる音声読み上げ機能があると大変便利だ。それから、回答の必要がない、全マークシートが空欄となっている部分は、予め指定することで、チェックから除外できる機能も欲しい。

さらに、スキャナーから読み込んだ画像データを回転させたり、連番で扱いやすい名前に変更したり、様式の異なるマークシートをテンプレートとして登録できるような機能も搭載したい。

より一層ユーザーに優しい、夢に見たようなマークシートリーダーを開発したい。この希望の実現に向けて、日々努力する私でありたい。

Web上に貴重な資料を公開してくださった多くの皆さまに心より深く御礼申し上げます。ほんとうにありがとうございました。

6.著作権表示の記載方法

参考:Python4DelphiのLicenseについて

GitHubのPython4Delphiのダウンロードページには「The project is licensed under the MIT License.」とある。これは「改変・再配布・商用利用・有料販売すべてが自由かつ無料」であること、及び使用するにあたっての必須条件はPython4Delphiの「著作権を表示すること」と「MITライセンスの全文」or 「 MITライセンス全文へのLink」をソフトウェアに記載する、もしくは、別ファイルとして同梱しなさい・・・ということを意味する。

したがってPython4Delphiを利用したプログラムの配布にあたっては、ソフトウェアの中で、次のような著作権表示を行うか、もしくは P4DフォルダのルートにあるLicenseフォルダをプログラムに同梱して配布すればよいことになる。

Python4Delphiを利用した場合の著作権表示の記載例:

Copyright (c) 2018 Dietmar Budelsky, Morgan Martinet, Kiriakos Vlahos
Released under the MIT license
https://opensource.org/licenses/mit-license.php

7.お願いとお断り

このサイトの内容を利用される場合は、自己責任でお願いします。記載した内容を利用した結果、利用者および第三者に損害が発生したとしても、このサイトの管理者は一切責任を負えません。予め、ご了承ください。

【関連記事】

How to use Python4Delphi

「PythonForDelphiの使い方(Delphiのプログラム内でPythonを動かす)」

1.Delphiで埋め込みPythonを使う
2.準備
3.ノートPCの電池残量を表示するプログラムを作成
4.PythonEngineのメモリリーク
5.Delphi11のIDEが真っ白になってしまう問題への対応方法
6.著作権表示の記載方法
7.お願いとお断り

こちらで紹介した方法の応用版として、自作のマークシートリーダーの読み取り速度をPython4Delphiで高速化。プログラムのダウンロード(無料)も可能です。もし、よかったら次のリンク先記事もご参照ください。

1.Delphiで埋め込みPythonを使う

ノートPCの電池残量を表示する練習プログラムを、埋め込みPythonを使ってDelphiで書いてみる。
埋め込み用途のembeddable pythonをDelphiで使うには? というテーマで悩んでいらっしゃる方の参考になれば、望外の喜びです。なお、以下の内容はDelphiで開発経験のある方を対象としています。IDEの基本的な操作方法等は省略していますので、予めご了承ください。

2.準備

(1)DelphiにPython4Delphi(P4D)のパッケージを予めインストールしておく。

(2)埋め込み用のEmbeddable Pythonをダウンロードし、各種ライブラリをインストール(下記リンク先ではNumpyとOpenCVライブラリをインストール)。

(3)Embeddable PythonにノートPCの電池残量を表示するため、psutilライブラリをインストール( Embeddable Python のダウンロードと設定方法は上の(2)を参照してください)。

「python -m pip install psutil」と入力してEnterキーを押す

(4)Delphiを起動して「ファイル」→「新規作成」→「Windows VCL アプリケーション」の順にクリックして新しいプロジェクトを準備する。

VCLアプリケーションの新規作成(Delphi11の場合)

3.ノートPCの電池残量を表示するプログラムを作成

(1)プロジェクトに名前を付けて保存する
(2)GUIを作成
(3)コンパイル & Python環境をコピー
(4)Python関連のVCLコンポーネントを配置
(5)Python関連のVCLコンポーネントのプロパティを設定
(6)エラー対応(ライブラリパスの確認)
(7)閉じるボタンのコードを書く
(8)FormのCreateでPython39-32の有無を確認する
(9)Messageダイアログを使う
(10)埋め込みPythonと接続する
(11)OnSetDataイベントを利用する
(12)プログラムの完成と動作確認

(1)プロジェクトに名前を付けて保存する

新しいフォルダを作成(名称は任意:ここではBTRC_byP4Dとしている)し、Unit1.pasを保存(Unit1を別名にしてもよいが、名称をメモしておく)。

参考 BTR:Battery(電池) / C:Charging(充電)/ P4D:PythonForDelphi

つづけて、プロジェクトファイル(Project1.dproj)を同じフォルダに保存。
Project1は別名にしてもよいが、上のpasファイルと同じ名称にしないこと。
また、別名にした場合は、名称を忘れないようにメモしておく。

(2)GUIを作成

画面にVCLコンポーネントを配置してGUIを作る。
Memoを2つ(Memo1とMemo2)、
Buttonを2つ(Button1とButton2)が最低限必要。

パレットのTMemoとTButtonをそれぞれ2つずつ、FormへD&Dする。

DelphiのIDEの基本的な操作方法や、VCLコンポーネントの配置方法は、次のリンク先の解説がわかりやすい。

はじめてのDelphiアプリケーション (VCL Form編) (Delphi プログラミング)

URL:https://www.ipentec.com/document/delphi-first-application-vcl-form-application



※ Formの大きさの変更にMemoの大きさやButtonの表示位置を追随させる方法は、別途解説する予定。

各VCLコンポーネントの名称はデフォルト設定のまま

Button1のCaptionプロパティを「実行」に変更。
Button2のCaptionプロパティを「終了」に変更。

Button1のCaptionプロパティを「実行」に変更。 Button2も同様にして「終了」に変更する。
ボタンのCaptionプロパティを変更

(3)コンパイル & Python環境をコピー

ビルド構成(Debug)のまま、ここで1回コンパイルしてexeを生成。

Ctrl+F9(Ctrlキーを押しながらF9キーを押す)でもOK!
コンパイル成功を確認→OKをクリック

※ ツールバーの実行(F9)をクリックして実行した場合は、生成されたexeが実行されてFormが表示されるので、表示されたFormを右上の閉じるボタンをクリックして閉じる。

ツールバーの実行(F9)から実行する場合
右上の「閉じる」ボタンでFormを閉じる

コンパイルに成功すると、BTRC_byP4Dフォルダの中にWin32フォルダが、さらにその下にDebugフォルダがそれぞれ自動的に作成される。このDebugフォルダを開き、別途作成しておいたEnbeddable Pythonの入ったフォルダをコピーして、貼り付ける(下の例では Enbeddable Pythonの入ったフォルダ名をpython39-32としている)。

Enbeddable Pythonの入ったフォルダを
ここへ貼り付ける。
フォルダとファイルの構造はこうなる。

Embeddable Pythonのダウンロードと各種ライブラリのインストール方法は以下のリンク先を参照してください。

(4)Python関連のVCLコンポーネントを配置

DelphiにPythonのスクリプトを埋め込んで実行するには、PythonForDelphiが必要。
PythonForDelphi(またはPython4Delphi さらに略すと P4D)をDelphiにセットアップする方法は以下のリンク先で解説。

(Python4Delphiのパッケージがインストールされた)Delphiのパレットのいちばん下にPython4Delphiの非ビジュアルコンポーネントがあるので、この中から次の3つのコンポーネント

「PythonEngine、PythonGUIInputOutput、PythonDelphiVar」

をForm上にドラッグ&ドロップ(各非ビジュアルコンポーネントをダブルクリックしてもよい)。

※ 非ビジュアルとは、「実行時に見えなくなる」コンポーネントを意味する。

Python4Delphiの非ビジュアルコンポーネント
非ビジュアルコンポーネントなので画面の任意の位置へD&DすればOK!
非ビジュアルコンポーネントは表示しない設定にすることも出来る(忘れっぽい私は常に表示している)。

(5) Python関連のVCLコンポーネントのプロパティを設定

・PythonEngine1のAutoLoadプロパティをFalseに設定

Form上にパレットからPythonEngineコンポーネントをドラッグ&ドロップすると、名称は自動的に PythonEngine1になる。上の図のようにこれをクリックして選択すると、オブジェクトインスペクタにPythonEngine1のプロパティが表示されるので、その中のAutoLoadプロパティをFalseに変更する(デフォルトTrueに設定されているので、チェックボックスのチェックを外す)。

AutoLoadプロパティをFalseに変更

練習ではなく、本格的にプログラミングする際、私はビジュアルコンポーネントについては、その名称を必ず変更するようにしている。理由はButtonコントールなどは使用数が多く、わかりやすい名前を付けておいた方がプログラミングしやすいからだ。

 例:OKボタンなら、そのNameプロパティを button1→btnOK へ変更

しかし、非ビジュアルコンポーネントの場合は、同じコンポーネントを複数配置することは稀なので、Delphiが自動的に割り振った名前をそのまま利用している。ここでもその例にならって、非ビジュアルコンポーネントの名称は Delphiが自動的に割り振った名前をそのまま利用することにする。

・PythonEngine1のDllNameプロパティは、python39.dllを予め指定(組み込み用のPythonのバージョンに合わせて設定する)。

最新版のPython4Delphiでは「python310.dll」がデフォルト値になっていた。

python39.dllは、上でDebugフォルダ内に張り付けたPython39-32フォルダ内にある。

・PythonEngine1のIOプロパティにはPythonGUIInputOutput1を指定する。

IOプロパティのデフォルト設定は「空欄」になっていた。

・PythonGUIInputOutput1のOutPutプロパティに「Memo2」のように出力先を指定したくなるが、ここでは敢えて何も設定しない。

・PythonDelphiVar1のVarNameプロパティは、プログラムコードの記述に合わせるため「var1」とする。※var1と入力後、Enterで確定すること!(青く反転表示されるのを確認する)

「var1」と入力後、Enterキーを押さないと変更が反映されない。

・この状態で実行(F9)した際に「Python Engineが見つかりません」というようなエラーメッセージが表示される場合は、P4Dのパッケージをインストールした際のライブラリパス設定に誤りがないか、確認する

画面下のメッセージ欄の表示:[dcc32 致命的エラー] Unit1.pas(7): F2613 ユニット ‘PythonEngine’ が見つかりません。
コンパイルエラー発生時のUnit1画面

(6)エラー対応(ライブラリパスの確認)

GitHubから入手したPython4DelphiのフォルダのSourceフォルダ以下にある、このプログラムの動作に必要なファイルへのライブラリパスが正しく設定されていることを確認する。設定されていない場合は、(灰色で表示されている誤ったパスを削除して)ライブラリパスを再設定する。

「ツール」→「オプション」の順にクリックして、次の画面を表示する。

「言語」→「Delphi」→「ライブラリ」とクリックして、赤枠囲みの中をクリック。

ライブラリパスを正しく設定する。

PCを新しくした場合等、再設定する必要があるかもしれないので、
設定内容をメモしておく。

ライブラリパスの設定が完了したら、再度コンパイル(実行:F9)してエラーが発生しないことを確認する。

(右上の閉じるボタンで終了)

参考:コンパイルとビルドの違い

・メニューの「プロジェクト」 →「Project1をコンパイル」
 (ショートカットは「Ctrl+F9」)

前回のビルド以降に変更されたファイルと、それに依存するファイルのみをコンパイルして EXE を生成するが、アプリケーションは起動しない。

・メニューの「プロジェクト」 →「Project1をビルド」
 (ショートカットは「Shift+F9」)

変更の有無に関わらず、全てのユニットを再コンパイルして EXE を生成するが、アプリケーションは起動しない。ユニット数が多ければ当然それだけ遅くなる。

・実行(ショートカットはF9)

変更されたソースコードをすべてコンパイルする。コンパイルが成功した場合は、アプリケーションを実行するので、そのアプリケーションを IDE でテストできるようになる。

・デバッガを使わずに実行。(ショートカットは「Shift+Ctrl+F9」)

変更があったユニットだけをコンパイルしてexeを生成し、 アプリケーションを起動する(exe単体での起動と同じ)。

(7)閉じるボタンのコードを書く

Formの「終了」ボタンをダブルクリックすると画面は次のようになる。ここに終了ボタン(Button2)がクリックされた時のProcedure(手続き)を記述する。

procedure TForm1.Button2Click(Sender: TObject);
begin

end;

beginとend;の間に次のように記入する。

procedure TForm1.Button2Click(Sender: TObject);
begin
  //プログラムの終了
  Close;
end;

//は1行をコメント化(コンパイラはコメント部分を無視する)

Closeは、Formを閉じる命令(正確にはメソッドだから方法?)。アプリケーションのメインフォームを閉じると、そのアプリケーションは終了する。
(ここはApplication.TerminateでもOKだが、 Windowsでは、Application.Terminate でアプリケーションを強制終了させた場合には、OnCloseQueryイベントが実行されない仕様になっているとのこと)。← これは不具合ではなく、Windowsの仕様。

もし、アプリケーション終了時(Windowsの終了やログアウト時も含む)に、何らかの終了処理(中止を含む)を行いたい場合は、OnCloseQueryイベントが実行されるCloseを使用する。(今回は行わないがForm生成時に、例えばTStringListをCreateしてプログラム内で利用するような場合には、CreateしてTry文で使用(~Finally ここで解放 End;)の一般的流れが使えないので、 OnCloseQueryイベントもしくはOnDestroyイベントで、TStringList.Freeのようにして確実に解放しなければならない。)

実行(F9)してFormが表示されたら、「終了」ボタンでアプリケーションを終了できることを確認する。

(8)FormのCreateでPython39-32の有無を確認する

FormがCreateされる時に、Embeddable Python(Python39-32 フォルダ)があることを確認し、必要な諸設定を行う。F12を押すとFormとUnitの表示を交互に切り替えることができる。画面をFormに切り替え、アクティブ(Formのどこかをシングルクリック)にし、オブジェクトインスペクタのイベントタブをクリックして、下にスクロールさせ、OnCreateイベントの右の空白部分をダブルクリックする。自動的にUnit画面に表示が切り替わり、下のようにForm.Create手続き部が生成される。

procedure TForm1.FormCreate(Sender: TObject);
begin

end;

Python39-32フォルダのパスを入れる変数を宣言する。procedureとbeginの間にvar(宣言)を入力して、改行&字下げを行い、文字列型変数AppDataDirを宣言する。必要であればコメントで変数の用途を書いておく。

procedure TForm1.FormCreate(Sender: TObject);
var
  //Python39-32へのPath
  AppDataDir:string;
begin

end;

次に、beginとend;の間にForm.Create手続きで行いたい内容を記述する。

begin

  //Embeddable Pythonの存在の有無を調査
  AppDataDir:=ExtractFilePath(Application.ExeName)+'Python39-32';

  if DirectoryExists(AppDataDir) then
  begin
    //フォルダが存在したときの処理
    MessageDlg('Embeddable Pythonが利用可能です。',
      mtInformation, [mbOk] , 0);
    PythonEngine1.AutoLoad:=True;
    PythonEngine1.IO:=PythonGUIInputOutput1;
    PythonEngine1.DllPath:=AppDataDir;
    PythonEngine1.SetPythonHome(PythonEngine1.DllPath);
    PythonEngine1.LoadDll;
    //PythonDelphiVar1のOnSeDataイベントを利用する
    PythonDelphiVar1.Engine:=PythonEngine1;
    PythonDelphiVar1.VarName:=AnsiString('var1');  //プロパティで直接指定済み
    //初期化
    PythonEngine1.Py_Initialize;
  end else begin
    MessageDlg('Embeddable Pythonが見つかりません!',
      mtInformation, [mbOk] , 0);
    PythonEngine1.AutoLoad:=False;
  end;

end;

Ctrl+Sでコードを上書き保存。保存したら実行(F9)。
ここまでの操作にミスがなければ次のメッセージが表示される。

「OK」をクリックして閉じる

続けてFormが表示されるので、終了ボタンをクリックして閉じる。
画面下のメッセージ欄に次のヒントが表示されることを確認する。

(9) Messageダイアログを使う

[dcc32 ヒント] Unit1.pas(118): H2443 インライン関数 ‘MessageDlg’ はユニット ‘System.UITypes’ が USES リストで指定されていないため展開されません

ヒントの言う通り、 ‘System.UITypes’ を USES リストで指定する。以下のように、30行目付近の implementation (実装・実現部)宣言と、その下の コンパイラ指令 {$R *.dfm}の間が空白行になっているので、ここに「uses」と「 System.UITypes ;」を記述。なお、System.UITypes の後ろには行末を意味するセミコロン;を半角で入力する。

implementation

{$R *.dfm}

implementation の下に「uses」と入力してEnter & 字下げ(TABキー)、
で、次の行に「System.UITypes;」を記述。

implementation

uses
  System.UITypes;  // <-入力する

{$R *.dfm}

{$R *.dfm} はコメントではなく、dfmファイルを見つけて 実行ファイルにリンクさせるコンパイラ指令(命令)。「不要なコメントである」と勘違いして、消してはいけない。

以上が入力した状態。上書き保存(Ctrl+S)して、実行(F9)。メッセージにヒントが表示されないことを確認。 表示されたらメッセージ欄を確認。確認後、Formを閉じる。

警告もヒントも表示されない

(10) 埋め込みPythonと接続する

次に、いよいよ埋め込みPythonと接続する。Unitが表示されている場合はF12キーを押してFormの画面に切り替え、左下の「実行」ボタンをダブルクリックする。表示は自動的に以下のように、Button1Click手続きに切り替わる。

procedure TForm1.Button1Click(Sender: TObject);
begin

end;

初めにPythonのスクリプトを入れる文字列型リストと、Pythonから送られたデータを保存する文字列型リストをローカル変数として、以下のように宣言する。

procedure TForm1.Button1Click(Sender: TObject);
var
  //PythonのScriptを入れる
  strScrList:TStringList;
  //Pythonから送られたデータを保存する
  strAnsList:TStringList;
begin

end;

最初に、Memo1を初期化し、データの入れ物をそれぞれ準備する。

begin

  //初期化
  Memo1.Clear;

  //Scriptを入れるStringList
  strScrList:=TStringList.Create;
  //結果を保存するStringList
  strAnsList:=TStringList.Create;

end;

準備したStringListが処理の最後にきちんと解放されるよう、try文を用いて処理する。
tryと入力してEnterキーを押すと、次の画面のようにfinallyとend;が自動入力される。

begin

  //初期化
  Memo1.Clear;

  //Scriptを入れるStringList
  strScrList:=TStringList.Create;
  //結果を保存するStringList
  strAnsList:=TStringList.Create;

  try

  finally

  end;

end;

StringListの解放処理を先に書いてしまう。これで万一、トラブルが発生しても必ずStringListは処理の最後に解放(メモリが空く)される。

  //Scriptを入れるStringList
  strScrList:=TStringList.Create;
  //結果を保存するStringList
  strAnsList:=TStringList.Create;

  try

  finally
    //StringListの解放
    strAnsList.Free;
    strScrList.Free;
  end;

最後に、バッテリー残量を取得するPython Scriptを文字列型リストへ、1行ずつ書き込んで、Memo1に表示、Python側でMemo1に表示されたスクリプトを実行し、返ってきた結果を文字列型リストに読み込んで、Memo2に表示するコードを記述する。

  try
    //バッテリー残量を取得するPython Script
    strScrList.Add('import psutil');
    //バッテリー残量
    strScrList.Add('btr = psutil.sensors_battery()');
    //バッテリー残量を表示
    strScrList.Add('var1.Value = str("残量:") 
      + str(btr.percent) + str("%")');
    //Scriptを表示
    Memo1.Lines.Assign(strScrList);
    //Execute
    PythonEngine1.ExecStrings(Memo1.Lines);
    //結果を表示
    Memo2.Lines.Assign(strAnsList);
  finally
    //StringListの解放
    strAnsList.Free;
    strScrList.Free;
  end;

入力したら上書き保存(Ctrl+S)して、実行(F9)する。Formが表示されたら、Form上の「実行」ボタンをクリックする。結果は次のようになる。

Memo1には、意図した通り、StringListに入れたPythonのScritが表示されているが、
Memo2は空欄のままである。

Object Pascalのコードをよく読むとPythonEngineをExecuteしてPythonに電池残量を計算させるところまではOKだが、Pythonが計算した結果を「Delphi側が受け取れていない」ことがわかる。

    //Execute
    PythonEngine1.ExecStrings(Memo1.Lines);

    { ここでPythonからの結果通知を受け取る必要がある }

    //結果を表示
    Memo2.Lines.Assign(strAnsList);

(11) OnSetDataイベントを利用する

では、Pythonからの結果通知を受け取るにはどうしたらいいかというと、残念ながらその処理はこのprocedure内には書けない。

結論から言うと、Pythonの返した結果は、Formに配置したPythonDelphiVar1コンポーネントのOnSetDataイベントで受け取ることができる。その処理を実現するため、プログラムに必要な変更を加える。

まず、実行ボタンがクリックされた時の手続きの冒頭で、「結果を保存するStringList」として「strAnsList」というローカル変数を宣言したが、今、結果は「PythonDelphiVar1のOnSetDataイベントで受け取る」ことにした=つまり「別の手続きの中で受け取る」ことになるから、この変数をプログラムのあちこちから使える(見える)プライベートメンバー変数(クラス内部でのみ利用可能な変数) に変更することにする。以下、その処理を示す。

まず、 Button1Click手続きでローカル変数として宣言したstrAnsList変数をコメント化する。

procedure TForm1.Button1Click(Sender: TObject);
var
  //PythonのScriptを入れる
  strScrList:TStringList;
  //Pythonから送られたデータを保存する
  //strAnsList:TStringList;  //コメント化してしまう
begin

22行目付近のprivate部に、このクラス内部でのみ利用可能な プライベートメンバー変数として、strAnsList変数を再宣言する。

  private
    { Private 宣言 }
    //Pythonから送られたデータを保存する
    strAnsList:TStringList;
  public
    { Public 宣言 }
  end;

これでstrAnsList変数は、プライベートメンバー(クラス内部でのみ利用)化され、異なる手続きの中でアクセスできるようになった。

続けて、PythonDelphiVar1のOnSetDataイベントの処理を実装する。F12を押して画面をFormの方に切り替えて、PythonDelphiVar1をクリックして選択する。

選択する

画面左下のオブジェクトインスペクタにPythonDelphiVar1が表示されていることを確認して、イベントタブをクリックし、下にスクロールしてOnSetDataイベント部分の右の空白をダブルクリックする。

OnSetDataの右の空白をダブルクリック

PythonDelphiVar1SetData手続きが自動的に生成されるので、次のコードを記述する。

procedure TForm1.PythonDelphiVar1SetData(Sender: TObject; Data: Variant);
begin
  //値がセットされたら文字列リストに値を追加
  strAnsList.Add(Data);
  Application.ProcessMessages;
end;

これでPython側からDelphi側へ、計算結果を渡せるようになった。ここでは単純な処理しかしていないので実質不要であるが、例えばループ処理を行って何度も結果が返るなど、より複雑な計算処理をPython側で行わせる場合に、確実に結果を受け取れるよう、 Application.ProcessMessagesを「おまじない」として入れてある。

Application.ProcessMessages メソッドは、「Windows がイベントに応答できるようアプリケーションの実行を一時的に停止」する命令であるとのこと。このメソッドについては下記リンク先の説明が詳しい。

Article: 待ち関数の必要性

URL:http://gumina.sakura.ne.jp/CREATION/OLD/COLUMN/CD1MATI.htm

(12)プログラムの完成と動作確認

これで、最低限の機能だけは組み込んだノートPCの電池の残容量を表示するプログラムの完成である。上書き保存(Ctrl+S)して、実行(F9)し、結果を確認する。

電池の残量が表示された

4. PythonEngineのメモリリーク

参考 PythonEngineのメモリリークが起きた時は・・・

別のプログラムでPythonEngineがメモリリークを起こしたことがある。この問題について、次のようにFormのOnDestroyイベントでFinalize処理を行うよう対応したところ、メモリリークは解消された。備忘録として記しておく。

procedure TFormZZZ.FormDestroy(Sender: TObject);
begin
  //これでメモリーリークは発生しなくなった
  //PythonDLLによって割り当てられたすべてのメモリが解放される
  //旧バージョンのPythonEngineの場合
  //PythonEngine1.Finalize;
  //最新バージョン(2021年12月現在)のPythonEngineの場合
  PythonEngine1.Py_Finalize;
  PythonDelphiVar1.Finalize;
end;

5. Delphi11のIDEが真っ白になってしまう問題への対応方法

参考リンク Delphi11のIDEが真っ白になってしまう問題への対応方法

RAD Studio 11のプロジェクトファイル(.dproj、.cbproj)をダブルクリックしてIDEを起動し、デバッグ実行すると、IDEの各ウィンドウが白く表示される

URL:上のLinkをクリックしてください。

6.著作権表示の記載方法

参考:Python4DelphiのLicenseについて

GitHubのPython4Delphiのダウンロードページには「The project is licensed under the MIT License.」とある。これは「改変・再配布・商用利用・有料販売すべてが自由かつ無料」であること、及び使用するにあたっての必須条件はPython4Delphiの「著作権を表示すること」と「MITライセンスの全文」or 「 MITライセンス全文へのLink」をソフトウェアに記載する、もしくは、別ファイルとして同梱しなさい・・・ということを意味する。

したがってPython4Delphiを利用したプログラムの配布にあたっては、ソフトウェアの中で、次のような著作権表示を行うか、もしくは P4DフォルダのルートにあるLicenseフォルダをプログラムに同梱して配布すればよいことになる。

Python4Delphiを利用した場合の著作権表示の記載例:

Copyright (c) 2018 Dietmar Budelsky, Morgan Martinet, Kiriakos Vlahos
Released under the MIT license
https://opensource.org/licenses/mit-license.php

7.お願いとお断り

このサイトの内容を利用される場合は、自己責任でお願いします。記載した内容を利用した結果、利用者および第三者に損害が発生したとしても、このサイトの管理者は一切責任を負えません。予め、ご了承ください。

【関連記事】

Setup Old Python4Delphi

「Delphiで古いPythonForDelphiを使う(おすすめしません)」

OpenCVとNumpyをインストールしたembeddable pythonをDelphiから利用できるようにした。これはその覚書その2。タイトルにあるように古いPython4Delphiをセットアップした時の記録。

1.どなたにもおすすめしません(最新版が便利です)
2.旧バージョンのインストール方法
3.まとめ
4.著作権表示の記載方法
5.お願いとお断り

1.どなたにもおすすめしません(最新版が便利です)

今はどこを探しても、この古いPython4Delphiはダウンロードできないが、もし、それが入手できて、使わなければならなくなった時には参考になる(カモ)。

ちなみに、ずっと愛用していた(10年以上前のバージョン?の)Python4Delphiは最新のDelphi11に、ここに記載した方法でほぼ問題なくインストールでき、かつ、期待通りに(VCLコンポーネントとして)動作した。←が、どなたにもおすすめしません。

最新のPython4DelphiをDelphi10.3以降のバージョンにインストールする方法は・・・

2.旧バージョンのインストール方法

以下の内容を利用される場合は、自己責任でお願いします。以下に記載した内容を利用した結果、利用者および第三者に損害が発生したとしても、このサイトの管理者は一切責任を負えません。予め、ご了承ください。

まず、困ったことに、ここで取り上げているPython4Delphiのバージョンがいくつなのか、どれくらい前にリリースされたものなのか、いつ、どこから入手したものなのか、いずれもわからない。

気が付いた時には、My PCの中にいた・・・。そんな存在である。

python4delphi-master\PythonForDelphiにあるDeployment.txtには、See document “Deploying P4D.PDF” first.・・・とあるので、これを読むとドキュメントの日付は「5/1/2005」となっている。もしかしたら、それくらい前のものかもしれない。

fmxには非対応のようで、vcl関連のファイルのみで構成されている。Readme.txtで紹介されているファイルとフォルダの構成は以下の通り。

FILES:
Readme.txt This file.
Python.txt Infos about Python, and further references.
Changes.txt List of all changes since the first release.
Tutorial.txt A simple tutorial to use the PythonEngine
To do.txt A to do list.
Deploying P4D.pdf Notes on the Deployment of your applications using Python for Delphi.
C++ Builder Notes.txt Notes on using C++Builder with the Python for Delphi components.
PythonAtom.hlp A help file explaining the use of TPythonAtom
Demos A folder containing several demos of Python for Delphi.
Components\Python.* The “Python for Delphi” packages.
Components\Sources\Core The source folder of the core “Python for Delphi”.
Lib Library of Python modules.
PythonIDE A Python developpment environment written in Delphi.
See PythonIDE\Readme.txt for the required components.
Modules Contains the Delphi\Delphi.dpr project that creates the Modules\Delphi.pyd Python module
that allows you to interact with Delphi VCL objects from Python.

同じく Readme.txt にあるインストール方法は、次の通り。この手順でDelphi10.4にインストール。

INSTALLATION:
install the Python for Windows distribution (http://www.python.org/).

1) Install the core components
For recent versions of Delphi, install the “Python_d” package located in the
Components folder and add the folder “…\Components\Sources\Core” to the library path.

1) コアコンポーネントのインストール

Components フォルダにある “Python_d” パッケージをインストールし、ライブラリパスに “…\Components\Sources\Core” フォルダを追加してください。

注意:異なるバージョンのDelphiがインストールされている環境では、Python_D.dpkをダブルクリックすると拡張子dpkに関連付けされたバージョンのDelphiが起動してしまう(あたりまえ)。このような場合は、P4D環境をインストールしたいDelphiを起動し、ファイルメニューの「開く」からPython_D.dpkを指定してパッケージをインストールする。

また、「開く」のは「Python_D.dpk」で、「Python_D.dproj」ではないことにも注意する。で、「Python_D.dpk」を開いたら・・・

プロジェクトマネージャーに表示されたPython_D.bplを右クリックして、表示されたサブメニューの「インストール」をクリック。

【Delphi10.4の場合】

この方法でエラーなくインストールできた。(・・・気がするだけかもしれない)

【Delphi11の場合】

次のエラーが発生!

[dcc32 エラー] PythonEngine.pas(63): E2029 ‘INTERFACE’ が必要な場所に 識別子 ‘Error’ があります。

エラーが起きている場所を確認すると・・・

unit PythonEngine;

{ TODO -oMMM : implement tp_as_buffer slot }
{ TODO -oMMM : implement Attribute descriptor and subclassing stuff }

{$IFNDEF FPC}
{$IFNDEF DELPHI2010_OR_HIGHER}
  Error! Delphi 2010 or higher is required! ←ここでエラーが発生!
{$ENDIF}
{$ENDIF}

とりあえず、この1行をコメント化して再実行。

{$IFNDEF FPC}
{$IFNDEF DELPHI2010_OR_HIGHER}
  //Error! Delphi 2010 or higher is required!
{$ENDIF}
{$ENDIF}

エラーは発生せず。表示されたメッセージを読み、インストールの成功を確認。

もう一度Python_D.bplを右クリックして、表示されたサブメニューの「上書き保存」をクリック。これでパッケージのインストールは完了。

「ライブラリパスに “…\Components\Sources\Core” フォルダを追加・・・」とあるが、パスを追加しなくてもプログラムの動作に必要な.pasファイルをプロジェクトファイルのあるフォルダにコピーすれば動くから、ここでは「追加しない」ことを選択。

重要 特別な理由のない限り、最新版のPython4Delphiを選択することをお勧めします。
(最新版のP4Dパッケージを登録する場合は、ライブラリパスをきちんと設定しましょう)

2) Install the VCL components (this is optional)

For recent versions of Delphi, install the “PythonVCL_d” package located in the Components folder and add the folder “…\Components\Sources\Core” to the library path.

2) this is optional ・・・とあるので、オプションならやらなくてもいいか!ということで実行しない。

3) Build Modules\Delphi\Delphi.dpr (This is optional and unsupported)

Once the project is build you can either extend the Python path with ..\Modules or copy ..Modules\Delphi.pyd to C:\Python24\DLLs, to be able to import the Delphi module from Python.

Note that you can try this module by invoking the ..\Modules\TestApp.py script.

3) This is optional and unsupported ・・・とあり、オプションである上にサポートなしとあるので、これも実行しない。

3.まとめ

(1) Readme.txt の INSTALLATION の手順1)のみ実行すればOKだった。

(2)DelphiのXXX.dprojファイルのあるフォルダへコピーするPython関係のファイルは以下の通り。他のプロジェクトでも利用する場合は、ライブラリパスへ登録した方が使いやすくなるが、このP4Dは最新版ではないので、このようにして利用した(←過去形であることに注意)。

動作に必要なファイル

4. 著作権表示の記載例

参考:Python4DelphiのLicenseについて

GitHubのPython4Delphiのダウンロードページには「The project is licensed under the MIT License.」とある。これは「改変・再配布・商用利用・有料販売すべてが自由かつ無料」であること、及び使用するにあたっての必須条件はPython4Delphiの「著作権を表示すること」と「MITライセンスの全文」or 「 MITライセンス全文へのLink」をソフトウェアに記載する、もしくは、別ファイルとして同梱しなさい・・・ということを意味する。

したがってPython4Delphiを利用したプログラムの配布にあたっては、ソフトウェアの中で、次のような著作権表示を行うか、もしくは P4DフォルダのルートにあるLicenseフォルダをプログラムに同梱して配布すればよいことになる。

Python4Delphiを利用した場合の著作権表示の記載例:

Copyright (c) 2018 Dietmar Budelsky, Morgan Martinet, Kiriakos Vlahos
Released under the MIT license
https://opensource.org/licenses/mit-license.php

5.お願いとお断り

このサイトの内容を利用される場合は、自己責任でお願いします。ここに記載した内容を利用した結果、利用者および第三者に損害が発生したとしても、このサイトの管理者は一切責任を負えません。予め、ご了承ください。

【関連記事】

Setup Python4Delphi

「DelphiからPythonを使えるようにする」

追々記(20231208)

さらにカンタンな方法がありました!

https://coding-tips-memoranda.com/rad-studio-12-0%e3%81%abpython4delphi%e3%82%92%e3%82%a4%e3%83%b3%e3%82%b9%e3%83%88%e3%83%bc%e3%83%ab%e3%81%99%e3%82%8b%ef%bc%81%ef%bc%88%e3%81%9d%e3%81%ae%ef%bc%92%ef%bc%89/

以下、上記の方法にたどり着くまでの、長い長い歩みの記録です。

追記(20231126)

RAD Studio 12.0(Delphi 12.0 Athens)のリリースに合わせ、Python4Delphi も更新されました。
RAD Studio 12.0(Delphi 12.0 Athens)へのインストールに対応した Python4Delphi (20231109版)のインストール記事は、以下のリンク先にあります。

RAD Studio 12.0(Delphi 12.0 Athens)に Python4Delphi をインストールされる場合は、こちらをご参照ください。

以下、2021年12月31日に掲載した記事です(内容は当時のままです)。

OpenCVとNumpyをインストールしたembeddable pythonをDelphiから利用できるようにした。これはその覚書。

1.Python4Delphiのダウンロード
2.P4DパッケージをDelphiにインストール
3.ライブラリパスの設定
4.P4Dの著作権表示の記載例
5.お願いとお断り

1.Python4Delphiのダウンロード

まず最初に、Python for Delphi(P4D)をGitHubから入手してDelphiにインストール。

P4Dの入手先URL https://github.com/pyscripter/python4delphi

Git Bashがない場合は、Codeをクリックすると表示されるサブメニューのいちばん下にDownLoad ZIPがあるので、これをクリックしてZIPファイルをダウンロードし、任意の場所(フォルダ)に解凍する(ここではダウンロードするフォルダの名前を「P4D」として説明)。

Download ZIPをクリック

Git Bashがある場合は・・・

Codeをクリック → 表示されるサブメニューからURLをコピー

で、Git Bashがあれば開く。

Git Bashは公式サイト https://gitforwindows.org/ から入手可能。

Git Bashでは、ls(エルエス)コマンドで今いる場所が表示され、cdコマンドでディレクトリの移動ができる。今、いる場所の直下のフォルダに移動するのであれば、Git Bashの画面に直接「cd フォルダ名」と入力してEnterキーを押す。

今、いる場所の直下に新しくフォルダを作成する場合は「mkdir フォルダ名」と入力してEnterキーを押す。

1階層上に移動したい場合は、「cd ../」と入力してEnterキーを押す。

階層の深いフォルダへ移動したい場合は、「cd」+半角スペースを入力後、そのフォルダをGit Bashの画面上へドラッグ&ドロップすればOK!

ここでは、Git Hubのリポジトリをクローンする「中が空の任意のフォルダ」を選ぶ(上の図ではあらかじめ作成しておいたP4Dフォルダを選んでいる)。

フォルダの内容が空であれば、フォルダの名前は何でもOKだが、後のPython4Delphiのインストール時に、「インストール元フォルダとして選択するフォルダとなる」ことに十分注意(フォルダ名を忘れないように)する(ここではフォルダ名を「P4D」としている)。

Git cloneと入力し、半角スペースを入れ、画面を右クリックして表示されるサブメニューのPasteをクリック。Enterキーを押すとダウンロードが始まる。

Enterキーを押してダウンロード開始。
ダウンロード終了時の画面

※ Git Bashがない場合は、Codeをクリックすると表示されるサブメニューのいちばん下にDownLoad ZIPがあるので、これをクリックしてZIPファイルをダウンロードし、任意の場所(フォルダ)に解凍する。(再掲)

2.P4DパッケージをDelphiにインストール

ここで超重要ポイントがひとつ

ダウンロードが無事完了すると、P4Dフォルダの中には「python4delphi」フォルダが出来ている。

(ZIPファイルを解凍した場合は「python4delphi-master」フォルダが出来る)

このフォルダの名前を手動で「P4D」に変更(リネーム)する。

「P4D」フォルダの中(1階層下)に「P4D」フォルダがあることになるが、これでOK! 理由は以下の通り。

C:\XXX\P4D\P4D\Install\Readme.mdには、以下の記述が・・・

P4D Installation using MultiInstaller

Use for Delphi Seattle (10.4) or later to install all packages in one step.

  1. Clone or copy the Python4Delphi git repository to a folder of your choice. The setup.ini file assumes that the folder is called “P4D”. If you chose to name your folder differently then modify the “Folder” option in setup.ini.
  2. Close all Delphi IDEs running.
  3. Run MultiInstaller.exe
  4. Select the packages you want and press Next
  5. In the dialog box specify the parent folder of “P4D” (i.e. the folder containing the directory to which you have copied Python4Delphi) and the Delphi target version. Then press Next to install the components

Google先生曰く・・・

MultiInstallerを使用したP4Dインストール

Delphi Seattle(10.4)以降で使用して、すべてのパッケージを1つのステップでインストールします。

  1. Python4Delphigitリポジトリのクローンを作成するか選択したフォルダーにコピーします。 setup.iniファイルは、フォルダが「P4D」と呼ばれることを前提としています。フォルダに別の名前を付けることを選択した場合は、setup.iniの[フォルダ]オプションを変更します。
  2. 実行中のすべてのDelphiIDEを閉じます。
  3. MultiInstaller.exeを実行します。
  4. 必要なパッケージを選択して、[次へ]を押します。
  5. ダイアログボックスで、「P4D」の親フォルダ(つまり、Python4Delphiをコピーしたディレクトリを含むフォルダ)とDelphiのターゲットバージョンを指定します。次に、[次へ]を押してコンポーネントをインストールします。

Python4Delphiをコピーしたディレクトリを「P4D」にリネームして、さらに、インストール時に表示されるダイアログボックスでは・・・

「その親フォルダを指定せよ」

と言っている・・・。

C:\XXX\P4D\P4D\Installにある「MultiInstaller.exe」を起動
デフォルトですべてにチェックが入っている。そのままNextをクリック。
親の方のP4Dフォルダを選択し、OKをクリック
Compile packages and install on IDE にチェック(My環境ではDelphi11しかないので、チェックを入れると自動的にRAD Studio 11 Alexandriaのオプションが選択された)。

複数バージョンのDelphiがインストールされている環境であれば、インストールしたいバージョンを選択することになるはず。

Nextをクリックして続行。無事終了すれば下のような画面が表示される。

Delphi11にも無事インストールできた(あらかじめInstallフォルダ内にあったSetup.iniを確認したところ、Pathの設定に10.4+とあるから大丈夫と思ったが)。Finishをクリックしてインストール終了。

DelphiのIDEを起動して確認。

パレットに7匹のヘビを無事発見。

3.ライブラリパスの設定(確認)

追加したP4Dのパッケージを使用する場合、パッケージをインストールした後で、
「ツール」→「オプション」→「言語」→「Delphiオプション」→「ライブラリ」の順にクリックして下の画面を表示する(Delphi11の場合)。

「ツール」→「オプション」で上の画面が表示されるので、左ペインでさらに「言語」→「Delphi」→「ライブラリ」と進み、次に右ペインのライブラリパス(B)の赤枠囲みの…をクリックする。

表示される画面で、ライブラリのSourceファイルがあるフォルダのパスを登録する。ライブラリのパスの設定はターゲットにするそれらのプラットフォームごとに設定する必要がある。上の画面では 「Windows 32ビット」 のプラットフォームに対して設定している。 必要であれば、「Windows 64ビット」 のプラットフォームに対しても設定する。

ライブラリのSourceファイルは、PCを変更した場合でも容易に参照できるよう、
絶対に忘れない場所に置くようにしている。
また、上の例では最新版のP4DのSourceが階層構造を持っているため、共通利用するものとそうでないもの(vcl/fmx)を、それぞれ分けて登録している。

コンパイルを実行すると、Delphiはいちばん最初にプロジェクトファイル(.dproj)のあるフォルダ(ここはパスが通っているから登録は不要)を検索し、必要なユニットファイル等の有無を確認。もし、そこに必要なファイルがなければ、この画面に登録したライブラリパスを検索するようだ。

まとめ

(1)Python4Delphiをダウンロードするフォルダの名称は任意だが、そこに作られるフォルダ「 python4delphi 」は「P4D」にリネームする。

(2)MultiInstaller.exeを実行してインストール先フォルダを指定する際には、上でリネームした「P4D」フォルダの1階層上のフォルダを指定する。

(3)パッケージのインストール後、コンパイル時に必要なSourceファイルのある場所をライブラリパスに登録する。

4.P4Dの 著作権表示の記載例

参考:Python4DelphiのLicenseについて

GitHubのPython4Delphiのダウンロードページには「The project is licensed under the MIT License.」とある。これは「改変・再配布・商用利用・有料販売すべてが自由かつ無料」であること、及び使用するにあたっての必須条件はPython4Delphiの「著作権を表示すること」と「MITライセンスの全文」or 「 MITライセンス全文へのLink」をソフトウェアに記載する、もしくは、別ファイルとして同梱しなさい・・・ということを意味する。

したがってPython4Delphiを利用したプログラムの配布にあたっては、ソフトウェアの中で、次のような著作権表示を行うか、もしくは P4DフォルダのルートにあるLicenseファイルをプログラムに同梱して配布すればよいことになる。

Python4Delphiを利用した場合の著作権表示の記載例:

Copyright (c) 2018 Dietmar Budelsky, Morgan Martinet, Kiriakos Vlahos
Released under the MIT license
https://opensource.org/licenses/mit-license.php

5.お願いとお断り

このサイトの内容を利用される場合は、自己責任でお願いします。ここに記載した内容を利用した結果、利用者および第三者に損害が発生したとしても、このサイトの管理者は一切責任を負えません。予め、ご了承ください。

【関連記事】