Advertisement

基于FPGA的数字视频信号处理器设计(中)

阅读量:

我们今天将向大侠介绍基于FPGA的数字视频信号处理器设计。因为内容较为丰富,故分为三个部分。在本次系列中是第二部分,涵盖视频信号的基本概述及其处理架构,请期待发布。

以往也有与图像处理相关的文章,在这里超链接一些供各位大侠作参考。

《冈萨雷斯数字图像处理MATLAB版》中文版(第二版) 电子版

值此机会,《FPGA设计经验之图像处理》这篇文章值得一看。它深入探讨了FPGA在图像处理中的应用技巧和优化方法,并结合实际案例展示了硬件实现的具体方案。文章内容详实且具有实用价值

以FPGA为平台实现实时图像边缘检测系统的硬件设计(下)

基于FPGA的数字设计领域中采用Verilog HDL实现基础的图像滤波处理仿真研究或实验验证

导读

图像是一种通过多种观测手段对客观世界进行观察并获取信息的方式;这些信息既可以是直接作用于感官系统的事物本身(即实体),也可以经由中间媒介(如其他感官或技术辅助)传递信息而被感知。

伴随着电子技术和计算机技术的迅速发展,在科学研究、工业生产、医疗卫生以及通信等领域都得到了广泛应用,并且数字图像技术近年来获得了极大的关注和显著的进步。

视频信号由连续的图像序列构成。视频信号的运算在数字图像处理领域中扮演着重要角色。例如,在机器人模式识别的过程中就是一个典型的视频信号运算过程;电视制导导弹的目标识别技术则是充分利用了视频信号运算技术不断优化与预设目标图像匹配度的方法之一。本篇文章将介绍如何借助FPGA技术实现基本的视频信号运算功能;本篇文章中的实例可作为各位专家在实际进行视频信号处理时的重要参考依据;并且可以在其基础上根据具体需求进行相应的拓展研究。

本篇将阐述视频信号处理相关的电路构成及其工作原理, 包括主控制器FPGA、电视信号转换模块硬件配置以及图像缓存相关硬件组件等主要组成部分;具体阐述了视频处理相关的软件功能实现方案, 主要涵盖了核心功能模块的设计与实现方案, 视频图像数据采集相关的数据流程设计, 以及SRAM的数据读写控制机制及相关技术要点等关键内容。

三、视频信号处理的电路

下面将详细介绍视频转换模块和视频数据计算模块的电路。

3.1 中央控制器 FPGA

FPGA 芯片作为中央控制器控制整个视频信号的处理,如图 6 所示。

图 6 FPGA 实现的主要功能

FPGA 芯片实现的主要功能如下:

• 提供电源管理逻辑。

• 提供系统状态指示灯的管理,这些指示灯用来显示当前各个部分的状态。

负责视频数据的处理管理。当启动采样过程时,在接收到第一个同步信号后启动采样流程,并将其存储在SRAM中。完成采样后立即进入处理阶段,在此期间随后立即进行下一张图像的采集,并将其存储在另一块SRAM中。

• 模拟 I2C 接口,完成对 SAA7113 参数的配置。

3.2 电视信号转换模块电路

电视信号转换模块的电路设计如图 7 所示。

图 7 电视信号转换模块电路设计图

其中 SAA7113 内建的时钟信号由一个 24.576MHz 晶体振荡器生成,并在此基础上生成内部所需的关键频率域参数:主系统时钟(Frequency: 27 MHz)及其二次分频器输出(Frequency: 13.5 MHz),其中二次分频器输出用于实现对图像采集模块的精确同步。如图8所示。

图 8 一个 LLC2 周期采集一个像素数据

SAA7113系统内部集成了模拟电路和数字电路两大模块。其中,模拟电路模块不仅能够放大电视信号,并且具备抗混叠滤波功能;而数字处理模块则负责对经模数转换后的图像数据进行各项参数的详细处理工作。

1)输出数据的格式

相机传递的图像信号经由 RCA-JACK 插座连接至 SAA7113,在执行 A/D 转换及其他必要步骤后生成符合数字标准的图像数据。该系统支持以下数据格式:

(1)标准的 ITU 656 YUV 4:2:2(8 位)格式的数据;

(2)增强的 ITU 656 标准格式的各种数据,如 active video、raw CVBS 等。

该ITU 656 规范由国际电信同盟(ITU)建议作为数字视频数据格式的标准。基于ITU 656 YUV 4:2:2 标准构建的数字电视信号如图9所示。

图 9 SAA7113 输出数字视频信号数据格式

YUV(别名:YCrCb)是欧洲电视系统所采用的一种色彩编码方案(属于PAL制)。该方案的主要目的是提高彩色视频信号的传输效率,并使传输过程所需带宽减少的同时实现 backwards compatibility with older black-and-white TVs. 如果直接使用RGB视频信号进行传输,则需要同时传送R、G、B三个独立的视频通道,在频宽上会远为占据更多资源. 在YUV体系中,“Y”代表亮度值(Luminance或Luma),即灰度级数值;而U和V则代表色度值(Chrominance或Chroma),用于描述图像的颜色信息及其饱和度. 光亮值是由RGB输入信号所生成的. 色度则具体包含了色调与饱和度两个方面信息,并分别以Cr和Cb来表示. 这里 Cr反映了RGB输入信号中红色分量与其亮度值之间的差异性; Cb则反映了蓝色分量与其亮度值之间的差异性.

从 SAA7113 数字视频信号输出总线 VPO 输出的数据格式如图 9 所示。

"84:16标识当前 video 数据正处于 row cancellation 状态中。
其中 'SAR' 视频同步相关编码参数用于同步相关编码过程,
而 'YYYR' 则用于同步解码过程,
两者均需在主控制单元实现,
以确保各参数间的正确同步与配合运行。",
"YYRR:YYYR"参数组用于实现主控制单元与各子通道间的同步协调工作。",
"YYYC:CYYR"参数组用于实现主控制单元与各子通道间的同步协调工作。",
...]",
...]",
...]",
...]",
"YYRR:CYYR"参数组用于实现主控制单元与各子通道间的同步协调工作。\n"

用下面公式可以把传输用的 ITU 656 YUV 格式的数据还原为需要的 RGB 格式:

2)SAA7113 的配置

使用FPGA模拟实现I2C接口的功能,并能够读取和输出SAA7113提供的多组控制字信息;这样不仅能够全面控制SAA7113的状态

图 10 对 SAA7113 控制字的写操作

在I2C协议中,在I2C协议中每个器件都有独特的7位二进制地址码,在I2C协议中每个器件都有独特的7位二进制地址码

SAA7113 读控制字的过程如图 11 所示。

图 11 对 SAA7113 控制的读操作

在I2C协议中进行读操作相较于写操作更为复杂,在具体实现过程中需要遵循以下步骤:首先是依次完成对目标地址及其起始点的设置;接着在目标地址上加载所需的数据块并在末尾附加一个校验位之后启动数据传输过程;随后等待数据块的所有位移位到位并进行奇偶校验验证之后才允许接收方开始解码运算;最后当数据传输完成后立即发出终止指令以释放相关资源

整个 SAA7113 配置的流程如图 12 所示。

图 12 SAA7113 配置流程

采用 I2C 程序进行 SAA7113 的配置设置。具体操作可参考以往的 I2C 相关介绍,请注意后续计划补充模拟I2C 配置程序的实例。目前提供一个相关链接供参考。

来源

来源

3.3 图像缓存部分电路

FPGA 负责将采集到的图像数据存储在缓存中,并为此提供后续处理所需的数据。针对图像缓存的设计方案如下:使用两个CY7C1049型SRAM芯片作为缓存存储模块。 FPGA 通过SAA7113接收并立即存储一帧图像数据于SRAM中,并且其中后端处理器组(如DSP等)能够从另一块SRAM中读取数据开展后续处理工作。电路原理图见图13所示。

图 13 SRAM 的电路图

在首次采样阶段,在首次采样阶段

四、视频处理程序的具体实现

4.1 主体程序的实现

FPGA 整体控制程序的流程如图 14 所示。

图 14 FPGA 整体控制程序流程图

整体控制过程中有关状态机的代码如下:

复制代码
    //状态机always @(posedge clk or negedge BRD_RST_)    //缺省状态    if (!BRD_RST_)        begin            presState <= stIdle;            capture <= 1'b0;            toggle <= 1'b1;            dsprst <= 1'b0;            EXT_INT4 <= 1'b0;        end    else        begin            case (presState)              //初始状态              stIdle:                  //DSP 初始化完成                  if(DSP_INIT_FLAG)                      begin                          presState <= stWaitforDspInit;                      end                  else                          presState <= stIdle;              //等待 SAA7113 初始化                stWaitforDspInit:                    if(setup_flag && !saareset)                        begin                            capture <= 1'b1;                            presState <= stStartGrabData;                        end                    else                        begin                            presState <= stWaitforDspInit;                        end            //开始采集数据                stStartGrabData://                    if(!SAA_DATA_PRE)                        begin                            presState <= stToggleBus;                            capture <= 1'b0;                            EXT_INT4 <= 1'b0;                        end                    else                        presState <= stStartGrabData;            //切换总线                stToggleBus://                    if(!DSP_DATA_PRE)                        begin                            toggle <= !toggle;                            presState <= stStartGrabData;                            capture <= 1'b1;                            //外部中断给 DSP                            EXT_INT4 <= 1'b1;                        end                    else                        presState <= stToggleBus;            endcase        end
    
    代码解读

4.2 视频图像数据采集程序的实现

SAA7113输出的视频图像数据经由8位总线VPO传输至FPGA,并以确保数据得以保存于SRAM中。鉴于PAL制电视信号采用隔行扫描方式,在传输出现奇偶场后进行数字化处理时,则呈现出与原始格式一致的特点;因此,在将数据存入SRAM之前,则需将奇数场与偶数场的数据重构为完整的一幅图像。

当输入信号为 YUV 格式时,
应将其转换为适合 DSP 处理的 RGB 彩色空间。
FPGA 应执行以下操作:

(1)将所有按行排列的 8 位数据还原成一幅完整图像;

(2)根据需要进行格式转换。

SAA7113 输出的数据格式如图 15 所示。

图 15 SAA7113 输出视频数据格式

SAV 和 EAV 分别被称为起始位置(Start of Active Video)和结束位置(End of Active Video)。SAA7113 标准明确了 SAV 和 EAV 的数据格式规范(见表 1)。

表 1 SAV 和 EAV 数据格式

依据表 1 的数据可以看出,在完整的单帧图像中,在第一阶段(即场消隐环节)的SAV值定位在'101XXXXX'位置,在随后的第一有效数据阶段SAV值则定位于'1000XXXX'位置。其中符号'X'代表该位的状态信息无实际意义。对于其余各场次的SAV与EAV状态,则依此类推进行分析。

进行视频数据处理的流程如图 16 所示。

图 16 视频数据处理流程图

数字视频数据处理的主要代码如下:

复制代码
    //SAA7113 输出的 27MHz 的时钟信号always @(posedge llck or negedge reset)    //设置 reset 后的缺省状态    if (!reset)        begin            //下一状态            presState <= stIdle;            //采集标志            grab <= 1'b0;            //和下一状态相关的返回状态            returnState <= stIdle;            //场标志            field <= 1'b0;            //像素计数器和行计数器清零            grab_cntr_hori <= 1'b0;            grab_cntr_vert <= 1'b0;        end    else        //缺省状态        begin            //保存视频数据            vpoLatch <= vpo;                        //进入下一状态            presState <= nextState;                       //用来设置延时            grab <= nextGrab;//delay so colour can be calculated                        //设置和下一状态相关的返回状态            returnState <= nextReturnState;                        //保存场信号            field <= nextField;                        //保存各个数据分量            chrominanceR <= nextChrominanceR;            chrominanceB <= nextChrominanceB;            luminanceR <= nextLuminanceR;            luminanceB <= nextLuminanceB;                        //如果下一个状态是获取数据的状态,准备写数据到 SRAM 的操作            if (nextGrab)                //给予写地址一些建立时间                write_addr <= grab_addr;                            //如果当前状态就是获取数据,开始写数据到 SRAM 的操作            if (grab)                begin                    //由于是对黑白图像进行处理,只需要获取亮度信息即可;如果需要对彩色图像进行处理,可以在这里加入保存彩色图像数据的代码                    writeData <= luminanceR;                    write <= 1'b1;                end            else                write <= 1'b0;                            // 像素计数和行计数            //开始计数            if (clr_grab_cntr)                begin                    grab_cntr_hori <= 1'b0;                    grab_cntr_vert <= 1'b0;                end            else                //像素计数                if (inc_grab_hori == 1)                    grab_cntr_hori <= grab_cntr_hori + 1;                //行计数                if (inc_grab_vert == 1)                    begin                        grab_cntr_vert <= grab_cntr_vert + 1;                        grab_cntr_hori <= 1'b0; // clear horizontal counter                        with each new line                    end            end                //从 vpo 总线获得数据always @(presState or vpoLatch or returnState or field or luminanceB or luminanceRor chrominanceB or chrominanceR or capture)    begin        //缺省信号值        clr_grab_cntr <= 1'b0;        inc_grab_hori <= 1'b0;        inc_grab_vert <= 1'b0;        nextGrab <= 1'b0;        nextReturnState <= returnState;        nextField <= field;        nextLuminanceB <= luminanceB;        nextLuminanceR <= luminanceR;        nextChrominanceB <= chrominanceB;        nextChrominanceR <= chrominanceR;        error <= 1'b0;        //状态机        case (presState)            stIdle://                //确认开始获取数据                if (capture == 1'b1)                    begin                        //下一状态                        nextState <= stWaitForEscape;                        //下一状态的返回状态                        nextReturnState <= stCheckForNewPage;                    end                else                    nextState <= stIdle;                                    //等待时间参考代码的开始                //等待“FF”                stWaitForEscape:                    if (vpoLatch == 8'hFF)                        nextState <= stCheckEscape1;                    else                        nextState <= stWaitForEscape;                                        //等待“00”                stCheckEscape1:                    if (vpoLatch == 8'h00)                        nextState <= stCheckEscape2;                    else                        nextState <= stError;                                        //等待“00”                stCheckEscape2:                    if (vpoLatch == 8'h00)                        nextState <= returnState;                    else                        nextState <= stError;                                //根据 SAV 和 EAV 内容判断,进行下一步操作                stCheckForNewPage:                //开始获取新的一行                    if (vpoLatch[6:5] == 2'b01)                        begin                            nextState <= stWaitForEscape;                            nextReturnState <= stCheckForFirstLine;                            //初始化计数器                            clr_grab_cntr <= 1'b1;                        end                    //重新开始                    else                        begin                            nextState <= stWaitForEscape;                            nextReturnState <= stCheckForNewPage;                        end                                        //根据 SAV 和 EAV 的内容进行下一步操作                stCheckForFirstLine:                    //开始接收数据                    if (vpoLatch[6:4] == 3'b000)                        begin                            //接收 Cb 数据                            nextState <= stChromaBlue;                            //初始化场记录标志                            nextField <= 1'b0;                        end                    //继续等待                    else                        begin                            nextState <= stWaitForEscape;                            nextReturnState <= stCheckForFirstLine;                        end                                        //记录 Cb 数据              stChromaBlue:                  //如果数据是“FF”,说明是 EAV 的开始                  if (vpoLatch == 8'hFF)                      begin                          //下一状态开始等待“00”                          nextState <= stCheckEscape1;                          //检查是否为当前一场数据的最后一行                          nextReturnState <= stCheckForEndLine;//Check if this  is the last line of the field                      end                  //如果是“00”,状态有错                  else if (vpoLatch == 8'h00)                      nextState <= stError;                  //记录数据,并进入后面的过程                  else                      begin                          nextState <= stLumaBlue;                          nextChrominanceB <= vpoLatch;                      end                                  //记录 Yb 数据            stLumaBlue:                //如果数据不等于“FF”和“00”,数据有效                if ((vpoLatch !== 8'hFF) && (vpoLatch !== 8'h00))                    begin                        nextState <= stChromaRed;                        nextLuminanceB <= vpoLatch;                    end                //错误处理                else                    nextState <= stError;                        //记录 Cr 数据            stChromaRed:                //如果不等于“FF”和“00”,数据有效                if ((vpoLatch !== 8'hFF) && (vpoLatch !== 8'h00))                    begin                        nextState <= stLumaRed;                        nextChrominanceR <= vpoLatch;                    end                //错误处理                else                    nextState <= stError;                                //记录 Yr 数据            stLumaRed:                //如果不等于“FF”和“00”,数据有效                if ((vpoLatch !== 8'hFF) && (vpoLatch !== 8'h00))                    begin                        nextState <= stChromaBlue;                        nextLuminanceR <= vpoLatch;                        nextGrab <= 1'b1;//Set up a write after a delay (see clocked process)                        inc_grab_hori <= 1'b1;//Increment horizontal counter every two pixels                    end                //错误处理                else                  nextState <= stError;                                //检查是否为一行的结束              stCheckForEndLine://possible conditions here are the end of field 0,end of field 1,or an EAV code indicating a new line in the active region.                  //等于“111”说明为第二场的结束                  if (vpoLatch[6:4] == 3'b111)                      begin                          nextState <= stIdle;                      end                                       //等于“011”说明为第一场的结束                else if (vpoLatch[6:4] == 3'b011) //end of field 0                    begin                        //清空计数器,为接收第二场数据做好准备                        clr_grab_cntr <= 1'b1;                        nextState <= stWaitForEscape;                        //检查新的一行开始                        nextReturnState <= stCheckForNewLine;                    end                                    //一行数据结束                else if (vpoLatch[5:4] == 2'b01)                    begin                        //下一行的开始                        inc_grab_vert <= 1'b1;                        nextState <= stWaitForEscape;                        //接收下一行数据                        nextReturnState <= stCheckForNewLine;                    end                //错误处理                else                    nextState <= stError;​            //新的一行数据开始            stCheckForNewLine:                if (vpoLatch[5:4] == 2'b00)                    begin                        //接收 Cb 数据                        nextState <= stChromaBlue;                        nextField <= vpoLatch[6];                    end                //重新开始接收数据                else                    begin                        nextState <= stWaitForEscape;                        nextReturnState <= stCheckForNewLine;                    end                        //错误状态            stError:                //重新开始接收数据                if (capture == 1'b1)                    begin                        nextState <= stWaitForEscape;                        nextReturnState <= stCheckForNewPage;                    end                    //继续等待                    else                        begin                            nextState <= stError;                            //错误提示,二极管亮                            error <= 1'b1;                        end            //缺省状态            default:                nextState <= stError;        endcase    end                  
    
    代码解读

4.3 SRAM 的读写控制

SRAM在完成读写操作时必须遵循严格的时序规范;通过WE、OE及CE三种信号进行精确控制来实现数据写入过程;详细的时间流程图见图17

图 17 SRAM 的写时序

具体流程如下:首先生成并持续地OE 地址信号;随后将片选信号CE设为低电平;与此同时将输出有效信号OE 设为高电平;接着随后将写有效信号WE 设为低电平;并启动数据写入过程。
用于控制SRAM进行数据 writes 的程序流程如下所述:

复制代码
    //写操作发生在当前状态为 stWrite1 并且处于时钟的低电平时//第一个时钟周期里开始一个写操作,OE 信号置为高电平。这是为了//使 SRAM 停止驱动双向数据线,使其数据线处于高阻态。//在等待半个时钟周期后,FPGA 可以驱动 SRAM 的数据线,开始写数据//写数据的状态机always @(writeDataReg or presState or clk)    if (((presState == stWrite1) && (clk == 1'b0)) ||(presState == stWrite2))        SRAM_DATA <= writeDataReg;    else        SRAM_DATA <= 8'hzz;        //输出 WE 信号的状态机always @(presState or clk)    if (((presState == stWrite1) && (clk == 1'b0)) || ((presState == stWrite2) &&(clk == 1'b1)))        SRAM_WE_ <= 1'b0;    else        SRAM_WE_ <= 1'b1;    //获得写操作地址和数据的状态机always @(posedge clk or negedge reset)    //缺省状态    if(!reset)        begin            presState <= stIdle;            SRAM_CE_ <= 1'b1;            SRAM_ADDR <= 8'h00;            writeDataReg <= 8'h00;        end    else        begin            SRAM_CE_ <= 1'b0;            //获得地址            if (regWriteAddr == 1'b1 ) //Handle the clock-enabling of each register                SRAM_ADDR <= writeAddr;            //获得数据内容                  if (regWriteData == 1'b1) //Handle the clock-enabling of each register:                  writeDataReg <= writeData;                presState <= nextState;        end    //完成写操作的状态机always @(presState or doWrite)    begin        case (presState)            //等待状态            stIdle://                begin                    SRAM_OE_ <= 1'b0;                    regWriteAddr <= 1'b0;                    regWriteData <= 1'b0;                    nextState <= stIdle;                    //如果开始写数据                    if (doWrite == 1'b1)                        begin                            nextState <= stWrite1;                            regWriteAddr <= 1'b1;                            regWriteData <= 1'b1;                        end                end            //这个状态完成 OE 的输出            stWrite1://                begin                    nextState <= stWrite2;                    SRAM_OE_ <= 1'b1;                end            //这个状态完成写操作            stWrite2://                begin                    nextState <= stIdle;                    if(doWrite == 1'b1)                        begin                            nextState <= stWrite1;                            regWriteAddr <= 1'b1;                            regWriteData <= 1'b1;                        end                    SRAM_OE_ <= 1'b1;            end        endcase    end       
    
    代码解读

系统中的两组 SRAM 分别由 DSP 和 FPGA 进行控制。在完成相应的 SRAM 操作后,在完成相应的 SRAM 操作后,在完成相应的 SRAM 操作之后,在完成相应的 SRAM 操作完成后,在完成相应的 SRAM 操作之后,在完成相应的 SRAM 操作完成后,在完成相应的 SRAM 操作之后,在完成相应的 SRAM 操作完成后,在完成相应的 SRAM 操作之后,在完成相应的 SRAM 操作完成后,
切换至另一条总线上后,
DSP 和 FPGA 重新对另一组
SR
RAM

行相
应操
作。
主要代码如下:

复制代码
    //控制信号 toggle =1,C6711 的 EMIF 连接到 SRAM1,从 SRAM1 中读取视频数据    //控制信号 toggle =0 时,切换到 SRAM2    assign ED_SRAM = toggle ? SRAM_1_IN_ED : SRAM_2_IN_ED;    assign SRAM_1_O_ED = toggle ? 8'hzz : ld;    assign SRAM_1_OEN = toggle ? 1'b0 : 1'b1;    assign SRAM_1_EA = toggle ? EA_SRAM : la;    assign SRAM_1_CE_ = toggle ? CE_SRAM : SRAM_CE_;//toggle =1 ,output dsp to sram1;0,output saa7113 to sram1    assign SRAM_1_OE_ = toggle ? OE_SRAM : SRAM_OE_;    assign SRAM_1_WE_ = toggle ? WE_SRAM : SRAM_WE_;    //控制信号 toggle =1,SAA7113 连接到 SRAM2,SAA7113 写数据到 SRAM2    //控制信号 toggle =0 时,切换到 SRAM1    assign SRAM_2_O_ED = toggle ? ld : 8'hzz;    assign SRAM_2_OEN = toggle ? 1'b1 : 1'b0;    assign SRAM_2_EA = toggle ? la : EA_SRAM;    assign SRAM_2_CE_ = toggle ? SRAM_CE_: CE_SRAM ;//toggle =1 ,output SAA7113 to sram1;0,output DSP to sram1    assign SRAM_2_OE_ = toggle ? SRAM_OE_: OE_SRAM ;    assign SRAM_2_WE_ = toggle ? SRAM_WE_: WE_SRAM ;
    
    代码解读

**

**

这篇到这里就结束了。下篇文章将采用FPGA架构设计数字视频信号处理器(下),详细介绍程序测试及运行流程。该内容将涵盖测试程序、测试结果分析以及总结等内容。

END

后续会不断更新,并为各位提供包括但不限于Vivado、ISE和Quartus II 等安装相关的详细设计教程。同时也会分享学习材料和实践案例,并推荐优质文章以满足您的需求。期待各位持续关注。

大侠们,江湖偌大,继续闯荡,愿一切安好,有缘再见!

精彩推荐

OV7670简体中文说明书

以FPGA技术为基础的单眼内窥装置定位系统的构建(下)

基于 FPGA 的高级技术研究:时序特性评估与收敛性分析

针对FPGA平台设计了一种不采用时钟同步的异步FIFO电路(附完整源代码包)

全部评论 (0)

还没有任何评论哟~