2021年12月27日月曜日

D2メガテンの烙印の失敗がどの程度かモデルで検証してみた。

どうも、あまりにも失敗が目立って酷いので、コードを書いて検証してみることにした。

烙印のステータスアップの成功率 10% に対して40回以上失敗が連続するのは、どの程度で、最高何回ぐらい続くのか?
#include <iostream>
#include <random>
#include <array>
#include <algorithm>
#include <numeric>
#include <vector>

#define TRY_COUNT 10000

using seed_v_t = std::array<std::random_device::result_type, sizeof(std::mt19937)/sizeof(std::random_device::result_type)>;
seed_v_t create_seed_v() {
  std::random_device rnd;
  seed_v_t sed_v;
  std::generate(sed_v.begin(), sed_v.end(), std::ref(rnd));
  return sed_v;
}


std::mt19937 create_random_engine()
{
  const auto sed_v = create_seed_v();
  std::seed_seq seq(sed_v.begin(), sed_v.end());
  return std::mt19937(seq);
}

std::mt19937& random_engine()
{
  static thread_local std::mt19937 engine = create_random_engine();
  return engine;
}

int main() {
  std::mt19937& engine = random_engine();
  std::uniform_int_distribution<int> dist(0, 10);
  std::vector<int> cnts;
  int bingo = 0;
  int cnt = 0;
  for(int i = 0; i < TRY_COUNT; ++i) {
    int val = dist(engine);
    if( val != 0 ) ++cnt;
    else {
      cnts.push_back( cnt );
      cnt = 0;
    }
  }
  int acc = std::accumulate( cnts.begin(), cnts.end(), 0);
  std::cout << std::endl;
  std::cout << "成功回数         = " << cnts.size() << " / " << TRY_COUNT << std::endl;
  std::cout << "平均施行回数     = " << (acc / (double)cnts.size()) << std::endl;
  std::cout << "31回以上連続失敗 = " << std::count_if( cnts.begin(), cnts.end(), [](int x) { return (x > 30); } ) << std::endl;
  std::cout << "41回以上連続失敗 = " << std::count_if( cnts.begin(), cnts.end(), [](int x) { return (x > 40); } ) << std::endl;
  std::cout << "51回以上連続失敗 = " << std::count_if( cnts.begin(), cnts.end(), [](int x) { return (x > 50); } ) << std::endl;
  std::cout << "61回以上連続失敗 = " << std::count_if( cnts.begin(), cnts.end(), [](int x) { return (x > 60); } ) << std::endl;
  std::cout << "71回以上連続失敗 = " << std::count_if( cnts.begin(), cnts.end(), [](int x) { return (x > 70); } ) << std::endl;
  std::cout << "最大連続失敗回数 = " << *std::max_element( cnts.begin(), cnts.end() ) << std::endl;
  std::cout << "連続40回以上の失敗に出会う確率 = " << 
    std::count_if( cnts.begin(), cnts.end(), [](int x) { return (x > 40); } ) / (double)cnts.size() * 100.0 << "%" << std::endl;
}

結果は

成功回数         = 922 / 10000
平均施行回数     = 9.83406
31回以上連続失敗 = 50
41回以上連続失敗 = 20
51回以上連続失敗 = 8
61回以上連続失敗 = 3
71回以上連続失敗 = 0
最大連続失敗回数 = 70
連続41回以上の失敗に出会う確率 = 2.1692%

成功回数         = 883 / 10000
平均施行回数     = 10.3001
31回以上連続失敗 = 50
41回以上連続失敗 = 22
51回以上連続失敗 = 7
61回以上連続失敗 = 1
71回以上連続失敗 = 1
最大連続失敗回数 = 81
連続41回以上の失敗に出会う確率 = 2.49151%

成功回数         = 884 / 10000
平均施行回数     = 10.3032
31回以上連続失敗 = 60
41回以上連続失敗 = 27
51回以上連続失敗 = 11
61回以上連続失敗 = 5
71回以上連続失敗 = 2
最大連続失敗回数 = 84
連続41回以上の失敗に出会う確率 = 3.0543%


成功回数         = 881 / 10000
平均施行回数     = 10.3507
31回以上連続失敗 = 55
41回以上連続失敗 = 20
51回以上連続失敗 = 11
61回以上連続失敗 = 4
71回以上連続失敗 = 1
最大連続失敗回数 = 75
連続41回以上の失敗に出会う確率 = 2.27015%

成功回数         = 934 / 10000
平均施行回数     = 9.67024
31回以上連続失敗 = 46
41回以上連続失敗 = 20
51回以上連続失敗 = 5
61回以上連続失敗 = 0
71回以上連続失敗 = 0
最大連続失敗回数 = 57
連続41回以上の失敗に出会う確率 = 2.14133%

成功回数         = 947 / 10000
平均施行回数     = 9.55649
31回以上連続失敗 = 44
41回以上連続失敗 = 21
51回以上連続失敗 = 11
61回以上連続失敗 = 4
71回以上連続失敗 = 0
最大連続失敗回数 = 70
連続41回以上の失敗に出会う確率 = 2.21753%

ひどいケースは無くはないけど、プレイしていると、これよりは頻繁に40回以上連続失敗に遭遇していると思う。
また、思ったよりも連続失敗する事が多いのだなと思った。
しょっちゅう連続失敗でマッカが無くなるので、連続失敗の上限を設けてほしいです。
ユーザは、この仕様にかなりやる気を削がれていると思います

2022/1/10 追記: よくみたら、0-10じゃなくて、0-9で計算しないとあきませんやん。こうやって考えると d2の烙印の実装、バグっぽくありません?

2021年12月25日土曜日

tp-link の無線LANルータに固定IPの機器を接続する忘備録

tp-link の無線LANルータにプリンタをLAN接続しようと思ったら、予想外の仕様で時間と取られたので、その忘備録。
ネットワークの知識がないと、かなり辛い。
プリンタに 192.168.1.253/255.255.255.0 のIPv4アドレスを設定し、無線LANルータとLAN接続したが、通信がうまくいかない。
grayhole:~ user$ ping 192.168.1.253
PING 192.168.1.253 (192.168.1.253): 56 data bytes
Request timeout for icmp_seq 0
Request timeout for icmp_seq 1
Request timeout for icmp_seq 2
^C
まず、下記画面のようにプリンタに設定したIPアドレスが、IPアドレス プールの範囲内に存在しないと、繋がらない変テコ仕様。
このままでは、無線LANルータのDHCPサーバにより、192.168.1.253が他の機器に割り当てられてしまう可能性があるので、上図のアドレス予約に192.168.1.253を追加しなければならない。
次のコマンド(arp -a)を実行します。
grayhole:~ user$ arp -a
...
? (192.168.1.1) at b0:a7:b9:62:9e:e0 on en0 ifscope [ethernet]
? (192.168.1.123) at 0:92:21:33:2d:91 on en0 ifscope [ethernet]
? (192.168.1.132) at a4:5e:32:2f:7f:20 on en0 ifscope [ethernet]
? (192.168.1.221) at 1a:32:ad:33:6e:33 on en0 ifscope permanent [ethernet]
? (192.168.1.253) at 8:0:37:cf:8e:26 on en0 ifscope [ethernet]
? (192.168.1.255) at ff:ff:ff:ff:ff:ff on en0 ifscope [ethernet]
...
broadcasthost (255.255.255.255) at ff:ff:ff:ff:ff:ff on en0 ifscope [ethernet]
gr
192.168.1.253 の行に着目し、8:0:37:cf:83:26 がMACアドレスというやつなので、2桁づつの数字 08:00:37:cf:83:26に置き換えて、アドレス予約に追加します。
以上

2021年12月15日水曜日

android darkmode の theme でハマった忘備録

layout.xml において、テキスト色や背景色は、darkmode のテーマに沿って変更されるので問題無いのですが、リストのアイテムが選択された時の挙動をカスタマイズしている所で、罠におちました。
こんなセレクター list_item_color.xml を用意して
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

  <item android:state_pressed="true"  android:drawable="@color/press" />
  
  <item android:state_checked="true"  android:drawable="@color/check" />

  <item android:state_selected="true" android:drawable="@color/white" />

  <item android:state_focused="true"  android:drawable="@color/red" />
  
  <item android:drawable="@color/black" />
 </selector>
リストアイテムとして foo_list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<com.example.Util.CheckableLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/list_item_color"
    android:orientation="horizontal" >

    <CheckBox
        android:id="@+id/pnt_check"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        android:paddingRight="5dp"
        android:text="@string/foo_name" />

    <TextView
        android:id="@+id/id_value"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_margin="5dp"
        android:layout_weight="1"
        android:gravity="center_vertical"
        android:textAppearance="?android:attr/textAppearanceMedium" />

    <ImageView
        android:id="@+id/detail_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_margin="5dp"
        android:focusable="false"
        android:src="@drawable/ic_action_right_dark" />

</com.example.Util.CheckableLinearLayout>
としていました。
ところが、リスト項目の背景に list_item_color.xml の item android:drawable の色 @color/black が使用され、
フォント色は何も指定していないけど、CheckableLinearLayout で制御されているためか、@color/black が使用されてしまいました。
これでは、黒背景に黒文字のため、画面が真っ黒で何も見えません。
リストを選択をすると、背景が違う色になるため、かろうじて黒文字が見える状態です。
list_item_color.xml は、テーマの影響を受けません。
よって、背景色は item android:drawable 固定です。よって正解は、
<?xml version="1.0" encoding="utf-8"?>
<com.example.Util.CheckableLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/list_item_color"
    android:orientation="horizontal" >

    <CheckBox
        android:id="@+id/pnt_check"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        android:paddingRight="5dp"
        android:buttonTint="@color/white"
        android:textColor="@color/white"
        android:text="@string/foo_name" />

    <TextView
        android:id="@+id/id_value"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_margin="5dp"
        android:layout_weight="1"
        android:gravity="center_vertical"
        android:textColor="@color/white"
        android:textAppearance="?android:attr/textAppearanceMedium" />

    <ImageView
        android:id="@+id/detail_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_margin="5dp"
        android:focusable="false"
        android:src="@drawable/ic_action_right" />
    <!--
        _theme を指定すべきではない。
        android:src="@drawable/ic_action_right_dark" />
-->

</com.example.Util.CheckableLinearLayout>
と、チェックボックスの色と、テキスト色を固定で指定する事でした。
また、@drawable/ic_action_right_dark は darkmode の色なので、_dark を除いた @drawable/ic_action_right にすべきでした。
ただし、ここでは背景色が黒固定なので @color/white を指定すべきかもしれません。

2021年12月6日月曜日

WixFirewallExtension 忘備録

Windows Install Wix で Firewall を追加しようとした時に、いろいろ躓いたので忘備録

コマンドラインからでも、以下のようにすればFirewallを制御できるので
netsh advfirewall firewall add rule name="test" dir="in" action="allow" program="c:\Proguram Files\Foo\x.exe" description="test description" enable="yes" profile="any"
カスタム・アクションを定義してやれば、要件はクリアしそうである。
<CustomAction Id="CA_01_BYPASSFIREWALL" ExeCommand='netsh advfirewall firewall add ...' Directory="Foo" Execute="deferred" Impersonate="no" Return="check" />
<CustomAction Id="CA_02_REMOVEFIREWALL" ExeCommand='netsh advfirewall firewall del ...' Directory="Foo" Execute="deferred" Impersonate="no" Return="check" />
<InstallExecuteSequence>
  <Custom Action="CA_02_REMOVEFIREWALL" Before="InstallFinalize">REMOVE="ALL"</Custom>
  <Custom Action="CA_01_BYPASSFIREWALL" Before="InstallFinalize">not (REMOVE="ALL")</Custom>
</InstallExecuteSequence>
上記は試してません

WixFirewallExtension を使用すると、ファイヤーウォールを制御できます。
プロジェクトのソリューション・エクスプローラのReferencesを右クリックして、「参照の追加」を選択し、Program Files\Wix Toolset v3.11\bin\WixFirewallExtension.dll を追加します。
Product.wxsに FirewallExtension の namespace を追加します。
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:fw="http://schemas.microsoft.com/wix/FirewallExtension">
x64 のコンポーネントだと FirewallExtension が x86 コンポーネントのため、怒られてしまいます。
そのため、x86 のプログラム・フォルダにFirewallのコンポーネントを追加します。
      <Directory Id="ProgramFilesFolder" Name="ProgramFiles86Folder">
        <!-- Firewall -->
        <Component Id="grpcFirewallException" Guid="2B6866D0-0F1F-4579-84A7-87CDE9F57640" KeyPath="yes">
          <fw:FirewallException Id="anyFirewallException" Program="[INSTALLFOLDER]x.exe" Description="BYPASS:x.exe network port" Name="Foo Firewall Exception" Scope="any" />
        </Component>
      </Directory>
      
      
      ...
      <Fragment>
        <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
          ...
          <ComponentRef Id="anyFirewallException"/>
        </ComponentGroup>
      </Fragment>
としてやればOKです。

2021年10月6日水曜日

go で grpc の proto ファイルから生成されたソースをローカル利用する 備忘録

ウィンドウズ環境にて grpc の protoc から go のソースを生成してビルドするまでに、えらいハマったので、その備忘録。
まず、grpc の go チュートリアルを読みましたが、唐突に source_relative という未定義の単語が出てきたり、キーワードが、どこの項目と連動しているのか理解できなくて、ちんぷんかんぷんでした。また、生成されたパッケージを利用する方法がわからず、エラー頻出で、体力消耗しました。普段からgoに親しんでいる人にとっては簡単な事なんでしょうけど、普段goを使わない自分には的確な情報にたどり着く手段が少なくて、正解にたどり着くまでに3日ほど浪費しました。
以下のディレクトリ構成で作業をしている前提で話を進めます。
C:/Projects/Hello/
         bin/                           ... インストールで追加される exe のディレクトリ
         pkg/                           ... 追加されたパッケージのディレクトリ
         proto/hello.proto              ... proto 定義
         my.hello/hello.proto           ... proto 定義(仕様変更により自動生成されるのと同じ場所に置く必要あり)
         my.hello/hello.pb.go           ... 自動生成される proto の go ソース
         my.hello/hello_grpc.pb.go   ... 自動生成される grpc の go ソース
         client.go                      ... クライアント・アプリケーションの go ソース
         go.mod                         ...  go mod init で生成される go.mod ファイル
         go.sum                         ...  go mod init で生成される go.sum ファイル
         goenv.bat           ... go を使うための環境設定バッチファイル

参考として goenv.bat の中身
@echo off
set GOROOT=D:\go
echo 'set GO install directory : %GOROOT%'

rem 
rem GOPATH は非推奨なので使用しません
rem 
rem set GOPATH=D:\somewhere
rem echo 'set Build directory : %GOPATH%'

rem
rem とりあえず、x86 をターゲットとします
rem 
echo 'set GOARCH win32 application'
set GOARCH=386

echo 'set GOARCH : %GOARCH%'

rem
rem プロキシーを利用している環境では、プロキシーを設定する必要があります
rem
rem set http_proxy=192.168.0.254:8080
rem set https_proxy=%http_proxy%

rem echo 'set proxy settings : %http_proxy%'

rem 
rem vcpkg により、protobuf をインストールしているため、そちらを流用しています
rem   d:\vcpkg\installed\x64-windows\tools\protobuf 
rem  が、それに該当します。この下に protoc.exe が配置されています。
rem 
set path=%PATH%;%GOROOT%\bin;d:\vcpkg\installed\x64-windows\tools\protobuf

さて、コマンド・プロンプトで、
C:/Projects/Hello ディレクトリ下に移動して作業を行います。
最初に go mod init コマンドを実行し、go.mod ファイルを生成します
C:\Projects\Hello> go mod init test-client
test-client.exe を作成する下地の準備ができました。
次に必要なgrpcのパッケージ類をインストールします
C:\Projects\Hello> go get -u -v google.golang.org/grpc
C:\Projects\Hello> go get -u -v google.golang.org/protobuf/proto
C:\Projects\Hello> go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
C:\Projects\Hello> go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

C:\Projects\Hello\bin 下に protoc-gen-go.exe, protoc-gen-go-grpc.exe が展開されているので
環境変数 PATH に上記 exe が存在するディレクトリを追加します。
C:\Projects\Hello\bin\windows_386 下に exe が展開されていた場合の PATH の追加例は下記
C:\Projects\Hello> set PATH=%PATH%;C:\Projects\Hello\bin\windows_386
パスの追加を怠ると、protoc コマンド実行時に原因不明のエラーに悩まされます。
*.proto の書き方が悪いのか、protoc-gen-go.exe が見つからない事によるエラーなのか
切り分けが容易ではなくなるので、必ず PATH を設定しましょう。

補足:現在は非推奨であるが、GOPATH=c:\gopub というように GOPATHを指定していた場合には
C:\gopub\bin 下に protoc-gen-go.exe, protoc-gen-go-grpc.exe が展開されているかもしれません。
グローバルにインストールした場合は、もしかしたら、%USERPROFILE%\go\bin ディレクトリ下に展開されているかもしれません。
GOPATHが書き込み禁止の Program Files ディレクトリだった場合も、%USERPROFILE%\go\bin に展開されると思われます。

C:\Projects\Hello\proto\hello.proto から
C:\Projects\Hello\ のディレクトリ下に hello_grpc.pb.go 等のソースを展開したいので
下記コマンドを実行します。

仕様が変更されました。
option go_package="[パッケージのパス];[パッケージ名]"
とした上で、proto ファイルは、パッケージのパスに置く事が必須となりました。
option go_package="my.hello;my_hello" と指定した上で
下記コマンドを実行します。
仕様が変更されていました。hello_grpc.pb.go は、hello.pb.go を含まなくなりました。
なので、2つ出力する必要があります。
C:\Projects\Hello> protoc --go-grpc_out=./ my.hello/hello.proto
C:\Projects\Hello> protoc --go_out=./ my.hello/hello.proto

参考として hello.proto の一部抜粋を記載します
syntax = "proto3";

option java_multiple_files = true;
option java_package = "my.hello";
option java_outer_classname = "Hello";

// 仕様が変更されたので設定に注意!
option go_package="my.hello;my_hello";

package hello;

service Hello {

  rpc SayHello (SendRequest) returns (SendResponse) {}

}

message SendRequest {
  string msg = 1;
}
  
message SendResponse {
  string msg = 1;
}
そうすると、
C:\Projects\Hello\my.hello というディレクトリ[パッケージのパス]下に
hello.pb.go ([protoの拡張子を抜いたファイル名].pb.go) というソースが生成されます。

更に仕様が変更されて、下記は解消されました。
仕様変更により下記は、どうなったか知りません
参考として、下記コマンドを実行すると hello.grpc.pb.go も生成されます

C:\Projects\Hello> protoc --go_out=plugins=grpc:./ --go-grpc_out=./ proto/hello.proto

しかしながら、hello.grpc.pb.go で定義されている内容は hello.pb.go に定義されているため
hello.grpc.pb.go を出力してしまうと、再定義のエラーが発生します。
従って、こちらは出力しません。
C++のソースを生成する場合は、対応する両方のファイルが必要だったので、言語間でインターフェイスの統一が取れていないと感じました。


出力されたパッケージをローカルパッケージとして利用するために下記コマンドを実行する必要があります。
C:\Projects\Hello> go mod edit --require my.hello@v0.0.0-local --replace my.hello@v0.0.0-local=./my.hello
コマンド中の v0.0.0 はバージョン情報です。公開して管理するわけではないので、version 0.0.0 の指定をしています。
コマンド中の -local は、ローカルパッケージだよというオマジナイです。
コマンド中で指定するパッケージ名は、あくまで protoファイル中で指定した option go_package="my.hello" を指定します。
go のソースに展開された時にパッケージ名が my_hello へ変更されていたとしても、元の名前を指定します。

このコマンドを実行すると、go.mod ファイルへ
   require () 節中に my.hello v0.0.0-local の行が追加され、
   replace () 節中に my.hello v0.0.0-local => ./my.hello の行が追加されます。
これにより、my.hello が外部モジュールから、ローカルの ./my.hello ディレクトリへとマッピング検索されるようになります。

仕様が変更されました
go ソース内での go_package は、proto ファイルで "my.hello" を指定していますが
"my_hello" という . を _ に置換した名前に変換される仕様となっています。
尚、proto ファイルで指定した package の名前は、go では利用されません。

これでようやく、client.go ファイルに
 import (
   myhello "my.hello"
 )
と書いて、インポートが可能になります。
名前が紛らわしいので、import 時にエイリアス指定("myhello")しておきましょう。パッケージ名に振り回されなくて済みます。
風が吹いたから、雨が降ったから、というような理由で、仕様がコロコロと変更されます。エイリアス指定しておくのが吉だと思います。
大事なことだから、もう一度書きます。
風が吹いたから、雨が降ったから、というような理由で、仕様がコロコロと変更されます。エイリアス指定しておくのが吉だと思います。

go build するのには、もう一手間が必要です。
C:\Projects\Hello> cd my.hello
C:\Projects\Hello\my.hello> go mod init my_hello
C:\Projects\Hello\my.hello> go mod tidy
C:\Projects\Hello\my.hello> cd ..
これで、protoc から生成された go パッケージが利用できるようになり、go build が可能になりました。

整理すると import myhello "my.hello" は、
   go.mod の require の my.hello v0.0.0-local で参照され、
   go.mod の replace 指定によりローカルの ./my.hello に置き換えられ
   ローカルディレクトリ ./my.hello のソース my_hello パッケージが読み込まれ、
   my_hello を myhello というエイリアスにより参照する事ができる、
という流れになります。

疲れた。ほんとに疲れた。

2021/12/07 追記:書いたとたんに仕様がどんどん変わっていく。疲れる。ほんとに疲れる。コンパイルする毎に仕様が変わっていてコンパイルできる状態にもっていくのに、時間を取られる。素晴らしいツールには違いないが、毎回コンパイルできる状態にもっていくのに時間的リソースの消費を余儀なくされるため、生産性の高いツールという位置づけから生産性の低いツールというレッテルを貼りたくなる。ツールを書いているやつは、「API design for C++」を読めや!!!!と、ぼやきたくなる。
Read "API design for C++"
please!
コマンド実行したら、android studio の gradle コマンド同様に、オプション変更の予告があります。そのうち、この内容も変更される事でしょう。
2021/12/08 追記: なんか、修正に次ぐ修正に次ぐ修正で、書いてる事の整合性が崩れてる…。もう疲れたので、お家帰りたい

2021年9月22日水曜日

enable_shared_from_this と継承 忘備録

マルチスレッド・プログラミングで、継承されたオブジェクトの破棄タイミングが難しく、enable_shared_from_this を使う事にした。 ただ、共通インタフェイスを持たせたくて継承させたので、素のままの enable_shared_from_this では不味そうなので、正しく動作させるための用法を忘備録として書き留める事にした。
#include <iostream>
#include <cassert>
#include <memory>

class Base : public std::enable_shared_from_this<Base> {
protected:

  template <typename T>
  std::shared_ptr<T> shared_from(T* derived) {
    assert(this == derived);
    return std::static_pointer_cast<T>(shared_from_this());
  }

  int n_;
public:
  Base(int n) : n_(n) { std::cout << "Base()" << std::endl; }
  virtual ~Base() { std::cout << "~Base(" << n_ << ")" << std::endl; }
};

class Delived : public Base {
public:

  auto shared_from_this() { return shared_from(this); }


  Delived(int m) : Base(m) {
    std::cout << "Delived()" << std::endl;
  }
  
  virtual ~Delived() {
    std::cout << "~Delived()" << std::endl;
  }

};


void main() {

  std::shared_ptr<Base> test1;
  {
  {
    auto pbase = std::make_shared<Base>(1);
    auto pderived = std::make_shared<Delived>(2);

    test1 = pbase->shared_from_this();
    std::shared_ptr<Delived> test2 = pderived->shared_from_this();
  }
  std::cout << "end" << std::endl;
  }

}
実行すると
Base()     // pbase のコンストラクタ
Base()          // pderived の Base コンストラクタ
Delived()       // pderived のコンストラクタ
~Delived()      // スコープを抜けて test2が破棄され pbaseの参照カウントが0になり破棄
~Base(2)
end             // スコープを抜ける
~Base(1)        // test1が破棄され pderivedの参照カウントが0になり破棄

2021年8月30日月曜日

cmake の /showIncludes が邪魔

cmake でコンパイルしてたら、
・<・ インクルード ファイル:      C:\Program Files (x86)\Windows Kits\10\include\10.0.19041.0\shared\driverspecs.h
いちいち何をインクルードしてるか表示されて、コンパイルの状況が全く読めない事態に陥った。
正直言って、邪魔

How to disable /showIncludes on cmake


cmake のディレクトリ
C:\Program Files\CMake\share\cmake-3.21\Modules\Platform
下に
Windows-MSVC.cmake というファイルがあり、
  ...

  # define generic information about compiler dependencies
  if (MSVC_VERSION GREATER 1300)
    #set(CMAKE_DEPFILE_FLAGS_${lang} "/showIncludes")
    set(CMAKE_DEPFILE_FLAGS_${lang} " ")
    set(CMAKE_${lang}_DEPFILE_FORMAT msvc)
  endif()
endmacro()

  ...
上記のように書き換えればOKです。

2021年7月12日月曜日

PsExec 備忘録

RemoteDesktop 接続じゃなく GUI アプリを動かせないか調べていたら、PsExec を使えばGUI アプリが動くみたいに書いてあるのを見つけて、本当かな?と思いながらも、試してみました。
その忘備録。

ローカルのコンピュータ上だと

  D:\usr\Psexec>psexec -i -u MY_ACTIVE_DIRECTORY_NAME\MY_ACCOUNT_NAME -p *********  \\MY_MACHINE_NAME notepad

  PsExec v2.34 - Execute processes remotely
  Copyright (C) 2001-2021 Mark Russinovich
  Sysinternals - www.sysinternals.com

  notepad exited with error code 0.

というように動作しました。

REMOTE_SERVER_MACHINE_NAME のコンピュータに対してだと
上記方法では動作しません。

UACが有効だと動作しないという事だったので
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" /v "LocalAccountTokenFilterPolicy" /t REG_DWORD /d 1

をリモートサーバ上で実行して UACをオフにし、ファイヤーウォールをオフにして試してみましたが、動作しません。

 認証の方法が厳密になったようで、psexec の引数でログイン認証はできないらしく、 cmdkey というもので資格情報を得ながらコマンド実行する事で、コマンドのプログラムは実行できました(コマンドプロンプトは管理者権限で開いて実行しています)。

 D:\usr\Psexec>cmdkey /add:REMOTE_SERVER_MACHINE_NAME /user:MY_ACTIVE_DIRECTORY_NAME\MY_ACCOUNT_NAME_admin /pass:*********

    CMDKEY: 資格情報を正しく追加しました。
    
 D:\usr\Psexec>psexec64 \\REMOTE_SERVER_MACHINE_NAME ipconfig

  PsExec v2.34 - Execute processes remotely
  Copyright (C) 2001-2021 Mark Russinovich
  Sysinternals - www.sysinternals.com

  Windows IP 構成

  イーサネット アダプター イーサネット 2:

   接続固有の DNS サフィックス . . . . .:
   IPv4 アドレス . . . . . . . . . . . .: 10.9.9.9
   サブネット マスク . . . . . . . . . .: 255.0.0.0
   デフォルト ゲートウェイ . . . . . . .: 10.10.0.1

  Tunnel adapter isatap.{CD764B44-40CC-4A3A-8B45-FF41F756EB20}:

   メディアの状態. . . . . . . . . . . .: メディアは接続されていません
   接続固有の DNS サフィックス . . . . .:
  ipconfig exited on REMOTE_SERVER_MACHINE_NAME with error code 0.


 

GUIアプリを動かすには、-i オプションが必要という事で同じく 実行しました。
しかしながら、実行ウィンドウが開かないまま終了しました。
  D:\usr\Psexec>psexec64 -i \\REMOTE_SERVER_MACHINE_NAME ipconfig

  PsExec v2.34 - Execute processes remotely
  Copyright (C) 2001-2021 Mark Russinovich
  Sysinternals - www.sysinternals.com

  ipconfig exited on REMOTE_SERVER_MACHINE_NAME with error code 0.

メモ帳を開いてみましたが

  D:\usr\Psexec>psexec64 -i \\REMOTE_SERVER_MACHINE_NAME notepad.exe

  PsExec v2.34 - Execute processes remotely
  Copyright (C) 2001-2021 Mark Russinovich
  Sysinternals - www.sysinternals.com


  となったまま永遠に制御が返ってくる気配がありません。
psexec は、ネット上でGUIアプリが動くと書かれていますが、
(過去には GUI が動作したのかもしれない)
CUI のためのコマンドで、GUIが使えるようなものではありませんでした。

2021年6月15日火曜日

OpenCV imagecodes が color palette をモノクロ扱いしてしまう備忘録

前にも、pullreq を出そうとしたのだが、テストケースも含めて提案しないと取り込んでもらえなさそうだったので、ここに記す。

バージョンは opencv 4.5.2

imagecodes の grfmt_tiff.cpp
    if (tif)
    {
        uint32 wdth = 0, hght = 0;
        uint16 photometric = 0;

        CV_TIFF_CHECK_CALL(TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &wdth));
        CV_TIFF_CHECK_CALL(TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &hght));
        CV_TIFF_CHECK_CALL(TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &photometric));

        {
            bool isGrayScale = photometric == PHOTOMETRIC_MINISWHITE || photometric == PHOTOMETRIC_MINISBLACK;
            uint16 bpp = 8, ncn = isGrayScale ? 1 : 3;
            if (0 == TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &bpp))
            {
                // TIFF bi-level images don't require TIFFTAG_BITSPERSAMPLE tag
                bpp = 1;
            }
//char buf[256];
//sprintf(buf, "%d, %d, %d\n", ncn, isGrayScale, bpp);
//OutputDebugStringA(buf);
// カラーパレットの場合 ncn = 3, isGrayScale = false, bpp = 8
            CV_TIFF_CHECK_CALL_DEBUG(TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &ncn));

//sprintf(buf, "%d, %d\n", ncn, photometric);
//OutputDebugStringA(buf);
// カラーパレットの場合 ncn = 1, photometric = PHOTOMETRIC_PALETTE
            m_width = wdth;
            m_height = hght;
            if (ncn == 3 && photometric == PHOTOMETRIC_LOGLUV)
            {
                m_type = CV_32FC3;
                m_hdr = true;
                return true;
            }
            m_hdr = false;

            if( bpp > 8 &&
               ((photometric > 2) ||
                (ncn != 1 && ncn != 3 && ncn != 4)))
                bpp = 8;

            int wanted_channels = normalizeChannelsNumber(ncn);

            // カラーパレットの画像がモノクロ扱いされないよう修正
            // ここから --->
            if( !isGrayScale && ncn == 1 && bpp == 8) {
              wanted_channels = 3;
            }
            // <--- ここまで
            switch(bpp)
            {
                case 1:
                    m_type = CV_MAKETYPE(CV_8U, !isGrayScale ? wanted_channels : 1);
                    result = true;
                    break;
                case 8:
                    m_type = CV_MAKETYPE(CV_8U, !isGrayScale ? wanted_channels : 1);
                    result = true;
                    break;
                case 16:
                    m_type = CV_MAKETYPE(CV_16U, !isGrayScale ? wanted_channels : 1);
                    result = true;
                    break;
                case 32:
                    m_type = CV_MAKETYPE(CV_32F, wanted_channels);
                    result = true;
                    break;
                case 64:
                    m_type = CV_MAKETYPE(CV_64F, wanted_channels);
                    result = true;
                    break;
            default:
                CV_Error(cv::Error::StsError, "Invalid bitsperpixel value read from TIFF header! Must be 1, 8, 16, 32 or 64.");
            }
        }
    }
とすることで、ちゃんとカラーで読み込まれる。

2021年6月3日木曜日

OpenCV アフィン変換の合成

OpenCV を使って、アフィン変換の合成をしようとしたら、うまく動作しなくてハマったので忘備録。

OpenCV で画像の回転をかけるアフィン変換を求める関数として、

  cv::getRotationMatrix2D という関数があります。

これが曲者、返されるのは 3 x 2 の行列で、アフィン変換の合成に使えない。
また、cv::warpAffine というのもあって、こいつも 3 x 2 の行列を受け取るアフィン変換の簡易バージョンで使えない

w1 x h1 の中心に alpha 回転し、横方向だけ n倍し、w2 x h2 のサイズに中心を移動したかったが、四苦八苦しました。

  cv::Mat ms = (cv::Mat_<double>(3,3) 
      << 1.0, 0.0, - w1 / 2.0,
         0.0, 1.0, - h1 / 2.0,
         0.0, 0.0, 1.0);
  cv::Mat rot = (cv::Mat_<double>(3,3)
      <<   cos(alpha), sin(alpha), 0.0,
         -sin(alpha), cos(alpha), 0.0,
         0.0,          0.0,         1.0);
  cv::Mat rat = (cv::Mat_<double>(3, 3)
      <<  n,  0.0, 0.0,
         0.0, 1.0, 0.0,
         0.0, 0.0, 1.0);
  cv::Mat mms = (cv::Mat_<double>(3,3)
      << 1.0, 0.0, w2 / 2.0,
      0.0, 1.0, h2 / 2.0,
      0.0, 0.0, 1.0);
  cv::Mat par = mms * rat * rot * ms;
  // src は (w1,h1)の入力画像
  // dst は (w2,h2)の出力画像
  cv::warpPerspective(src, dst, par, cv::Size(w2,h2), cv::INTER_LINEAR, cv::BORDER_TRANSPARENT);
とする事で、うまく合成変換できました。 #combine affine transform opencv

2021年4月26日月曜日

アプリケーションを正しく起動できませんでした(0xc000007b) Visual Studio 編

アプリケーションをビルドして実行しようとしたら、突然「アプリケーションを正しく起動できませんでした(0xc000007b)」と言われて、アプリケーションが実行できなくなりました。

バージョン管理システムで、プロジェクトを別ディレクトリに展開したばかりで、どこかプロジェクトの設定に問題がありそうですが、なかなか原因がわかりませんでした。


原因は、リソースのRT_MANIFEST/IDR_MANIFESTにありました。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> 
<assemblyIdentity 
    version="1.0.0.0" 
    processorArchitecture="*" 
    name="Microsoft.Windows.GisMaintenance"
    type="win32" 
/> 
<description>アプリケーションの説明をここに挿入します。</description> 
<dependency> 
    <dependentAssembly> 
        <assemblyIdentity 
            type="win32" 
            name="Microsoft.Windows.Common-Controls" 
            version="6.0.0.0" 
            processorArchitecture="x86" 
            publicKeyToken="1234a56789bcdeff" 
            language="*" 
        /> 
    </dependentAssembly> 
</dependency> 
</assembly>

processorArchitecture="x86" とありますが、プロセッサ変わる事があるので
processorArchitecture="*" に変更します

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> 
<assemblyIdentity 
    version="1.0.0.0" 
    processorArchitecture="*" 
    name="Microsoft.Windows.GisMaintenance"
    type="win32" 
/> 
<description>アプリケーションの説明をここに挿入します。</description> 
<dependency> 
    <dependentAssembly> 
        <assemblyIdentity 
            type="win32" 
            name="Microsoft.Windows.Common-Controls" 
            version="6.0.0.0" 
            processorArchitecture="*" 
            publicKeyToken="1234a56789bcdeff" 
            language="*" 
        /> 
    </dependentAssembly> 
</dependency> 
</assembly>

古いプロジェクトだと、x86 が入っていたりするので、注意が必要です。書いとかないと、同じ事が起こった時にまた調べる事になりそうで・・・

2021年4月20日火曜日

OpenCV warpPerspective を魔改造


ラスター変換で、図面の画像を縮小変換した場合に、Cubic や Lanczos4 といった周りの画素値から平均を求める手法だと、線が薄くなって消えてしまいます。縮小変換しても線を消さないようにするために、周りの画素値の最小値を取ってやれば良いと思い魔改造する事に・・・。

魔改造を施したのは OpenCV 4.5.2 のバージョンです。

/modules/imgproc/src/imgwarp.cpp
を改変していきます。尚、OpenCL や OpenVX など、コンパイル条件で多様なルーチンを選択できますが、最小限のコードのみを対象としました。
warpPerspectiveでは cv::INTER_AREA というフラグを指定しても cv::INTER_LINEAR というフラグに変換されて処理されていたので、cv::INTER_AREA が指定された場合に、線画用の縮小処理が実行されるという方針で実装しました

手っ取り早くBilinear のコードをベースに改造します。
係数配列を追加します。
static float AreasupTab_f[INTER_TAB_SIZE2][4][4];
static short AreasupTab_i[INTER_TAB_SIZE2][4][4];

初期化関数を追加します。不要なんですが、体裁だけ整えます。
static inline void interpolateAreasup( float x, float* coeffs )
{
    coeffs[0] = coeffs[1] = coeffs[2] = coeffs[3] = 0;
}

cv::INTER_AREAが指定された時に初期化されるコードを追加します
static const void* initInterTab2D( int method, bool fixpt )
{
    static bool inittab[INTER_MAX+1] = {false};
    float* tab = 0;
    short* itab = 0;
    int ksize = 0;
    if( method == INTER_LINEAR )
        tab = BilinearTab_f[0][0], itab = BilinearTab_i[0][0], ksize=2;
    else if( method == INTER_CUBIC )
        tab = BicubicTab_f[0][0], itab = BicubicTab_i[0][0], ksize=4;
    else if( method == INTER_LANCZOS4 )
        tab = Lanczos4Tab_f[0][0], itab = Lanczos4Tab_i[0][0], ksize=8;
        // --> ここから
    else if( method == INTER_AREA )
        tab = AreasupTab_f[0][0], itab = AreasupTab_i[0][0], ksize=4;
        // --> ここまで追加
    else
        CV_Error( CV_StsBadArg, "Unknown/unsupported interpolation type" );
    //...

係数配列を全部初期化する関数にもcv::INTER_AREA の初期化を追加します
static bool initAllInterTab2D()
{
    return  initInterTab2D( INTER_LINEAR, false ) &&
            initInterTab2D( INTER_LINEAR, true ) &&
            initInterTab2D( INTER_CUBIC, false ) &&
            initInterTab2D( INTER_CUBIC, true ) &&
            initInterTab2D( INTER_LANCZOS4, false ) &&
            initInterTab2D( INTER_LANCZOS4, true ) &&
            initInterTab2D( INTER_AREA, false ) && // この辺
            initInterTab2D( INTER_AREA, true );
}

処理の核心部を追加します
template<class CastOp, typename AT, int ONE>
static void remapAreasup( const Mat& _src, Mat& _dst, const Mat& _xy,
                          const Mat& _fxy, const void* _wtab,
                          int borderType, const Scalar& _borderValue )
{
    typedef typename CastOp::rtype T;
    typedef typename CastOp::type1 WT;
    Size ssize = _src.size(), dsize = _dst.size();
    const int cn = _src.channels();
    const AT* wtab = (const AT*)_wtab;
    const T* S0 = _src.ptr<T>();
    size_t sstep = _src.step/sizeof(S0[0]);
    T cval[CV_CN_MAX];
    CastOp castOp;

    for(int k = 0; k < cn; k++ )
        cval[k] = saturate_cast<T>(_borderValue[k & 3]);

    int borderType1 = borderType != BORDER_TRANSPARENT ? borderType : BORDER_REFLECT_101;

    unsigned width1 = std::max(ssize.width-3, 0), height1 = std::max(ssize.height-3, 0);

    if( _dst.isContinuous() && _xy.isContinuous() && _fxy.isContinuous() )
    {
        dsize.width *= dsize.height;
        dsize.height = 1;
    }

    for(int dy = 0; dy < dsize.height; dy++ )
    {
        T* D = _dst.ptr<T>(dy);
        const short* XY = _xy.ptr<short>(dy);
        const ushort* FXY = _fxy.ptr<ushort>(dy);

        for(int dx = 0; dx < dsize.width; dx++, D += cn )
        {
            int sx = XY[dx*2]-1, sy = XY[dx*2+1]-1;
            //const AT* w = wtab + FXY[dx]*16;
            if( (unsigned)sx < width1 && (unsigned)sy < height1 )
            {
                const T* S = S0 + sy*sstep + sx*cn;
                for(int k = 0; k < cn; k++ )
                {
                    WT sum = std::min<WT>(std::min<WT>(S[0],S[cn]), std::min<WT>(S[cn*2], S[cn*3]));
                    S += sstep;
                    sum = std::min<WT>(sum, std::min<WT>(std::min(S[0],S[cn]), std::min<WT>(S[cn*2], S[cn*3])));
                    S += sstep;
                    sum = std::min<WT>(sum, std::min<WT>(std::min(S[0],S[cn]), std::min<WT>(S[cn*2], S[cn*3])));
                    S += sstep;
                    sum = std::min<WT>(sum, std::min<WT>(std::min(S[0],S[cn]), std::min<WT>(S[cn*2], S[cn*3])));
                    S += 1 - sstep*3;
                    D[k] = static_cast<WT>(sum);
                }
            }
            else
            {
                int x[4], y[4];
                if( borderType == BORDER_TRANSPARENT &&
                    ((unsigned)(sx+1) >= (unsigned)ssize.width ||
                    (unsigned)(sy+1) >= (unsigned)ssize.height) ) {
                      continue;
                }

                if( borderType1 == BORDER_CONSTANT &&
                    (sx >= ssize.width || sx+4 <= 0 ||
                    sy >= ssize.height || sy+4 <= 0))
                {
                    for(int k = 0; k < cn; k++ )
                        D[k] = cval[k];
                    continue;
                }

                for(int i = 0; i < 4; i++ )
                {
                    x[i] = borderInterpolate(sx + i, ssize.width, borderType1)*cn;
                    y[i] = borderInterpolate(sy + i, ssize.height, borderType1);
                }

                for(int k = 0; k < cn; k++, S0++ )
                {
                  WT cv = cval[k], sum = std::numeric_limits<WT>::max();
                    for(int i = 0; i < 4; i++ )
                    {
                        int yi = y[i];
                        const T* S = S0 + yi*sstep;
                        if( yi < 0 )
                            continue;
                        if( x[0] >= 0 )
                            sum = std::min<WT>(sum, S[x[0]]);
                        if( x[1] >= 0 )
                            sum = std::min<WT>(sum, S[x[1]]);
                        if( x[2] >= 0 )
                            sum = std::min<WT>(sum, S[x[2]]);
                        if( x[3] >= 0 )
                            sum = std::min<WT>(sum, S[x[3]]);
                    }
                    D[k] = static_cast<WT>(sum);
                }
                S0 -= cn;
            }
        }
    }
}

remap に cv::INTER_AREA 時の処理を追加します。IPPとかは無視です。
void cv::remap( InputArray _src, OutputArray _dst,
                InputArray _map1, InputArray _map2,
                int interpolation, int borderType, const Scalar& borderValue )
{
    CV_INSTRUMENT_REGION();

    static RemapNNFunc nn_tab[] =
    {
        remapNearest<uchar>, remapNearest<schar>, remapNearest<ushort>, remapNearest<short>,
        remapNearest<int>, remapNearest<float>, remapNearest<double>, 0
    };

    static RemapFunc linear_tab[] =
    {
        remapBilinear<FixedPtCast<int, uchar, INTER_REMAP_COEF_BITS>, RemapVec_8u, short>, 0,
        remapBilinear<Cast<float, ushort>, RemapNoVec, float>,
        remapBilinear<Cast<float, short>, RemapNoVec, float>, 0,
        remapBilinear<Cast<float, float>, RemapNoVec, float>,
        remapBilinear<Cast<double, double>, RemapNoVec, float>, 0
    };

    static RemapFunc cubic_tab[] =
    {
        remapBicubic<FixedPtCast<int, uchar, INTER_REMAP_COEF_BITS>, short, INTER_REMAP_COEF_SCALE>, 0,
        remapBicubic<Cast<float, ushort>, float, 1>,
        remapBicubic<Cast<float, short>, float, 1>, 0,
        remapBicubic<Cast<float, float>, float, 1>,
        remapBicubic<Cast<double, double>, float, 1>, 0
    };

    static RemapFunc lanczos4_tab[] =
    {
        remapLanczos4<FixedPtCast<int, uchar, INTER_REMAP_COEF_BITS>, short, INTER_REMAP_COEF_SCALE>, 0,
        remapLanczos4<Cast<float, ushort>, float, 1>,
        remapLanczos4<Cast<float, short>, float, 1>, 0,
        remapLanczos4<Cast<float, float>, float, 1>,
        remapLanczos4<Cast<double, double>, float, 1>, 0
    };

    // --> ここから
    static RemapFunc areasup_tab[] = 
    {
        remapAreasup<FixedPtCast<int, uchar, INTER_REMAP_COEF_BITS>, short, INTER_REMAP_COEF_SCALE>, 0,
        remapAreasup<Cast<float, ushort>, float, 1>,
        remapAreasup<Cast<float, short>, float, 1>, 0,
        remapAreasup<Cast<float, float>, float, 1>,
        remapAreasup<Cast<double, double>, float, 1>, 0
    };
    // --> ここまで追加
  
    CV_Assert( !_map1.empty() );
    CV_Assert( _map2.empty() || (_map2.size() == _map1.size()));

    CV_OCL_RUN(_src.dims() <= 2 && _dst.isUMat(),
               ocl_remap(_src, _dst, _map1, _map2, interpolation, borderType, borderValue))

    Mat src = _src.getMat(), map1 = _map1.getMat(), map2 = _map2.getMat();
    _dst.create( map1.size(), src.type() );
    Mat dst = _dst.getMat();


    CV_OVX_RUN(
        src.type() == CV_8UC1 && dst.type() == CV_8UC1 &&
        !ovx::skipSmallImages<VX_KERNEL_REMAP>(src.cols, src.rows) &&
        (borderType& ~BORDER_ISOLATED) == BORDER_CONSTANT &&
        ((map1.type() == CV_32FC2 && map2.empty() && map1.size == dst.size) ||
         (map1.type() == CV_32FC1 && map2.type() == CV_32FC1 && map1.size == dst.size && map2.size == dst.size) ||
         (map1.empty() && map2.type() == CV_32FC2 && map2.size == dst.size)) &&
        ((borderType & BORDER_ISOLATED) != 0 || !src.isSubmatrix()),
        openvx_remap(src, dst, map1, map2, interpolation, borderValue));

    CV_Assert( dst.cols < SHRT_MAX && dst.rows < SHRT_MAX && src.cols < SHRT_MAX && src.rows < SHRT_MAX );

    if( dst.data == src.data )
        src = src.clone();

    // --> 使われていない INTER_AREA を利用するので、以下コメントアウトして有効化
    //if( interpolation == INTER_AREA )
    //    interpolation = INTER_LINEAR;

    int type = src.type(), depth = CV_MAT_DEPTH(type);

#if defined HAVE_IPP && !IPP_DISABLE_REMAP
    CV_IPP_CHECK()
    {
        if ((interpolation == INTER_LINEAR || interpolation == INTER_CUBIC || interpolation == INTER_NEAREST) &&
                map1.type() == CV_32FC1 && map2.type() == CV_32FC1 &&
                (borderType == BORDER_CONSTANT || borderType == BORDER_TRANSPARENT))
        {
            int ippInterpolation =
                interpolation == INTER_NEAREST ? IPPI_INTER_NN :
                interpolation == INTER_LINEAR ? IPPI_INTER_LINEAR : IPPI_INTER_CUBIC;

            ippiRemap ippFunc =
                type == CV_8UC1 ? (ippiRemap)ippiRemap_8u_C1R :
                type == CV_8UC3 ? (ippiRemap)ippiRemap_8u_C3R :
                type == CV_8UC4 ? (ippiRemap)ippiRemap_8u_C4R :
                type == CV_16UC1 ? (ippiRemap)ippiRemap_16u_C1R :
                type == CV_16UC3 ? (ippiRemap)ippiRemap_16u_C3R :
                type == CV_16UC4 ? (ippiRemap)ippiRemap_16u_C4R :
                type == CV_32FC1 ? (ippiRemap)ippiRemap_32f_C1R :
                type == CV_32FC3 ? (ippiRemap)ippiRemap_32f_C3R :
                type == CV_32FC4 ? (ippiRemap)ippiRemap_32f_C4R : 0;

            if (ippFunc)
            {
                bool ok;
                IPPRemapInvoker invoker(src, dst, map1, map2, ippFunc, ippInterpolation,
                                        borderType, borderValue, &ok);
                Range range(0, dst.rows);
                parallel_for_(range, invoker, dst.total() / (double)(1 << 16));

                if (ok)
                {
                    CV_IMPL_ADD(CV_IMPL_IPP|CV_IMPL_MT);
                    return;
                }
                setIppErrorStatus();
            }
        }
    }
#endif

    RemapNNFunc nnfunc = 0;
    RemapFunc ifunc = 0;
    const void* ctab = 0;
    bool fixpt = depth == CV_8U;
    bool planar_input = false;

    if( interpolation == INTER_NEAREST )
    {
        nnfunc = nn_tab[depth];
        CV_Assert( nnfunc != 0 );
    }
    else
    {
        if( interpolation == INTER_LINEAR )
            ifunc = linear_tab[depth];
        else if( interpolation == INTER_CUBIC ){
            ifunc = cubic_tab[depth];
            CV_Assert( _src.channels() <= 4 );
        }
        else if( interpolation == INTER_LANCZOS4 ){
            ifunc = lanczos4_tab[depth];
            CV_Assert( _src.channels() <= 4 );
        }
        // --> ここから
        else if( interpolation == INTER_AREA ) {
            ifunc = areasup_tab[depth];
            CV_Assert( _src.channels() <= 4 );
        }
        // --> ここまで追加
        else
            CV_Error( CV_StsBadArg, "Unknown interpolation method" );
        CV_Assert( ifunc != 0 );
        ctab = initInterTab2D( interpolation, fixpt );
    }

    const Mat *m1 = &map1, *m2 = &map2;

    if( (map1.type() == CV_16SC2 && (map2.type() == CV_16UC1 || map2.type() == CV_16SC1 || map2.empty())) ||
        (map2.type() == CV_16SC2 && (map1.type() == CV_16UC1 || map1.type() == CV_16SC1 || map1.empty())) )
    {
        if( map1.type() != CV_16SC2 )
            std::swap(m1, m2);
    }
    else
    {
        CV_Assert( ((map1.type() == CV_32FC2 || map1.type() == CV_16SC2) && map2.empty()) ||
            (map1.type() == CV_32FC1 && map2.type() == CV_32FC1) );
        planar_input = map1.channels() == 1;
    }

    RemapInvoker invoker(src, dst, m1, m2,
                         borderType, borderValue, planar_input, nnfunc, ifunc,
                         ctab);
    parallel_for_(Range(0, dst.rows), invoker, dst.total()/(double)(1<<16));
}

成果
 変換元画像

 cv::INTER_LINEAR で変換

 魔改造 cv::INTER_AREA で変換

2021年2月17日水曜日

OpenCV copy between cv::Mat and Windows BITMAP 忘備録

opencv のコードを一部修正したくて、vcpkg の opencv を update したら、バージョン 3.4.3 から 4.3.0 にアップデートされた。
既存コードをコンパイルしてみると、IplImage が定義されていないと怒られてしまう。どうやら、IplImage が deprecated になったようだ。

どこで IplImage を使っていたかと言うと、BITMAPハンドルとcv::Matの相互変換部分。
cv::Mat BitmapToMat(HANDLE hBitmap) {
  cv::Mat result;
  {
    BITMAP bm;
    GetObject(hBitmap, sizeof(BITMAP), &bm );
    result = cv::Mat( bm.bmHeight, bm.bmWidth, CV_8UC4, cv::Scalar(255,255,255,255) );
    IplImage img = result;
    memcpy( img.imageData, bm.bmBits, bm.bmWidthBytes * bm.bmHeight );
  }
  return result;
}

HBITMAP MatToBitmap(const cv::Mat& src, HDC hMemDC) {
  HBITMAP hBitmap = NULL;
  if( !src.empty() && src.cols > 0 && src.rows > 0 ) {
    BITMAPINFO bi;
    bi.bmiHeader.biSize        = sizeof(BITMAPINFOHEADER);
    bi.bmiHeader.biWidth       = src.cols;
    bi.bmiHeader.biHeight      = src.rows;
    bi.bmiHeader.biPlanes      = 1;
    bi.bmiHeader.biBitCount    = 32;
    bi.bmiHeader.biCompression = BI_RGB;
    {
      LPDWORD lpPixels;
      hBitmap = CreateDIBSection( hMemDC, &bi, DIB_RGB_COLORS, reinterpret_cast<void**>(&lpPixels), NULL, 0 );
      GdiFlush();
    }
    BITMAP bm;
    GetObject(hBitmap, sizeof(BITMAP), &bm);
    cv::Mat rev;
    cv::flip(src, rev, 0);
    IplImage img = rev;
    memcpy( bm.bmBits, img.imageData, src.cols * src.rows * 4 );
  }
  return hBitmap;
}
どう手直ししたら良いのかわからなくて、ググってみたが情報が少ない。
これは需要があるかもしれん?と思って、ここに記すことにした
cv::Mat BitmapToMat(HANDLE hBitmap) {
  cv::Mat result;
  {
    BITMAP bm;
    GetObject(hBitmap, sizeof(BITMAP), &bm );
    result = cv::Mat( bm.bmHeight, bm.bmWidth, CV_8UC4, (void*)bm.bmBits );
  }
  return result;
}

HBITMAP MatToBitmap(const cv::Mat& src, HDC hMemDC) {
  HBITMAP hBitmap = NULL;
  if( !src.empty() && src.cols > 0 && src.rows > 0 ) {
    BITMAPINFO bi;
    bi.bmiHeader.biSize        = sizeof(BITMAPINFOHEADER);
    bi.bmiHeader.biWidth       = src.cols;
    bi.bmiHeader.biHeight      = src.rows;
    bi.bmiHeader.biPlanes      = 1;
    bi.bmiHeader.biBitCount    = 32;
    bi.bmiHeader.biCompression = BI_RGB;
    {
      LPDWORD lpPixels;
      hBitmap = CreateDIBSection( hMemDC, &bi, DIB_RGB_COLORS, reinterpret_cast<void**>(&lpPixels), NULL, 0 );
      GdiFlush();
    }
    BITMAP bm;
    GetObject(hBitmap, sizeof(BITMAP), &bm);
    cv::Mat rev;
    cv::flip(src, rev, 0);
    memcpy( bm.bmBits, rev.ptr(), src.cols * src.rows * 4 );
  }
  return hBitmap;
}
ポインタをダイレクトに扱っているので、適正に使用しないと Abnormal Termination を引き起こすでしょう。

2021年2月3日水曜日

Proj4 C++ 座標変換忘備録

Proj4 ver 6.3.1 で座標変換するコードの習作
#include <proj.h>
#include <iostream>

int main() {

  PJ_CONTEXT* C;
  PJ*         P;
  PJ*         P_for_GIS;

  C = proj_context_create();
  P = proj_create_crs_to_crs( C, "EPSG:3857", "EPSG:6679", NULL );
  if( !P ) {
    std::cerr << "Fail to create proj crs conversion" << std::endl;
    return 1;
  }
  P_for_GIS = proj_normalize_for_visualization(C,P);
  if( !P_for_GIS ) {
    std::cerr << "Fail to normalize proj" << std::endl;
    return 1;
  }
  proj_destroy(P);
  P = P_for_GIS;
  
  PJ_COORD sp = proj_coord(15734773.4,5323579.9,0,0);
  
  PJ_COORD dp = proj_trans(P, PJ_FWD, sp);
  std::cout << std::fixed << sp.v[0] << "," << sp.v[1] << std::endl;
  std::cout << " to ";
  std::cout << dp.v[0] << "," << dp.v[1] << std::endl;
  
  proj_destroy(P);
  proj_context_destroy(C);
  
  return 0;
}
参考: https://github.com/OSGeo/PROJ/blob/master/examples/pj_obs_api_mini_demo.c