LoginSignup
10
10

More than 5 years have passed since last update.

MyHDLで画像処理

Posted at

MyHDLとは

http://www.myhdl.org/
で開発されている、Pythonでハードウェアのモデリングを行うためのライブラリです。今回、MyHDLを使って画像処理をモデリングしてみました。レイヤー的には、SystemCと同じ位置にいると感じました。

全てのソースはこちらにupしています。
https://github.com/natsutan/computervision/tree/master/LOCV/chap5

画像処理の説明

入力画像のある範囲にたいして、単純なぼかしを入れてみました。アルゴリズムは注目画素に対して、周辺5x5の平均値をその画素としています。

OpenCVでの記述

ここはOpenCVを使ってさくっと書きます。

def run_opencv():
    src = cv2.imread('../../image/twittan/twittan.jpg')
    dst = src.copy()

    roi_x = 100
    roi_y = 100
    roi_w = 150
    roi_h = 200

    dst[roi_y:roi_y + roi_h, roi_x:roi_x + roi_w] = cv2.blur(src[roi_y:roi_y + roi_h, roi_x:roi_x + roi_w], (5, 5),
                                                             (-1, -1))
    print("image size:width = %d, height = %d" % (dst.shape[1], dst.shape[0]))

    cv2.imwrite('twi_blur_cv.jpg', dst)

入力画像

twi.jpg

出力画像

すこし顔面にぼかしが入ってます。
twi_blur_cv.jpg

MyHDLでの記述

ファイル構成

今回は5つのファイルを使っています。
- smooth.py OpenCVの処理を含む全体のTOPです。python smooty.py でSim実行します。
- myhdl_top.py MyHDL環境のTOPです。Clk, Resetが含まれます。
- smooth_hdl.py RTLが記載されています。
- mem.py MyHDLのシミュレーション用メモリモデルです。
- reg_driver.py レジスタ設定を行います。

テストベンチ側

テストベンチ側の記述です。

CLK, Reset等

サンプルとほぼ同じなので省略

レジスタ設定

正論理のリセットが1clkだけ来るので、それを待ってレジスタ設定を行っています。その後、startレジスタを1clkだけ1にし処理を開始、endレジスタが1になるのを待って処理終了です。 yield clk.posedge がwait posedge(clk); に相当します。

reg_driver.py
# -*- coding: utf-8 -*-
__author__ = 'natu'
from myhdl import *

def reg_driver_top(
        clk, reset,
        reg_start, reg_end,
        reg_width, reg_height,
        reg_roi_x, reg_roi_y, reg_roi_h, reg_roi_w
        ):

    @instance
    def regDriver():
        while reset == 0:
            yield clk.posedge
        while reset == 1:
            yield clk.posedge

        reg_width.next = 358
        reg_height.next = 557
        reg_roi_x.next = 100
        reg_roi_y.next = 100
        reg_roi_h.next = 200
        reg_roi_w.next = 150
        yield clk.posedge

        reg_start.next = 1
        yield clk.posedge
        reg_start.next = 0
        yield clk.posedge

        while reg_end == 0:
            yield clk.posedge

        print("end == 1")
        yield clk.posedge

    return regDriver

メモリ

ここはPythonのパワーが発揮できるところでした。OpenCVのimreadを使ってjpegファイルを直接オープンしてSimに使用できます。Hexダンプして、readmemh を使わなくても大丈夫です。出力も同様にOpenCVで直接画像に落とせます。

read側は組み合わせ回路でradrが変化したらread_r, read_g, read_bを更新、write側はclk同期でwenが1の時メモリに書き込みます。

mem.py
# -*- coding: utf-8 -*-
__author__ = 'natu'
from myhdl import *
import numpy
import cv2

dst = None

def mem_top(
        clk, reset,
        read_r, read_g, read_b, radr,
        write_r, write_g, write_b, wadr, wen):
    global dst

    src = cv2.imread('../../image/twittan/twittan.jpg')
    dst = numpy.zeros(src.shape)

    @always_comb
    def mem_read():
        x, y = adr_dec(radr)
        read_r.next = clop_8bit(src[y][x][0])
        read_g.next = clop_8bit(src[y][x][1])
        read_b.next = clop_8bit(src[y][x][2])


    @instance
    def mem_write():
        while True:
            if wen == 1:
                x, y = adr_dec(wadr)
                dst[y][x][0] = write_r
                dst[y][x][1] = write_g
                dst[y][x][2] = write_b
            yield clk.posedge

    return mem_read, mem_write

def write_image():
    cv2.imwrite('twi_blur_rtl.jpg', dst)

def adr_dec(adr):
    width = dst.shape[1]
    x = int(adr) % width
    y = int(adr) / width
    return x, y

def clop_8bit(x):
    if x >= 255:
        return 255

    return int(x)

RTL側

ステートマシーンはサンプルにあったのでとりあえず入れてますが、あまり重要な動きはしていません。x, yの処理をするのに、forで回せるのが便利ですね。

この2重ループで、注目画素の周辺5x5を取得しています。

      for ry in range(-2,3):
          for rx in range(-2,3):

sum_r, sum_g, sum_bに値を足しこんでいって、//25で平均値を計算しています。これだけで合成してくれたらすごく便利ですね。

以下、全ソース

smooth_hdl.py
# -*- coding: utf-8 -*-
__author__ = 'natu'
from myhdl import *

t_State = enum('IDLE', 'RUNNING')

def smoother_top(
        clk, reset,
        rin, gin, bin, radr,
        rout, gout, bout, wadr, wen,
        reg_start, reg_end,
        reg_width, reg_height,
        reg_roi_x, reg_roi_y, reg_roi_h, reg_roi_w
    ):

    state = Signal(t_State.IDLE)

    @instance
    def main_proc():
        while 1:
            if state == t_State.RUNNING:
                for y in range(reg_height):
                    print("y = %d" % y)
                    for x in range(reg_width):
                        if reg_roi_x <= x and x < reg_roi_x + reg_roi_w and reg_roi_y <= y and y < reg_roi_y + reg_roi_h:
                            # ROI
                            sum_r = 0
                            sum_g = 0
                            sum_b = 0
                            for ry in range(-2,3):
                                for rx in range(-2,3):
                                    radr.next = adr(x + rx, y + ry)
                                    yield  clk.posedge
                                    sum_r = sum_r + rin
                                    sum_g = sum_g + gin
                                    sum_b = sum_b + bin
                                    yield  clk.posedge
                            wadr.next = adr(x, y)
                            rout.next = sum_r // 25
                            gout.next = sum_g // 25
                            bout.next = sum_b // 25
                            wen.next = 1
                            yield  clk.posedge
                            wen.next = 0
                        else:
                            radr.next = adr(x, y)
                            yield  clk.posedge
                            wadr.next = adr(x, y)
                            rout.next = rin
                            gout.next = gin
                            bout.next = bin
                            wen.next = 1
                            yield  clk.posedge
                            wen.next = 0
                reg_end.next = 1
                yield  clk.posedge

            yield  clk.posedge


    def adr(x, y):
        return y * reg_width + x

    @always_seq(clk.posedge, reset=reset)
    def fsm():
        if state == t_State.IDLE:
            if reg_start == 1:
                state.next = t_State.RUNNING
        elif state == t_State.RUNNING:
            if reg_end == 1:
                state.next = t_State.IDLE
        else:
            raise ValueError("Undefined state")

    return fsm, main_proc

処理結果

MyHDLによる出力画像

OpenCVの結果とほぼ同じです。

twi_blur_rtl.jpg

vcd出力

vcdも出力できるので、gtkwaveで波形が確認できます。思ったとおりに動いてます。
gtkwave.png

Verilogへの変換

変換方法

Verilogへの変換も、toVerilog関数を使うと一発です。
while 1:はOKなのにwhile Trueは駄目とか、微妙な壁はありましたが、この記述でVerilogできました。

    toVerilog(smoother_top,
        clk, reset,
        rin, gin, bin, radr,
        rout, gout, bout, wadr, wen,
        reg_start, reg_end,
        reg_width, reg_height,
        reg_roi_x, reg_roi_y, reg_roi_h, reg_roi_w
    )

変換結果

わくわくしますね。

// File: smoother_top.v
// Generated by MyHDL 0.9.dev0
// Date: Tue May 19 14:30:05 2015


`timescale 1ns/10ps

module smoother_top (
    clk,
    reset,
    rin,
    gin,
    bin,
    radr,
    rout,
    gout,
    bout,
    wadr,
    wen,
    reg_start,
    reg_end,
    reg_width,
    reg_height,
    reg_roi_x,
    reg_roi_y,
    reg_roi_h,
    reg_roi_w
);


input clk;
input reset;
input [7:0] rin;
input [7:0] gin;
input [7:0] bin;
output [19:0] radr;
reg [19:0] radr;
output [7:0] rout;
reg [7:0] rout;
output [7:0] gout;
reg [7:0] gout;
output [7:0] bout;
reg [7:0] bout;
output [19:0] wadr;
reg [19:0] wadr;
output wen;
reg wen;
input reg_start;
output reg_end;
reg reg_end;
input [9:0] reg_width;
input [9:0] reg_height;
input [9:0] reg_roi_x;
input [9:0] reg_roi_y;
input [9:0] reg_roi_h;
input [9:0] reg_roi_w;

reg [0:0] state;




function integer MYHDL13_adr;
    input x;
    integer x;
    input y;
    integer y;
begin: MYHDL17_RETURN
    MYHDL13_adr = ((y * $signed({1'b0, reg_width})) + x);
    disable MYHDL17_RETURN;
end
endfunction

function integer MYHDL14_adr;
    input x;
    integer x;
    input y;
    integer y;
begin: MYHDL18_RETURN
    MYHDL14_adr = ((y * $signed({1'b0, reg_width})) + x);
    disable MYHDL18_RETURN;
end
endfunction

function integer MYHDL15_adr;
    input x;
    integer x;
    input y;
    integer y;
begin: MYHDL19_RETURN
    MYHDL15_adr = ((y * $signed({1'b0, reg_width})) + x);
    disable MYHDL19_RETURN;
end
endfunction

function integer MYHDL16_adr;
    input x;
    integer x;
    input y;
    integer y;
begin: MYHDL20_RETURN
    MYHDL16_adr = ((y * $signed({1'b0, reg_width})) + x);
    disable MYHDL20_RETURN;
end
endfunction


always @(posedge clk, posedge reset) begin: SMOOTHER_TOP_FSM
    if (reset == 1) begin
        state <= 1'b0;
    end
    else begin
        case (state)
            1'b0: begin
                if ((reg_start == 1)) begin
                    state <= 1'b1;
                end
            end
            1'b1: begin
                if ((reg_end == 1)) begin
                    state <= 1'b0;
                end
            end
            default: begin
                $finish;
            end
        endcase
    end
end


initial begin: SMOOTHER_TOP_MAIN_PROC
    integer sum_b;
    integer rx;
    integer ry;
    integer sum_g;
    integer y;
    integer x;
    integer sum_r;
    while (1) begin
        if ((state == 1'b1)) begin
            for (y=0; y<reg_height; y=y+1) begin
                $write("y = ");
                $write("%0d", y);
                $write("\n");
                for (x=0; x<reg_width; x=x+1) begin
                    if ((($signed({1'b0, reg_roi_x}) <= x) && (x < (reg_roi_x + reg_roi_w)) && ($signed({1'b0, reg_roi_y}) <= y) && (y < (reg_roi_y + reg_roi_h)))) begin
                        sum_r = 0;
                        sum_g = 0;
                        sum_b = 0;
                        for (ry=(-2); ry<3; ry=ry+1) begin
                            for (rx=(-2); rx<3; rx=rx+1) begin
                                radr <= MYHDL13_adr((x + rx), (y + ry));
                                @(posedge clk);
                                sum_r = (sum_r + rin);
                                sum_g = (sum_g + gin);
                                sum_b = (sum_b + bin);
                                @(posedge clk);
                            end
                        end
                        wadr <= MYHDL14_adr(x, y);
                        rout <= (sum_r / 25);
                        gout <= (sum_g / 25);
                        bout <= (sum_b / 25);
                        wen <= 1;
                        @(posedge clk);
                        wen <= 0;
                    end
                    else begin
                        radr <= MYHDL15_adr(x, y);
                        @(posedge clk);
                        wadr <= MYHDL16_adr(x, y);
                        rout <= rin;
                        gout <= gin;
                        bout <= bin;
                        wen <= 1;
                        @(posedge clk);
                        wen <= 0;
                    end
                end
            end
            reg_end <= 1;
            @(posedge clk);
        end
        @(posedge clk);
    end
end

endmodule

initial begin: SMOOTHER_TOP_MAIN_PROC、、、

(つд⊂)ゴシゴシ→(;゚ Д゚) …!?

_人人人人人人人人_
> initial begin: <
 ̄Y^Y^Y^Y^Y^Y^ ̄

合成できない!
だめじゃん><

感想

良かったところ

  • PyCharmが便利。未使用の信号を教えてくれたり、スコープを理解した変数の一括置換など。ただ、デフォルトの状態だと警告が多すぎて使いにくい。やれ、改行が多いだの少ないだの、ここにスペースがいるだのいらんだの、ちょっと辛くなってしまった。
  • Pythonのライブラリが便利。

アサーションとかカバレッジも挑戦したかったけど、自分の作った回路が合成できないと知ってがっくしきてしまい、そこまで踏み込めず。

駄目だったところ

  • 動く回路を書ききる難易度が高い。回路の動きを記載した関数を返すというのが直感的ではない。デバッグしにくい。
  • エラーが分かりにくい。Pythonの構文ツリーを直接見ているのか、エラーメッセージが長くC++のような絶望感がある。 sum_r = 0 を間違えて sum_r == 0 とするだけで67行のエラーメッセージが出てくる。
  • 合成できる記述が非常に限られている。今回の回路でも全体は無限ループ、waitが入るのは全てclkの立ち上がりなので、これは合成可能なVerilogに置き換えてくれるだろうと思って書いていたが、この記述だと駄目でした。
    @always_seq(clk.posedge, reset=reset)
    def fsm():
        if state == t_State.IDLE:
            if reg_start == 1:
                state.next = t_State.RUNNING
        elif state == t_State.RUNNING:
            if reg_end == 1:
                state.next = t_State.IDLE
        else:
            raise ValueError("Undefined state")

合成可能な記述はこのパターンで、途中にyieldが入ってこないジェネレータを書ききらないといけない。つまりVerilog-HDLと同じ。あくまでモデリングの言語ですね。

10
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
10