Contenu connexe Similaire à PBL1-v1-002j.pptx (20) PBL1-v1-002j.pptx1. CPU GPU
Ultimate CGRA w/ high-speed compiler
CGRA for Energy-efficient Cryptography
Beyond-Neuromorphic Systems
Non-Deterministic Computing
1
ナレータ VOICEVOX:もち子(cv 明日葉よもぎ)
はらぺこエンジニアに贈るCGRAの世界2022
(2. 画像フィルタ初級編)
スパコンからIoTまで 省エネ社会に
AI+BCだけじゃない超効率計算手法
2. /* SCREEN=WD*HT */
for (row=0; row<HT; row++) {
for (col=0; col<WD; col++) {
pix = in[row*WD+col];
r = t[ pix>>24 ];
g = t[256+((pix>>16)&255)];
b = t[512+((pix>> 8)&255)];
out[row*WD+col]=r<<24 | g<<16 | b<<8;
} }
20220202
2
簡単な tone_curveをC言語で書く
Load →
Store ←
Color map tables
3. /* SCREEN=WD*HT */
for (row=0; row<HT; row++) {
//EMAX5A begin tone_curve mapdist=0
for (LOOP0=WD, col=-4; LOOP0--;) {
col += 4;
pix = in[row*WD+col/4];
r = t[ pix>>24 ];
g = t[256+((pix>>16)&255)];
b = t[512+((pix>> 8)&255)];
out[row*WD+col/4]=r<<24 | g<<16 | b<<8;
}
//EMAX5A end
}
//EDMAX5A drain_dirty_lmm
20220202
3
IMAXのループ構造記述に合わせる
Load →
Store ←
Color map tables
/* SCREEN=WD*HT */
for (row=0; row<HT; row++) {
for (col=0; col<WD; col++) {
pix = in[row*WD+col];
r = t[ pix>>24 ];
g = t[256+((pix>>16)&255)];
b = t[512+((pix>> 8)&255)];
out[row*WD+col]=r<<24 | g<<16 | b<<8;
} }
Load →
Store ←
Color map tables
4. /* SCREEN=WD*HT */
for (row=0; row<HT; row++) {
//EMAX5A begin tone_curve mapdist=0
for (LOOP0=WD, col=-4; LOOP0--;) {
col += 4;
mop(OP_LDWR, &pix, in_row_WD, col, MSK_W0, in, WD); //pix = in[row*WD+col/4];
mop(OP_LDUBR, &r, t_r, pix, MSK_B3, t, 256*3/4); //r = t[ pix>>24 ];
mop(OP_LDUBR, &g, t_g, pix, MSK_B2, t, 256*3/4); //g = t[256+((pix>>16)&255)];
mop(OP_LDUBR, &b, t_b, pix, MSK_B1, t, 256*3/4); //b = t[512+((pix>> 8)&255)];
out[row*WD+col/4]=r<<24|g<<16|b<<8;
}
//EMAX5A end
}
//EDMAX5A drain_dirty_lmm
20220202
4
IMAXの高機能関数記述に書き換えながらデバッグする
Load →
Store ←
Color map tables
5. /* SCREEN=WD*HT */
for (row=0; row<HT; row++) {
//EMAX5A begin tone_curve mapdist=0
for (LOOP0=WD, col=-4; LOOP0--;) {
exe(OP_ADD, &col, col, EXP_H3210, 4, EXP_H3210, 0, EXP_H3210); //col += 4;
mop(OP_LDWR, &pix, in_row_WD, col, MSK_W0, in, WD); //pix = in[row*WD+col/4];
mop(OP_LDUBR, &r, t_r, pix, MSK_B3, t, 256*3/4); //r = t[ pix>>24 ];
mop(OP_LDUBR, &g, t_g, pix, MSK_B2, t, 256*3/4); //g = t[256+((pix>>16)&255)];
mop(OP_LDUBR, &b, t_b, pix, MSK_B1, t, 256*3/4); //b = t[512+((pix>> 8)&255)];
exe(OP_MMRG, &out, r, EXP_H3210, g, EXP_H3210, b, EXP_H3210);
mop(OP_STWR, &out, out_row_WD, col, MSK_W0); //out[row*WD+col/4]=r<<24|g<<16|b<<8;
}
//EMAX5A end
}
//EDMAX5A drain_dirty_lmm
20220202
5
全部書き換えたら逐次実行プログラムとしてデバッグ
Load →
Store ←
Color map tables
6. 20220202
6
データの配置と流れの観点から見直す
Load →
Store ←
Color map tables
/* SCREEN=WD*HT */
for (row=0; row<HT; row++) {
//EMAX5A begin tone_curve mapdist=0
for (LOOP0=WD, col=-4; LOOP0--;) {
exe(OP_ADD, &col, col, EXP_H3210, 4, EXP_H3210, 0, EXP_H3210); //col += 4;
mop(OP_LDWR, &pix, in_row_WD, col, MSK_W0, in, WD); //pix = in[row*WD+col/4];
mop(OP_LDUBR, &r, t_r, pix, MSK_B3, t, 256*3/4); //r = t[ pix>>24 ];
mop(OP_LDUBR, &g, t_g, pix, MSK_B2, t, 256*3/4); //g = t[256+((pix>>16)&255)];
mop(OP_LDUBR, &b, t_b, pix, MSK_B1, t, 256*3/4); //b = t[512+((pix>> 8)&255)];
exe(OP_MMRG, &out, r, EXP_H3210, g, EXP_H3210, b, EXP_H3210);
mop(OP_STWR, &out, out_row_WD, col, MSK_W0); //out[row*WD+col/4]=r<<24|g<<16|b<<8;
}
//EMAX5A end
}
//EDMAX5A drain_dirty_lmm
8. int loop=WD/2;
//EMAX5A begin tone_curve mapdist=0
while (loop--) {
mop(OP_LDR, &BR[0][1][1], in++, 0LL, MSK_D0, in, WD, 0, 0, NULL, 0);
mop(OP_LDBR, &BR[1][1][1], t1, BR[0][1][1], MSK_B3, t, 256*3/4, 0, 0, NULL, 0);
mop(OP_LDBR, &BR[1][1][0], t1, BR[0][1][1], MSK_B7, t, 256*3/4, 0, 0, NULL, 0);
mop(OP_LDBR, &BR[1][2][1], t2, BR[0][1][1], MSK_B2, t, 256*3/4, 0, 0, NULL, 0);
mop(OP_LDBR, &BR[1][2][0], t2, BR[0][1][1], MSK_B6, t, 256*3/4, 0, 0, NULL, 0);
mop(OP_LDBR, &BR[1][3][1], t3, BR[0][1][1], MSK_B1, t, 256*3/4, 0, 0, NULL, 0);
mop(OP_LDBR, &BR[1][3][0], t3, BR[0][1][1], MSK_B5, t, 256*3/4, 0, 0, NULL, 0);
exe(OP_CCAT, &r1, BR[1][1][0], EXP_H3210, BR[1][1][1], EXP_H3210, 0, EXP_H3210,OP_NOP,0,OP_NOP,0);
exe(OP_CCAT, &r2, BR[1][2][0], EXP_H3210, BR[1][2][1], EXP_H3210, 0, EXP_H3210,OP_NOP,0,OP_NOP,0);
exe(OP_CCAT, &r3, BR[1][3][0], EXP_H3210, BR[1][3][1], EXP_H3210, 0, EXP_H3210,OP_NOP,0,OP_NOP,0);
exe(OP_MMRG, &r0, r1, EXP_H3210, r2, EXP_H3210, r3, EXP_H3210,OP_NOP,0,OP_NOP,0);
mop(OP_STR, &r0, out++, 0LL, MSK_D0, out, WD, 0, 0, NULL, 0);
}
//EMAX5A end
20220202
8
2way SIMD版
SIMD Load →
SIMD Store ←
Color map tables
10. //EMAX5A begin tone_curve mapdist=0
for (CHIP=0; CHIP<NCHIP; CHIP++) { /* output channels are parallelized by multi-chip (OC/#chip) */
/*2*/for (INIT1=1,LOOP1=RMGRP,rofs=0-WD*4; LOOP1--; INIT1=0) { /* stage#0 *//* mapped to FOR() on BR[63][1][0] */
/*1*/for (INIT0=1,LOOP0=WD,cofs=0-4; LOOP0--; INIT0=0) { /* stage#0 *//* mapped to FOR() on BR[63][0][0] */
exe(OP_ADD, &cofs, INIT0?cofs:cofs, EXP_H3210, 4, EXP_H3210, 0LL, EXP_H3210, OP_AND, 0x00000000ffffffffLL, OP_NOP, 0LL); /* stage#0 */
exe(OP_ADD, &rofs, rofs, EXP_H3210, INIT0?WD*4:0, EXP_H3210, 0LL, EXP_H3210, OP_NOP, 0LL, OP_NOP, 0LL); /* stage#0 */
exe(OP_ADD, &pofs, rofs, EXP_H3210, cofs, EXP_H3210, 0LL, EXP_H3210, OP_AND, 0x00000000ffffffffLL, OP_NOP, 0LL); /* stage#1 */
/*map0*/
mop(OP_LDWR, 1, &BR[2][1][1], (Ull)rtop0[CHIP], pofs, MSK_D0, (Ull)rtop0[CHIP], WD*RMGRP/2, 0, 0, (Ull)NULL, WD*RMGRP/2);/* stage#2 */
mop(OP_LDUBR, 1, &BR[3][1][1], (Ull)t1, BR[2][1][1], MSK_B3, (Ull)t1, 256/8, 0, 0, (Ull)NULL, 256/8); /* stage#3 */
mop(OP_LDUBR, 1, &BR[3][2][1], (Ull)t2, BR[2][1][1], MSK_B2, (Ull)t2, 256/8, 0, 0, (Ull)NULL, 256/8); /* stage#3 */
mop(OP_LDUBR, 1, &BR[3][3][1], (Ull)t3, BR[2][1][1], MSK_B1, (Ull)t3, 256/8, 0, 0, (Ull)NULL, 256/8); /* stage#3 */
exe(OP_MMRG, &r1, BR[3][1][1], EXP_H3210, BR[3][2][1], EXP_H3210, BR[3][3][1], EXP_H3210, OP_NOP, 0LL, OP_NOP, 0LL); /* stage#3 */
mop(OP_STWR, 3, &r1, (Ull)dtop0[CHIP], pofs, MSK_D0, (Ull)dtop0[CHIP], WD*RMGRP/2, 0, 0, (Ull)NULL, WD*RMGRP/2);/* stage#3 */
/*map1*/
mop(OP_LDWR, 1, &BR[4][1][1], (Ull)rtop1[CHIP], pofs, MSK_D0, (Ull)rtop1[CHIP], WD*RMGRP/2, 0, 0, (Ull)NULL, WD*RMGRP/2);/* stage#4 */
mop(OP_LDUBR, 1, &BR[5][1][1], (Ull)t1, BR[4][1][1], MSK_B3, (Ull)t1, 256/8, 0, 0, (Ull)NULL, 256/8); /* stage#5 */
mop(OP_LDUBR, 1, &BR[5][2][1], (Ull)t2, BR[4][1][1], MSK_B2, (Ull)t2, 256/8, 0, 0, (Ull)NULL, 256/8); /* stage#5 */
mop(OP_LDUBR, 1, &BR[5][3][1], (Ull)t3, BR[4][1][1], MSK_B1, (Ull)t3, 256/8, 0, 0, (Ull)NULL, 256/8); /* stage#5 */
exe(OP_MMRG, &r1, BR[5][1][1], EXP_H3210, BR[5][2][1], EXP_H3210, BR[5][3][1], EXP_H3210, OP_NOP, 0LL, OP_NOP, 0LL); /* stage#5 */
mop(OP_STWR, 3, &r1, (Ull)dtop1[CHIP], pofs, MSK_D0, (Ull)dtop1[CHIP], WD*RMGRP/2, 0, 0, (Ull)NULL, WD*RMGRP/2);/* stage#5 */
/*map2*/
mop(OP_LDWR, 1, &BR[6][1][1], (Ull)rtop2[CHIP], pofs, MSK_D0, (Ull)rtop2[CHIP], WD*RMGRP/2, 0, 0, (Ull)NULL, WD*RMGRP/2);/* stage#6 */
mop(OP_LDUBR, 1, &BR[7][1][1], (Ull)t1, BR[6][1][1], MSK_B3, (Ull)t1, 256/8, 0, 0, (Ull)NULL, 256/8); /* stage#7 */
mop(OP_LDUBR, 1, &BR[7][2][1], (Ull)t2, BR[6][1][1], MSK_B2, (Ull)t2, 256/8, 0, 0, (Ull)NULL, 256/8); /* stage#7 */
mop(OP_LDUBR, 1, &BR[7][3][1], (Ull)t3, BR[6][1][1], MSK_B1, (Ull)t3, 256/8, 0, 0, (Ull)NULL, 256/8); /* stage#7 */
exe(OP_MMRG, &r1, BR[7][1][1], EXP_H3210, BR[7][2][1], EXP_H3210, BR[7][3][1], EXP_H3210, OP_NOP, 0LL, OP_NOP, 0LL); /* stage#7 */
mop(OP_STWR, 3, &r1, (Ull)dtop2[CHIP], pofs, MSK_D0, (Ull)dtop2[CHIP], WD*RMGRP/2, 0, 0, (Ull)NULL, WD*RMGRP/2);/* stage#7 */
/*map3*/
mop(OP_LDWR, 1, &BR[8][1][1], (Ull)rtop3[CHIP], pofs, MSK_D0, (Ull)rtop3[CHIP], WD*RMGRP/2, 0, 0, (Ull)NULL, WD*RMGRP/2);/* stage#8 */
mop(OP_LDUBR, 1, &BR[9][1][1], (Ull)t1, BR[8][1][1], MSK_B3, (Ull)t1, 256/8, 0, 0, (Ull)NULL, 256/8); /* stage#9 */
mop(OP_LDUBR, 1, &BR[9][2][1], (Ull)t2, BR[8][1][1], MSK_B2, (Ull)t2, 256/8, 0, 0, (Ull)NULL, 256/8); /* stage#9 */
mop(OP_LDUBR, 1, &BR[9][3][1], (Ull)t3, BR[8][1][1], MSK_B1, (Ull)t3, 256/8, 0, 0, (Ull)NULL, 256/8); /* stage#9 */
exe(OP_MMRG, &r1, BR[9][1][1], EXP_H3210, BR[9][2][1], EXP_H3210, BR[9][3][1], EXP_H3210, OP_NOP, 0LL, OP_NOP, 0LL); /* stage#9 */
mop(OP_STWR, 3, &r1, (Ull)dtop3[CHIP], pofs, MSK_D0, (Ull)dtop3[CHIP], WD*RMGRP/2, 0, 0, (Ull)NULL, WD*RMGRP/2);/* stage#9 */
20220202
10
3重ループをIMAXの1回の起動に写像する
実際には、#define tone_core1(r, rm1, rp1)
などと定義して並べるので、こんな冗長な書き方は不要だが、
最初なのでブラックボックス無しで説明
Notes de l'éditeur 導入編が終わった人向けに、これから、アイマックスの具体的なプログラミング過程を説明していきます。
第2回は、画像フィルタです。 色を入れ換えるだけの簡単な画像処理から始めます。左画像を入力すると、右画像のように、色が変わります。色をどのように変換するかは、RGB成分ごとの変換表をひとつにまとめた配列tで定義します。まず、C言語で書きます。画像は2次元構造ですが、画素はメモリ上で一列に並んでいるので1次元配列を使い、プログラムの構造は、あとでステンシル計算に対応できるよう、2重ループにします。インが入力画素、ピックスが処理中の1画素、tが色変換表、アウトが出力画素です。RGBの色成分は8ビットなので、シフトとマスク演算を使ってピックスから各8ビットを取り出します。そして、各成分を使って色変換表を引き、最後に3色をつなぐと、出力のでき上がりです。
まず、アイマックスで実行する範囲をビギンとエンドで括ります。最初は、最内ループのみを対象とします。アイマックスは、各ユニット自身がループ制御も行います。ユニット内の単純な演算器に写像できるよう、最内ループは、ループ0という予約語を使って変形します。そして、Colを整数配列の添え字から、バイトアドレスに変更し、更新結果を後続ユニットが参照できるようにします。具体的には、初期値をマイナス4にして、ループ内部の先頭で4を加算するようにします。これで、先頭ユニットが、初期値マイナス4のcolを毎サイクルインクリメントし、後続ユニットが、元のプログラム通りにcolを参照できるようになります。もちろん、colを整数配列の添え字に使っている部分は、わる4が必要です。 次に、最内ループの中を少しづつ、アイマックス用の関数形式に書き換えていきます。ここでは、ピックス、RGBの代入部分のみを書き換えました。インロウWD、TR、TG、TBは、それぞれ、画像各ぎょうの先頭アドレス、赤成分、緑成分、青成分変換表の先頭アドレス、つまりベースアドレスです。LDUBRは、ロードしたピクセルちから、指定したバイト位置を切り出したものをオフセットとして、ベースアドレスに加えて1バイトをロードします。マスクB3は、右から4バイト目、マスクB2は、右から3バイト目、マスクB1は、右から2バイト目の各1バイトを意味します。元のC言語にあったシフトとマスク演算が、このように高機能ロード関数に吸収され、僅か4関数に置き換わりました。また、最初のロード関数末尾の、inとWDは、必要とするデータの先頭と長さです。今は画像1行分を扱うので、ワード数にWDを指定します。同様に、RGBの変換では、共通の配列tを使うので、先頭はt、ワード数は256かける3わる4です。さて、ここまで書き換えたら、コンパイルして、動作を確認することができます。実際に実行できるだけでなく、ロード関数の中で、ベースアドレスとオフセットが、指定した範囲を逸脱していないかがチェックされます。アイマックス用の関数形式は、途中の状態でも、普通のCコンパイラでコンパイルし、実行できます。途中にプリントエフを挿入しながら、少しづつ書き換えて、アルゴリズムのデバッグを進めていける点が大きな特長です。 残りの部分も、書き換えていきます。MMRGを使うと、変換後のRGBを1つにまとめることができます。最後のストアも、末尾に、先頭アドレスと長さを書いて、ベースアドレスとオフセットの逸脱をチェックします。以上で、元の最内ループが、わずか7関数に変換されました。最初、アイマックスの書き方が、アセンブラのようだと思ったことでしょう。確かにアセンブラに似ていますが、高機能ハードウェアを余すことなく使うためには、このような高機能関数として記述するほうが、無駄がなく、また、コンパイル時間を大幅に短縮できます。ノイマン型は、複雑なプログラムには、命令数を増やすことで対応しますが、CGRAは、高効率である代わりに、ハードウェア資源に制約があり、命令数をいくらでも増やすことはできません。自由記述のプログラムをコンパイラに丸投げすることをいくら繰り返しても、最適解にはたどり着けませんし、デバッグも不可能です。これが、アイマックスのプログラミングに高機能関数を使う理由です。ところで、CGRAをある程度知っている人は、これは逐次実行プログラムではないかと思うことでしょう。その通りです。逐次実行プログラムとしてアルゴリズムをデバッグし、最後は、アイマックス専用コンパイラでCGRAに写像し、同じ実行結果と、高効率処理を手に入れる。これが、アイマックスのプログラミング思想です。 では、データの流れに着目して、プログラムを見直します。青いぎょうは、入力画像を必要とします。CGRAでは、最も上に配置されるべきです。次に、緑のぎょうは、RGB変換表を必要とします。入力画像をロードした後に使うので、CGRAでは、入力画像よりも下のユニットにしか配置できません。ただし、RGBの変換表は、一度に引くことができるので、同じ位置に配置できます。同様に、出力画像は最後に配置されます。このように、アイマックスコンパイラは、変数の依存関係を解析して、どのユニットに、どのデータを配置するかを決めます。 アイマックスコンパイラは、このように、コンパイル結果を可視化してくれます。右うえはじが第0行0列です。ここには、最内ループのカウンタ初期値と、ALUを使う減算がセットされます。カウンタが0になったら、下のユニットに停止指示を出します。後続ユニットの動作が、順に止まっていきます。第0行1列には、colの加算、第1行0列には、最初のピックスロードがセットされています。右から2番目のレジスタに,pixという名前が見えます。これが、ロード結果が入るレジスタです。第2行0列から2列に、LDUBRがセットされています。そして,第3行0列に,紫のMMRGと、ストアがセットされます。このプログラムでは、アイマックスを起動すると、コンパイラが自動生成するDMA機能を使って、第1行0列に入力画像,第2行0列から2列に色変換表が用意された後、第3行0列のメモリに、毎サイクル1つの出力画素を格納します。演算器内部だけでなく、ユニット間もパイプライン化されているので、毎サイクル結果が出てくるわけです。実行が終わったら、同様にDMAでホストの主記憶に書き戻されます。これで、画像の1行だけを加速するアイマックスプログラムの完成です。でも、色がついている部分がまばらですね。ハードウェアは、64行4列分あります。まだ、ほんの小手調べです。 では、高性能化していきます。さっきのプログラムは,64ビットレジスタの半分しか使っていません。もったいないですね。アイマックスの演算器はツーウェイシムディーです。32ビットの画素値を2つロードすることで,処理速度を2倍に上げた64ビット版プログラムです。また、最初の1行を節約するために、フォー文の代わりにワイル文を使い、ロードストアにオートインクリメントを使っています。最初のLDRは,先頭ユニットのローカルメモリから,32ビットの画素値を2つロードして,BRゼロイチイチに格納します。続く6行のLDUBRは,色変換表の先頭をベースアドレス,最初にロードした画素値の合計6箇所の色成分をオフセットとして,1バイトをロードし,各々BRに格納します。3つのシーキャットは,同色の2つの1バイトデータを各々1つのレジスタにまとめ,MMRGが,3つのレジスタの内容をまとめて2つの画素値に戻し,最後のSTRが,64ビットデータを2つの出力画素としてメモリに書き込みます。アイマックスの実行回数も、画像の横幅、WDの半分になるので、ループ初期値はWDわる2になっています。 コンパイル結果です。右うえはじが第0行0列です。第0行は、さっきの32ビット版と同じですが、オートインクリメントを使うことで、最初のロードも第0行に配置できています。第1行1列から3列に、LDUBRが2つずつセットされています。デュアルポートメモリのおかげです。第2行0列から2列に、シーキャットがセットされています。そして、第3行0列に、MMRGとSTRがセットされます。第3行0列のメモリに、毎サイクル2つの出力画素を格納するので、性能が2倍になりました。たしかに実装密度は上がりました。でも、まだたくさん余っています。 アイマックスには、1基あたり、64個の物理ユニットが入っていますが、今までの2つの例では、4個しか使っていません。また、アイマックスは、画像1行を処理するだけなので、起動回数は、画像の高さHTイコール240でした。いろいろもったいないです。そこで、同じ処理を残りのユニットにも割り当て、多くのユニットを使うことで、アイマックスの起動回数を減らすことを考えます。32ビット版で作ったプログラムを10倍に増やしたのが、このプログラムです。長いので、途中は省略です。また、プログラムの構造が、ワイル文の単純ループではなく、3重ループになっています。アイマックスが4基参加し、10個の物理ユニットが各々2重ループにより、幅方向WD、高さ方向6の画像を一度に処理するようにします。これで、アイマックスの起動回数は何回になったでしょう。240わる4基わる10ユニットわる6は1です。起動回数はわずか1回になりました。 コンパイル結果です。左側の第0行と第1行は,3重ループの制御とアドレス計算に使われています。第2行から第4行に,さっきの32ビット版が埋め込まれているのがわかります。10倍にふやしたので,物理ユニットは,3掛ける10の30必要でしょうか。いいえ,違います。よく見ると,第2行から第4行に対応するデータフローと同じ形が,第5行から第7行ではなく,1つ手前の,第4行から第6行にあります。つまり,コードを10倍に増やしても,論理ユニットに空きがあれば,一部オーバラップできるということです。全体では,22個の物理ユニットにおさまります。まだまだ物理ユニットが空いていますね。4バイトの1画素は,320掛ける6でも8キロバイト未満しかないので,64キロバイトのローカルメモリも大部分が余っています。物理ユニット数64というのは,少ないように感じるかもしれませんが,まだまだ余力があります。ところで、導入編で、コンパイル時間が長いのはコンピュータじゃないと言いました。このプログラムのCGRA写像に要する時間は、1秒未満です。これなら、何度でも試すことができます。 ここまで、ただ色を入れ替えるだけの簡単なプログラムを説明してきました。アイマックスの機能も能力も、まだ5パーセントくらいしか使っていませんが、プログラミングスタイルとポテンシャルを理解できたことと思います。この動画の視聴者が増えてきたら、のこりの95パーセントも解説していくことにします。まあ、でも、99.99パーセントの、おなか一杯技術者は、別に、こんなの知らなくても、それなりに給料もらえるはずだと思ってることでしょう。あるいは、どこかのメーカーが、自由記述のプログラムをなんとかしてくれる、完全自動コンパイラを作ってくれると、見なかったことにすることでしょう。まだいるかもしれない、0.01パーセントのはらぺこ技術者のために、そのうち、残り95パーセントの解説動画を作ることにします。そういえば、画像フィルタ編のはずでしたが、色変換しか説明できませんでした。画像フィルタ編の続きは、メディアンフィルタ、アンシャープマスク、エッジ検出、フレーム補間、超解像、ステレオマッチングを予定しています。では、今回はここまでです。