nao-milkの経験ブログ

25年間の半導体エンジニア経験で知り得た内容を記載したブログです。

Excel(VBA)で拡大/縮小処理の確認(バイリニア補間追加)

f:id:nao-milk:20210404005615p:plain

拡大/縮小処理に「バイリニア補間処理」を追加した場合の画像をご紹介します。

前回の「Excel(VBA)で拡大/縮小処理の確認」を改造し、少し画質が良くなったものとなります。

(バイリニア補間の詳細は、ネットで検索すると演算式が出てきますので、割愛します。)

nao-milk.hatenablog.com

 

バイリニア補間画像

以下に画像を示します。

※前回の「単純に参照画素を出力」する方法を「近傍法」と言います。

f:id:nao-milk:20210404004620p:plain

拡大縮小画像(バイリニア補間)


近傍法より、バイリニア補間法がキレイに見えると思います。

 

補足)

 バイリニア補間法より「バイキュービック補間法」という方法がキレイになります。

 

VBAの追加変更箇所

バイリニア補間を追加/変更箇所を以下に示します。

f:id:nao-milk:20210404002219p:plain

VBA記述(追加変更箇所)

配列Pix()を追加し、GetPix()とBilinear()を追加しました。

GetPix()は、参照画素の座標を指定し、入力画像から画素値を取得します。

ただし、

入力画像範囲外の座標を指定した場合は端の画素を取得できるようになっています。

 

GetPix()記述

GetPixのVBA記述を以下に示します。

f:id:nao-milk:20210404002804p:plain

VBA記述(GetPix)

Bilinear()記述

BilinearのVBA記述を以下に示します。

f:id:nao-milk:20210404003028p:plain

VBA記述(Bilinear)

バイリニアは、4画素に対して重み付けを行い、その合計を結果として出力します。

Pix(0)が注目画素となり、その周辺画素がPix(1)~(3)になります。

 Pix(0) = 座標(x,y)

 Pix(1) = 座標(x+1,y)

 Pix(2) = 座標(x,Y+1)

 Pix(3) = 座標(x+1,y+1)

 

引数DtXがX方向の小数部、DtYがY方向の小数部となります。

変倍カウンタ CtXの下位16bit、CtYの下位16bitが小数部です。

 

各重み付けは、

X方向の重み付けしてからY方向の重み付けを行います。

これはビットオーバーフロー(演算結果が32bit以上)を防ぐため整数値に戻してから次のY方向の重み付けを行います。

演算式をbit幅で表すと「9bit×17bit×17bit」となり、演算結果の必要bit数は43bitとなり、Long型の32bit超えます。

 

そこで、最初に

 X方向:9bit×17bit → 26bit → (2^16=65536[17bit])で割り、9bitにして

 Y方向:9bit×17bit → 26bit → (2^16=65536[17bit])で割り、9bitにしています。

 

最後に

今回は、補間処理に関して紹介しました。

拡大/縮小では、参照画素を出力するだけではカクカクした画像になり、補間処理を行うことで滑らかになります。

 

Excel(VBA)で拡大/縮小処理の確認

f:id:nao-milk:20210402164348p:plain

拡大/縮小処理のプログラムをExcel VBAで作成しました。

尚、生成中は浮動小数演算を使用せず固定小数を用いています。

 

 

 参考記事

拡大/縮小処理は、以下の記事を参照ください。

nao-milk.hatenablog.com

 

浮動小数を使用しない演算については、以下の2つの記事を参照ください。 

nao-milk.hatenablog.com

 

nao-milk.hatenablog.com

 

どのような画像になるのか

横×縦=32×32画素の入力画像をVBAで0.75倍、1.25倍した画像を以下に示します。

尚、バイリニアなどの補間処理は行っていません。

※画素値は0~511までの範囲をとり、0が白、511に近づくほど青になっていきます。

f:id:nao-milk:20210402142328p:plain

拡大縮小画像(±0.25)

 

プログラム構成

Subプロシージャのみとなり、以下がプログラムです。

このプログラムの特徴は以下の通りです。

  1. 横と縦の変倍率は独立しています。(ParaRateXとParaRateY)
  2. 入力画像の横と縦のサイズは指定が可能。(ParaISizeXとParaISizeY)
  3. 出力画像の横と縦のサイズは指定が可能。(ParaOSizeXとParaOSizeY)
  4. 変倍カウンタの小数部のbit幅は指定が可能。(ParaBtSize)
  5. 拡大/縮小時、入力画像が無い場合は、出力画素値を「-1」にします。

 

f:id:nao-milk:20210402145025p:plain

VBA記述(BigSmall)

パラメータ取得(11~17行目)

シート「パラメータ」で設定された値を取得します。(以下、シート「パラメータ」の抜粋)

f:id:nao-milk:20210402145230p:plain

B列の「設定値」を変更し、[拡大/縮小 開始]ボタンをクリックすると、シート「Image」にある入力画像を拡大又は縮小して結果をシート「出力」に出力します。

冒頭の画像(「拡大縮小画像(±0.25)」)が抜粋したものになります。

 

カウンタ加算値(25~28行目)

変倍率から変倍カウンタの加算値を算出します。(25,27行目)

また、浮動小数から固定小数に変換します。(26,28行目)

小数部のbit幅は、シート「パラメータ」の"変倍カウンタ小数部bit幅"で指定した値を使用します。(このプログラムでは、16bitに設定しています。)

 

カウンタ動作(43~68行目)

横と縦方向にカウンタを動作させて拡大又は縮小処理を行います。

 

X方向 及び Y方向の出力画素数は、変数OtX及びOtYで管理し、出力画像サイズまで動作します。

また、変倍カウンタは変数CtX及びCtYとなり、カウンタ加算値で算出した値を毎回加算します。

 

拡大/縮小時に入力画像から参照する画素は変数CtXとCtYの整数部になり、その整数部(入力画像の参照画素位置)は、変数ItX及びItYとなります。

尚、変数DtXとDtYは小数部となります。(補間時に使用する目的で残しています。)

 

54行目で変数ItXとItYからシート「Image」にある入力画像から画素を参照し、変数OutValueに代入します。

その後、

61行目で出力画素位置(変数OtXとOtY)で示す位置にOutValueの値を出力します。

(シート「出力」に出力します。)

 

 

変倍カウンタ動作 

拡大及び縮小時のカウンタ動作を以下に示します。

「カウンタ値」欄は、VBAからExcelに表示させた値となり、

「整数部」欄は、「カウンタ値 / 65536」の整数値を示し、

「小数部」欄は、「カウンタ値 % 65536」の値を示します。

また、「(小数値)」欄は、「小数部 / 65536」を示し、「浮動小数値」は変倍率から浮動小数で計算したカウンタ値になります。

 

f:id:nao-milk:20210402162504p:plain

変倍カウンタ値

  

最後に

画質は参照画素をそのまま出力しているため、悪いですが、変倍カウンタは浮動小数で計算した場合と誤差1画素となります。

画質評価で小数部のbit幅を増やすなどで設計検討したりします。

 

Excel(VBA)でRGB2YUVの確認

 

f:id:nao-milk:20210402003358p:plain

 前回の「浮動小数を使用せず演算」で記載したRGB→YUV変換(演算式②)の精度を確認するため、Excel VBAでプログラムを作りました。

 

Excelを使用することにより、以下のように画像確認ができます。 

f:id:nao-milk:20210401234615p:plain

Excelでの確認例

 

前回の参考記事は、以下になります。

nao-milk.hatenablog.com

 

 

前回の内容

 以下がYUV変換(演算式②)となります。

f:id:nao-milk:20210330181138p:plain

係数は以下の通りとなり、n=16(×65536)VBAを作成しました。

f:id:nao-milk:20210330181929p:plain

 

VBAを使う理由

演算精度を確認する場合、Excelが便利です。

が、一番の理由は視覚的にすぐ修正できる所です。

特に画像処理の場合は、期待値を作るのと同時作業ができます。

 

補足)

 半導体設計では、期待値というものを作る必要があります。

 これは設計した回路が正しく出力されているかシミュレーションで確認するためのものです。

 回路では並列処理のためFFでバトンリレーのように処理します。

 一つでもタイミングがズレていたりすると出力値が違ってくるためそれを確認します。

 画像処理では、何万というデータがあります。

 その一つ一つを正しく出力しているか、期待値を準備して比較アプリ(コマンド)で確認します。

 ※目視確認では何万とういうデータを確認する為に時間がかかるため。

 

 (この期待値を作るために、C言語VBAを自然と勉強してしまいます。)

 

プログラム構成

テストがしやすいように2階層に分かれています。

f:id:nao-milk:20210401220317p:plain

階層構造

 

各Functionの概要と引数は以下の通りとなります。

 

f:id:nao-milk:20210401223302p:plain

 

f:id:nao-milk:20210401223509p:plain

 

Function CalcRGB2YUV

R,G,B成分と係数やClip処理用変数を受け、演算します。

また、上位階層で係数を負数(-???)で設定することにより、U,V成分の演算が可能になります。

 

記述

VBAプログラムは以下の通りです。

f:id:nao-milk:20210401223758p:plain

CalcRGB2YUV()の記述

 

検証

Functionを使用しているため、Excelのユーザー定義マクロとして使用できます。

従って、Excelを使って簡単に検証(デバッグ)が可能です。

 

確認内容を以下に示します。

「入力値」の各RGB成分使って「通常係数」の場合と「係数×65536」で計算します。

「通常係数」が浮動小数で演算した値を示し、「係数×65536」がCalcRGB2YUVで演算した値となります。

f:id:nao-milk:20210401230316p:plain

CalcRGB2YUVの検証

上表より、各YUV成分の整数部の誤差は1と分かります。
 

Function RGB2YUV

 引数Pointに指定したセルの背景色から各RGBを取得し、YUV変換の計算を行います。

 

記述

VBAプログラムは以下の通りです。

YUV変換で必要な各パラメータは「Const」で指定できるようにしています。

精度を変更する場合でもこのConstの値を変更するだけで良いようになっています。

f:id:nao-milk:20210401231233p:plain

RGB2YUV()の記述

Y成分は「ValueY」、U成分は「ValueU」、V成分は「ValueV」に代入し、変数Modeの設定値に応じて選択して返します。

 

検証

 入力用のシートを準備し、Y,U,V成分の値を出力し、確認します。

 

入力画像

入力画像を以下に示します。

簡易的にテストするため、縦横32画素としています。

 

f:id:nao-milk:20210401215745p:plain

入力画像

 入力画像は以下の式で生成しています。

f:id:nao-milk:20210401232814p:plain

入力画像生成式

Cells(Y + 2, X + 2).Interior.Color」の記述でセルに色を設定しています。

 

出力値

RGB2YUV()のPointに入力画像の各セルを指定し、Y成分、U成分、V成分の値をセルに出力し、色付けして確認します。

 

各結果を以下に示します。

 

Y成分結果

f:id:nao-milk:20210401233435p:plain

Y成分結果

 

U成分結果

f:id:nao-milk:20210401233550p:plain

U成分結果
V成分結果

f:id:nao-milk:20210401233701p:plain

V成分結果

最後に

画像処理のデバッグ段階では、Excelを使った方が便利です。

画像のように2次元的に見れるのと、シミュレーション結果との違いも色をつけたり、視覚的にとらえることが可能です。

 

ただ、欠点もあります。

  1. デバッグ時の小さいサイズならExcelの実行時間に問題はないですが、実サイズ検証で大きいサイズになると実行時間がかかり不向きです。
  2. VBAC言語と構文が違うため、C言語の移植が大変です。

 

私の場合(半導体設計時)は、ブロック単位のデバッグ時はExcelで期待値を作って視覚的にデータ解析を行います。

その後、C言語を使って大きいサイズの期待値を作成し、期待値比較確認を行うようにしています。

除算回路を使用しない方法

f:id:nao-milk:20210331130222p:plain

除算回路を使用せずに乗算とビットシフトで演算する方法です。

C言語では、普通に「/」で記述して問題ないですが、半導体設計では除算IPや自作します。

 

5個の8bit値の平均値をとる例で説明します。

尚、前回の記事「浮動小数を使用せず演算」とよく似ていますので、その記事も参考して頂ければと思います。

nao-milk.hatenablog.com

 

平均処理の演算式①

平均処理の演算式を以下に示します。

※式のA~Eは、8bitの値と示します。

f:id:nao-milk:20210331114923p:plain

平均処理の演算式①

 

回路構成①

上記演算式①の回路構成を以下に示します。

f:id:nao-milk:20210331115418p:plain

演算式①のRTL図

A値~E値の合計(最大値は1275)を「5」で割るだけですが、この「割る」という処理を自作した回路に処理するか 又は 除算IPを使用します。

また、除算の結果を得るまでに数クロックかかります。

※クロック周波数、半導体ベンダー 又は IPベンダーにより違う為、確認する必要があります。

 

平均処理の演算式②

除算を使用せず平均処理を行う式を以下に示します。

※「n」はビットシフト量を示します。(固定小数の小数部bit幅を示す)

f:id:nao-milk:20210331120335p:plain

平均処理の演算式②

例えば、

ビットシフト量を18bitと決めてしまうと、係数式「1/5×2^18」となり値は「52428.8」となります。

この「52428.8」の整数値をA値~E値の合計に掛け、その結果を18bitシフトすると除算結果が得られます。

尚、私は「52428.8」の小数点以下四捨五入し、「52429」としています。

 

演算精度を確認

通常計算した場合と、演算式②の方法で演算した場合との誤差を確認します。

 

A値~E値の合計の最大値(=255×5)は「1275」となります。

従って、

割られる値(被除数)は0~1275の範囲となるため、Excelで十分確認できます。

 

以下がExcelで確認した結果となります。

尚、被除数=1~1275の範囲での最大誤差は「0.00097504」となり、小数点以下3桁までは誤差は無いということになります。

f:id:nao-milk:20210331123908p:plain

演算精度確認

 

回路構成②

演算式②の回路構成を以下に示します。

f:id:nao-milk:20210331124818p:plain

演算式②のRTL図

乗算結果は26bitとなり、上位8bitが整数値となるためFFでラッチして平均値を出力します。

※下位18bitは、1/5を掛けた時の小数部となります。

 小数点以下四捨五入したい場合は、TMLT[17]を条件とすれば良いだけとなります。

 

RTL記述例

演算式②のRTL記述(Verilog-HDL)を以下に示します。

f:id:nao-milk:20210401201301p:plain

RTL記述例

21~25行目は加算器4つ使用するため、CKの周波数でタイミングがMetしなかった場合は、FFを用いて加算を分割する必要がある。

乗算(29行目)は固定値と上位8bitを使用することより、タイミングはMetする可能性はあるが、もしMetしなかった場合は、乗算IPを用いてパイプライン化する必要があります。

 

最後に

この方法は1つの例です。

ASIC又はFPGAの規模やDSP数などにより、除算IPを使用した方が良い場合もありますので、こういう方法もあると頭の片隅に置いて頂いて、設計検討して頂ければと思います。

 

浮動小数を使用せず演算

f:id:nao-milk:20210330230451p:plain

小数を含む演算は、浮動小数点ユニットを使用する為、処理速度が遅くなります。

しかし、その式に乗算とビットシフトを追加することで演算速度が速くなります。

 

その方法を順を追って、また、ソフトウェア(C言語)とハードウェア(半導体設計)に分けて説明します。

 

まずは説明する上で、画像処理の1つ「RGB→YUV(YCbCr)変換」をご存じでしょうか?

この変換式を例に以下説明します。

 

YUVの用途 (ちょっとした用途の説明)

YUVは、人間の目の特性を利用したデータであり、変化を敏感に感じ取る輝度そうではない色差に分かれます。

従って、

輝度さえしっかりしていれば色差は間引くことが可能とも言えます。

(詳細はネット上に説明があるので、調べてください。)

尚、

 Yは、輝度

 Uは、輝度信号と青色成分の差

 Vは、輝度信号と赤色成分の差

を示します。

 

この特性を持ったYUVは、画像(静止画)や映像(動画)圧縮(JPEG,MPEG,H.264など)で使われます。

例えば、

カメラで撮った映像(動画)を圧縮して動画ファイルとして保存する場合です。

 1、カメラからR(赤),G(緑),B(青)形式で撮ります。

 2、Image ProcessorRGB形式をYUV形式に変換して各圧縮に応じた処理を行います。

 3、圧縮後のデータをファイルに保存します。

f:id:nao-milk:20210330164800p:plain

 

RGB→YUV変換式

RGBからYUVへの変換式は、以下の通りです。

f:id:nao-milk:20210330181236p:plain

演算式①

上記の式では、YUV各成分の係数を足すと1.000になるのが特徴です。

 例)

  Y成分の係数の和 = 0.299 + 0.587 + 0.114  = 1.000

尚、変換式は圧縮規格を発表している所で変わるため、各勧告書を確認してください。

 

C言語の記述例①

演算式①をそのままC言語で記述すると以下のようになります。

f:id:nao-milk:20210330172133p:plain

C言語 記述例①

15~17行目で変換式そのままで演算し、cy,cu,cvを算出しています。

また、係数が小数の為、演算結果もdouble型(倍精度浮動小数)で演算しています。

※double型にすると、64bit演算となります。

※float型の場合は、32bitとなります。

 

この記述例①のソースでは、CPUはFPU(浮動小数点ユニット)を使用します。

 

たかがRGB各8bitデータからYUV各8bitデータを求めるのに64bit(or 32bit)浮動小数点ユニットを使用し、

しかも、キャスト演算(unsigned char 、char)で小数部は切り捨てられるにも関わらず。

私としては、浮動小数点ユニットは使用する必要は無いと考えます。

尚、浮動小数演算を使うことにより、1つの演算に時間がかかります

 

浮動小数を使わずに演算

では、小数を含む演算を浮動小数点ユニットを使用せず演算する方法は?

※注意事項となりますが、この方法は浮動小数点演算との差は発生します。

 ただ誤差も小さいため、製品精度又は画質評価を行って確認してください。

 (人間の目なのでわからないかもしれませんが。。。)

 

方法はすごく簡単です。

各係数を何倍かにして、算出した各YUV成分を倍した分で割るだけです。

尚、

割り算は処理に時間がかかるため、ビットシフトで演算できるよう、各係数を2のべき乗で掛けます。

演算式は以下のようになります。

f:id:nao-milk:20210330181138p:plain

演算式②

尚、係数(小数点以下あり)に2のべき乗を掛けた結果は、小数点以下切り捨て又は四捨五入して整数とします。

 

演算式①の係数を256倍(n=8) 及び 65536倍(n=16)にした場合の係数を以下に示します。

(下の表では、小数点以下を四捨五入しています。)

f:id:nao-milk:20210330181929p:plain

演算式②の各係数

上記係数を用いて演算した例を以下に示します。

尚、「256で割った値」と「65536で割った値」は誤差を示すため便宜上、小数点以下も表示させています。

 

f:id:nao-milk:20210330183415p:plain

演算例

上記演算例では、

係数を256倍すると、Y成分の整数部で誤差が発生しています。

この場合、YUV各成分結果を算出する時に小数点以下を切り捨てると、浮動小数計算と1bitの誤差が発生します。

また同様に65536倍した場合も小数部で浮動小数計算と誤差が発生しています。

 

これらの誤差が製品の許容範囲かどうかを画質評価などで確認する必要があります。

尚、演算式②の「n」を大きくするほど精度は向上します。

※YUV各成分は演算結果の整数部の8bitとなり、画質評価はその8bitの1~2bitの誤差をどこまで許せるかの判断になります。

 

C言語の記述例②

演算式②でn=16(65536倍)とした場合のC言語記述例を以下に示します。

尚、係数は表「演算式②の各係数」を参照してください。

f:id:nao-milk:20210330185415p:plain

C言語 記述例②

15~17行目で演算し、cy,cu,cvを算出しています。

この演算では、係数は小数ではなく整数 且つ RGB各成分をInt型にキャスト 且つ ビットシフトで割り算を行っています。

 

これにより、浮動小数点ユニットを使用する要素は無くなります。

 

浮動小数演算を使うと演算に時間がかかる

演算式①(記述例①)と演算式②(記述例②)でなぜ演算速度が変わるのか?

です。

 

浮動小数演算自体、乗算及び除算に対してはスムーズですが、加算及び減算は面倒なのと、整数から浮動小数浮動小数から整数にするため、フォーマット変換する必要があるためです。(詳細はネット上に説明があるので、調べてください。)

浮動小数のフォーマットと演算の仕組みが分かれば納得がいくと思います。

 

演算式①と演算式②で、Y成分を求めるまでの処理の流れを以下に示します。

※処理イメージとなり、CPUやコンパイラにより処理順序など変わります)

f:id:nao-milk:20210330191458p:plain

演算式①と演算式②の処理の流れ

大きな違いは、最初に「64bit浮動小数変換」が入ることです。

これにより、演算式②と比べて演算式①は「3つの処理」が増えるため、処理が遅くなります。

 

この「3つの処理」があることにより、

4Kテレビの場合では、縦横の画素数が3,840×2,160であり、計8,294,400(約800万)画素となり、演算式②に対して演算式①は24,883,200(=8,294,400×3)回数処理が余分にあることになります。そのためすごく遅く感じることとなります。
※処理クロック数でみると、整数演算と浮動小数演算とでも違いがあります。

 この違いは、CPUの仕様を確認してください。

 

RTL設計

上記まではソフトウェアを中心とした説明でしたが、ここからは半導体設計の場合で説明します。

 

半導体設計では、CPUを使わずRGB→YUV変換を自作します。

浮動小数演算で設計することも可能ですが、カメラ速度と出力速度を考慮すると演算式②で論理回路を作成します。

 

回路構成

演算式②の演算構成は、以下の通りとなります。

尚、先の「演算式①と演算式②の処理の流れ」と分かりやすいようにするため、各演算は2入力としています。

f:id:nao-milk:20210330210327p:plain

RTL図

※上図はY成分を算出する構成で記載していますが、U,Vも同じ構成 且つ Y成分構成と並列で処理したり、クロックの速度を3倍にしてシリアルで処理したりします。

 

verilog-HDLの記述例

演算式②のverilog-HDL記述例を以下に示します。

f:id:nao-milk:20210330211523p:plain

verilog-HDL記述例

65行目でClip処理を行っていますが、

係数の和が1(65536)となるようにしているため、TRGBが0xFF0000以上の値になることはありませんが、例として記述しています。

尚、Y成分は負数となることがありません。

 

タイミング図

RTL図のタイミング図を以下に示します。

f:id:nao-milk:20210330211303p:plain

タイミング図

 

最後に

係数が小数の場合、この方法で浮動小数点ユニットを使用せず計算できます。

それにより、処理速度も速くなります。

 f:id:nao-milk:20210330213653p:plain

補足)

 ×2^nは固定小数と同じ考え方となります。

 例えば

  2.12345の固定小数(小数部8bit)を求める場合、以下の式で求めます。

  f:id:nao-milk:20210330214931p:plain

  小数部8bitの固定小数を求める場合、2.12345×256=543.6032となり、

  この整数部の下位8bitが固定小数の小数点以下8bitとなります。

  (543=0x21F → 固定小数を2進数で示すと「10.00011111」となります。)

 

結局のところ、演算式②は固定小数点演算しているのと同じになります。