OpenCV 画像認識 detectMultiScale を実行してプロセスが終了しない

Windows環境、Python で、OpenCV で、画像から顔検出
OpenCV: Face Detection using Haar Cascades

顔認識として、以下から取得したカスケードファイル
opencv/data/haarcascades at master · opencv/opencv · GitHub

haarcascade_frontalface_default.xml 正面を向いた顔の認識用カスケードファイル
haarcascade_eye.xml 瞳を認識するカスケードファイル

(他にも、haarcascade_smile.xml など、笑顔の認識用などあるので参考に)

これで、cv2.CascadeClassifier より、
抽出処理 detectMultiScale を実行すると、処理が終わってもプロセスは停止しない。
OpenCVは、opencv-python 4.5.1.48 を使っている。

良い方法が見つからなかったので、並行処理プロセス multiprocessing で実行させる。
サンプル
cascade というフォルダに検出用のカスケードファイルを置き、
検出対象の画像pahと、検出後、検出部分を四角形で囲んだ画像を書込むpathを指定して実行する
クラスを用意して、multiprocessing で実行、
join(timeout=1) で待ち合わせて、.terminate() で終了させる。

# -*- coding: UTF-8 -*-
import cv2
import multiprocessing

class facecatch():
    def __init__(self):
        face_cascade_path = 'cascade/haarcascade_frontalface_default.xml'
        eye_cascade_path = 'cascade/haarcascade_eye.xml'
        self.face_cascade = cv2.CascadeClassifier(face_cascade_path)
        self.eye_cascade = cv2.CascadeClassifier(eye_cascade_path)
    def catch(self, inpath, out):
        src = cv2.imread(inpath)
        src_gray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)
        faces = self.face_cascade.detectMultiScale(src_gray)
        count = 0
        for (x, y, w, h) in faces:
            face = src[y: y + h, x: x + w]
            face_gray = src_gray[y: y + h, x: x + w]
            eyes = self.eye_cascade.detectMultiScale(face_gray)
            if len(eyes) == 2:
                count = count + 1
                cv2.rectangle(src, (x, y), (x + w, y + h), (255, 0, 0), 2)
        if count > 0:
            cv2.imwrite(out, src)
        return count

class Worker(multiprocessing.Process):
    def __init__(self):
        super().__init__()
    def setImagePath(self, imgpath, outpath):
        self.imgpath = imgpath
        self.outpath = outpath
    def run(self):
        self.f = facecatch()
        self.cnt = self.f.catch(self.imgpath, self.outpath)
        print('検出 count = %d' % self.cnt)
        return self.cnt
    def getcount(self):
        return self.cnt

if __name__ == '__main__':
    p = Worker()
    p.setImagePath('data/lena.jpg', 'out/result.jpg')
    p.start()
    p.join(timeout=1)
    p.terminate()
    print('finish !')

このサンプルは、顔の輪郭の中で、瞳を認識(瞳を2個検出)しないと、顔として検出したことにしない方法である。

deselect_node.jstree Event

jsTree で選択中のノードを解除する操作は、ctrl キーを押しながら
選択しているノードをマウスクリックする。

このイベントを拾うイベント名は、deselect_node.jstree

よって、 選択が外れた時の処理を記述する場合は、、

$('#tree').jstree({
       // 省略
}).on('deselect_node.jstree', function(e, data){
     // TODO 選択が外れた時の処理
});

jsTree描画オブジェクトを JSON にする

jsTree で描画するツリー構造イメージを、ノード名を JSON キー、JSON値は jsTreeデータオブジェクト のIDで、
JSON を作成する。
https://www.jstree.com/

f:id:posturan:20210110165458j:plain

RUNボタンを押したら、とりあえず、JSON を生成してコンソールログ出力する。
HTML

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>sample</title>
<link href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.5.0/css/all.min.css" rel="stylesheet">
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet"/>
<link href="jstree/themes/default/style.min.css" rel="stylesheet" type="text/css"/>
<link href="css/develop.css" rel="stylesheet" type="text/css"/>
<script src="https://code.jquery.com/jquery-3.5.1.min.js" type="text/javascript"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jstree@3.3.11/dist/jstree.min.js" type="text/javascript"></script>
<!-- ツリーデータ JSON = jsTree 描画用データ  -->
<script src="treedata.js" type="text/javascript"></script>
<!-- jsTree の描画データ を JSONに変換 -->
<script src="sample.js" type="text/javascript"></script>
<!-- jsTree 描画 -->
<script src="develop-tree.js" type="text/javascript"></script>
<style type="text/css">
.tree-div{
   border: 1px solid;
   width: 300px;
   margin: 10px;
}
</style>
</head>
<body>
<div id="tree" class="tree-div"></div>

<button id="run" type="button">RUN</button>
</body>
</html>

ツリーデータ JSON = jsTree 描画用データである
treedata.js

window.TREE= [
   {
    "id": "1",
    "icon": "jstree-notIcon",
    "text": "A",
    "children": [
         {
           "id": "11",
           "icon": "jstree-notIcon",
           "text": "sub_A1",
           "children": []
         },
         {
           "id": "12",
           "icon": "jstree-notIcon",
           "text": "sub_A2",
           "children": []
         },
      ]
   },
   {
     "id": "2",
     "icon": "jstree-notIcon",
     "text": "B",
     "children": []
   },
   {
     "id": "3",
     "icon": "jstree-notIcon",
     "text": "C",
     "children": [
         {
           "id": "31",
           "icon": "jstree-notIcon",
           "text": "sub_C",
           "children": [
               {
                 "id": "32",
                 "icon": "jstree-notIcon",
                 "text": "sub_CC",
                 "children": []
               }
            ]
         }
      ]
   }
]


jsTree 描画のdevelop-tree.js の一部(長いので省略)、
(treedata.js で、読込んだ window.TREE を jstree() で指定する)

$(function(){
   $.jstree.defaults.core.themes.variant = "large";
   $.jstree.defaults.core.themes.responsive = true;

   $('#tree').jstree({
      'plugins': [ 'contextmenu','dnd' ],
      'core':{
         'data': window.TREE,
         'check_callback' : true,
      },
      "contextmenu":{
         "items":function($node){
            return {

sample.js の内容、
jsTree描画オブジェクトを JSON にする処理で、
treeToJson関数を定義している。
再帰関数 _tjson() を用意するのがpoint

$(function(){
   /**
    * jsTree data Object to JSON
    */
   var treeToJson = function(treeObj){
      var _tjson = function(o, c){
         if (c.children.length==0){
            return c.id;
         }else{
            c.children.forEach(function(e){
              o[e.text] = _tjson({}, e);
            });
            return o;
         }
      };
      json = {};
      treeObj.forEach(function(e){
         json[e.text] = _tjson({}, e);
      });
      return json;
   };
   /* ボタン押下で、jsTree のデータObject を JSON にして
      コンソールログ出力
    */
   $('#run').click(function(e){
      tree = window.TREE;
      json = treeToJson(tree);
      console.log(JSON.stringify(json, null, 3))
   });
});

Blob作成、dispatchEvent イベントの伝播でダウンロード

実現させていく例として2年程前に書いた
oboe2uran.hatenablog.com

oboe2uran.hatenablog.com

で書いた、jsTree 編集データJSONのダウンロードする方法です。
今回は、Python eel を使いません。

初期表示の JSON もURL指定読込みでなく、JSソースとして用意します。
jsTreeJSON を任意の window変数、window.TREE として宣言する
だけの JS を用意する。
treedata.js

window.TREE= [
  {
    "id": "1",
    "icon": "jstree-notIcon",
    "text": "ルート",
    "children": [
      {
        "id": "11",
   :
  (省略)

HTMLヘッダの読む込みも、、

<script src="js/develop-tree.js" type="text/javascript"></script>
<script src="js/develop.js" type="text/javascript"></script>

ここから、以下のようになる。

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>develop.html</title>
<link href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.5.0/css/all.min.css" rel="stylesheet">
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet"/>
<link href="jstree/themes/default/style.min.css" rel="stylesheet" type="text/css"/>
<link href="css/develop.css" rel="stylesheet" type="text/css"/>
<script src="https://code.jquery.com/jquery-3.5.1.min.js" type="text/javascript"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jstree@3.3.11/dist/jstree.min.js" type="text/javascript"></script>
<script src="treedata.js" type="text/javascript"></script>
<script src="js/develop-tree.js" type="text/javascript"></script>
<script src="js/develop.js" type="text/javascript"></script>
</head>

develop-tree.js の core の data に、この window.TREE をセットする。

/**
 * develop-tree.js
 */
$(function(){
   $.jstree.defaults.core.themes.variant = "large";
   $.jstree.defaults.core.themes.responsive = true;

   $('#tree').jstree({
      'plugins': [ 'contextmenu','dnd' ],
      'core':{
         'data': window.TREE,
         'check_callback' : true,
      },
   :
  (省略)

Python eel で作成した時は、以下のようにしていた。

      'core':{
         'data':{
             "url":"./tree.json",
             "dataType":"json"
         },

以前は、SAVE ボタンを押した時に、これら Python , HTML を置いた場所(PC)に
TreeデータJSON を保存したが、
Python eel でない環境では、ダウンロードさせるしかありません。

そこで、ダウンロードさせるには、ボタンクリック時の処理として
マウスイベントを生成して dispatchEvent イベントの伝播でダウンロードさせます。

$(function(){
   $('#create').click(function(){
      var v = $('#tree').jstree(true).get_json('#', {flat:true});
      var data = JSON.stringify(v, null, 2);
      var blob = new Blob([data], {type: 'text/json'}),
      e = document.createEvent('MouseEvents'),
      a = document.createElement('a');
      a.download = "test.json";
      a.href = window.URL.createObjectURL(blob);
      a.dataset.downloadurl =  ['text/json', a.download, a.href].join(':');
      e.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
      a.dispatchEvent(e);
    });
});

vue-js-modal を試す

1年以上も前に、Vue.js Bootstrap のモーダルのサンプルを書いていた。
oboe2uran.hatenablog.com

oboe2uran.hatenablog.com

Vue.js でモーダルウィンドウで、リサイズ可や dragable 等ができないことに不満があった。

GitHub - euvl/vue-js-modal: Easy to use, highly customizable Vue.js modal library. が、MITライセンスで公開されています。

Installation | Vue.js Modal より、

Properties を参照すれば、リサイズやdraggable の説明がある。
Events を参照すれば、モーダルウィンドウを表示/非表示時のタイミングで
イベントを捕捉する方法が載っている。

Bootstrap デザインでサンプルを書いてみます。
CSSと JSの指定です。

<link type="text/css" rel="stylesheet" href="https://unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="https://unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.min.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vue-js-modal@2.0.0-rc.6/dist/styles.css">
<link rel="stylesheet" href="test.css">
<script src="https://code.jquery.com/jquery-3.4.1.min.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-js-modal@2.0.0-rc.6/dist/index.min.js"></script>
<script src="https://unpkg.com/babel-polyfill@latest/dist/polyfill.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"></script>

HTML

<body>
<div id="app" class="container-fluid all">
  <h1>Vue.js + Bootstrap モーダル</h1>
  <div  class="col-md-12">
    <button v-on:click="show" type="button" class="btn btn-secondary custom-btn-in" >設定</button>
    <!-- モーダルウィンドウ -->
    <modal name="sample-modal" :draggable="false" :width="400" :height="200" :adaptive="true" :resizable="false"
           @before-open="beforeOpen" @opened="opend" @before-close="beforeClose" @closed="closed">
     <!-- モーダルヘッダー -->
      <div class="modal-header">
        <h5 class="modal-title" id="exampleModalLabel" >タイトル</h5>
        <button v-on:click="hide" type="button" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <!-- モーダル本文 -->
      <div class="modal-body">
          <div class="form-group row">
              <label for="user" class="col-3">Name</label>
              <input type="text" name="userName" id="userName" v-model="uname"  class="col-3 form-control">
          </div>
      </div>
      <!-- モーダルフッター -->
      <div class="modal-footer">
          <button type="button" class="btn btn-primary" v-on:click="modalSubmit" data-dismiss="modal">更新</button>
          <button type="button" class="btn btn-secondary" v-on:click="hide" data-dismiss="modal">閉じる</button>
      </div>
    </modal>
  </div>
  <div>
    <span>{{message}}</span>
  </div>
</div>
</body>
<script src="test.js"></script>

test.css

.modal-header, .modal-body {
  padding: 10px 30px;
}
.modal-header {
  border-bottom: 1px solid #d0d0d0;
}

Vue.use(window["vue-js-modal"].default);
で適用させます。

test.js
show メソッド、hide メソッドでモーダルウィンドウの表示/非表示を切り替えます。

Vue.use(window["vue-js-modal"].default);

var app = new Vue({
  el: '#app',
  data:{
    message: "test",
    uname: ""
  },
  methods: {
    show : function() {
      this.$modal.show('sample-modal');
    },
    modalSubmit: function(v){
      console.log( this.uname );
      this.message = this.uname;
      this.$modal.hide('sample-modal');
    },
    hide : function(){
      this.$modal.hide('sample-modal');
    },
    beforeOpen : function(e){
        console.log(e);
    },
    opend : function(e){
        console.log(e);
    },
    beforeClose : function(e){
        console.log(e);
    },
    closed : function(e){
        console.log(e);
    }
  }
})

modelタグ属性で、 @before-open="beforeOpen" と指定しているので、
メソッド = beforeOpen が働きます。

f:id:posturan:20201228150728j:plain

デコレータを理解する為のサンプル

単純にデコレータを理解する為のサンプルで、
AOPを書く方法という観点のサンプル

メソッドをラップするという点を忘れない為のサンプル

def query(param1, param2=None):
    res = [1, 2, 3]
    def _wrap1(function):
        def _wrap2(*args, **kwargs):
            print('前処理:decorate parameter {} {}'.format(param1, param2))
            print('前処理:args   = {}'.format(args))
            print('前処理:kwargs = {}'.format(kwargs))
            i = function(*args, **kwargs)
            print('後処理:{}'.format(i))
            res.append(i)
            return res
        return _wrap2
    return _wrap1

@query(param1='a', param2='b')
def foo(x, y=None):
    print('foo')
    print(x)
    print(y)
    return 4

r = foo(12)
print('r = %s' % r)

結果

前処理:decorate parameter a b
前処理:args   = (12,)
前処理:kwargs = {}
foo
12
None
後処理:4
r = [1, 2, 3, 4]

HttpURLConnection で、PATCH を送る方法

java.net.HttpURLConnection は、HTTP Method  PATCH を送信しようとすると、

 Caused by: java.net.ProtocolException: Invalid HTTP method: PATCH

とエラーになってしまう。
これは、HttpURLConnection setRequestMethod が、
以下のHTTPメソッドしか受け入れないからである。

 GET, POST, HEAD, OPTIONS, PUT, DELETE, TRACE

Stackoverflow で以下を見つけたが、、
java - HttpURLConnection error: Invalid HTTP method PATCH - Stack Overflow

この Stackoverflow の Solution 2: の方法は、setRequestMethod が検証するメソッド名の配列 String
リフレクションで、HttpURLConnection しか参照しない String
methods に、
元の配列に許可したいメソッド名を追加する方法だ。

でもよくよく考えると、結局は HttpURLConnection が抱え持つ
String method に実行するメソッド名を検証してセットするだけだから、
PATCH を実行したければ、setRequestMethod で "PATCH" をセットするのではなく、
直接リフレクションで String method にセットすれば良い。

だから、以下のメソッドで充分なのである。

private static void setMethodForce(HttpURLConnection conn, String method){
   try{
      Field methodField = HttpURLConnection.class.getDeclaredField("method");
      methodField.setAccessible(true);
      methodField.set(conn, method);
   }catch(NoSuchFieldException | IllegalAccessException e){
      throw new IllegalStateException(e);
   }
}
URL url = new URL("http://xxxx/xxxxx/xxxx");
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setMethodForce(conn, "PATCH");