【数字图像处理】水平翻转、垂直翻转

图像翻转是常见的数字图像处理方式,分为水平翻转和垂直翻转。本文主要介绍 FPGA 实现图像翻转的基本思路,以及使用紫光同创 PGL22G 开发板实现数字图像水平翻转、垂直翻转的过程。

目录

1 水平翻转与垂直翻转

2 FPGA 布署与实现

2.1 功能与指标定义

2.2 逻辑设计

2.3 上板调试


1 水平翻转与垂直翻转

        在数字图像处理中,图像翻转是指将图像进行水平或者垂直方向的翻转,使其呈现不同的效果,分为水平翻转和垂直翻转。

        水平翻转是将图像沿着水平轴线进行翻转,将左半部分和右半部分进行交换。垂直翻转则是将图像沿着垂直轴线进行翻转,将上部分和下部分进行交换。

        使用 FPGA 实现水平翻转时,由于每次向 DDR3 写入一行数据,因此可以借助写 DDR3 之前的 Dual-port RAM,将一行数据进行翻转,实现水平翻转的功能。FPGA 逻辑设计的大致思路是:写 Dual-port RAM 时,写地址从 0 增加;读 Dual-port RAM 写 DDR3 时,读地址从最大值逐步减小到 0 结束。 

        对于垂直翻转功能,则需要借助 DDR3 来实现。像素数据第 1 行写到第 N 行的位置,第 2 行写到第 N-1 行的位置,以此类推。FPGA 逻辑设计的大致思路是:每一行数据写入 DDR3 时,用图像高度减去原来的行地址,作为新的行地址。

2 FPGA 布署与实现

2.1 功能与指标定义

        使用紫光同创 FPGA 平台实现图像翻转功能,FPGA 需要实现的功能与指标如下:

(1)与电脑的串口通信,用于接收上位机下发的图像数据,波特率为 256000 Bd/s;

(2)水平翻转与图像翻转:借助 Dual-port RAM 与 DDR3,分别实现水平翻转与垂直翻转功能;

(3)DDR3 读写控制,将处理前后的图像数据分别写入 DDR3 的不同区域,实现图像的拼接;

(4)HDMI 输出,输出一路 HDMI 信号源,用于将拼接后的图像显示在外接显示器上,分辨率为 1024×768。

2.2 逻辑设计

        图像翻转工程主要的设计模块层次与功能说明如下:

模块名称功能说明
top_uartuart_rx_slice串口接收驱动模块
uart_rx_parse串口数据解析模块,从上位机接收 8bit 原始图像
top_vidinvidin_pipelinepipeline 单元模块,缓存两行图像数据,并将数据提交到 ddr3 数据调度模块
merge_outdvi_timing_genHDMI 视频时序产生模块
dvi_ddr_rd根据 HDMI 控制信号,提交读指令到 ddr3 数据调度模块
dvi_encoderHDMI 输出编码(8b10b 编码)与输出驱动模块

其中,vidin_pipeline 模块实现图像翻转功能,代码如下:

`timescale 1 ns/ 1 ps`include "../ddr_scheduler/ddr_parameter.vh"module vidin_pipeline (// System levelsys_rst,sys_clk,ddr_init_done,flip_lr,flip_ud,// pipeline input portspipeline_in_info,pipeline_in_data,pipeline_in_wren,pipeline_in_end,// pipeline output portspipeline_out_info,pipeline_out_data,pipeline_out_vld,pipeline_out_end,// User defined ports for ddr_schedulerddr_wr_baseaddr,ddr_wr_addr,ddr_wr_priority,ddr_wr_burstsize,ddr_wr_req,ddr_wr_ack,ddr_wr_end,ddr_wr_rden,ddr_wr_q,ddr_wr_mask
);// IO direction/register definitions
input                     sys_rst;
input                     sys_clk;
input                     ddr_init_done;
input                     flip_lr;
input                     flip_ud;input  [31:0]             pipeline_in_info;
input  [31:0]             pipeline_in_data;
input                     pipeline_in_wren;
input                     pipeline_in_end;output [31:0]             pipeline_out_info;
output [31:0]             pipeline_out_data;
output                    pipeline_out_vld;
output                    pipeline_out_end;input  [`DDR_A_W-1:0]     ddr_wr_baseaddr;
output [`DDR_A_W-1:0]     ddr_wr_addr;
output                    ddr_wr_priority;
output [`DDR_BURST_W-1:0] ddr_wr_burstsize;
output                    ddr_wr_req;
input                     ddr_wr_ack;
input                     ddr_wr_end;
input                     ddr_wr_rden;
output [`DDR_D_W-1:0]     ddr_wr_q;
output [`DDR_D_W/8-1:0]   ddr_wr_mask;reg    [31:0]             pipeline_out_info;
reg    [31:0]             pipeline_out_data;
reg                       pipeline_out_vld;
reg                       pipeline_out_end;// internal signal declarations
reg    [`DDR_CMD_W-1:0]   ddr_cmd_data;
reg                       ddr_cmd_vld;reg    [9:0]              blk_mem_waddr;
reg    [31:0]             blk_mem_wdata;
reg                       blk_mem_wren;
reg    [9:0]              blk_mem_raddr;
wire   [31:0]             blk_mem_rdata;
reg                       blk_mem_rden;
reg                       blk_mem_rd_busy;
reg                       blk_mem_rd_end;
reg                       blk_mem_rd_vld;// line_buffer_inst: Dual-port ram for line pixel data buffer
blk_mem_1024x32b line_buffer_inst (.wr_data         (blk_mem_wdata   ), // input 32-bit.wr_addr         (blk_mem_waddr   ), // input 10-bit.wr_en           (blk_mem_wren    ), // input 1-bit.wr_clk          (sys_clk         ), // input 1-bit.wr_rst          (sys_rst         ), // input 1-bit.rd_addr         (blk_mem_raddr   ), // input 10-bit.rd_data         (blk_mem_rdata   ), // output 32-bit.rd_clk          (sys_clk         ), // input 1-bit.rd_rst          (sys_rst         )  // input 1-bit
);
// End of line_buffer_inst instantiationalways @(posedge sys_rst or posedge sys_clk) beginif (sys_rst) beginblk_mem_waddr <= {10{1'b0}};blk_mem_wdata <= {32{1'b0}};blk_mem_wren  <= 1'b0;endelse beginblk_mem_wdata <= pipeline_in_data;blk_mem_wren  <= pipeline_in_wren;// Use ping-pong storage hereif (pipeline_in_end) blk_mem_waddr <= {~blk_mem_waddr[9], {9{1'b0}}};else if (pipeline_in_wren) blk_mem_waddr <= {blk_mem_waddr[9], blk_mem_waddr[0+:9]+1'b1};end
endalways @(posedge sys_rst or posedge sys_clk) beginif (sys_rst) beginblk_mem_raddr   <= {10{1'b0}};blk_mem_rden    <= 1'b0;blk_mem_rd_busy <= 1'b0;blk_mem_rd_end  <= 1'b0;blk_mem_rd_vld  <= 1'b0;endelse beginif (~blk_mem_rd_busy && pipeline_in_end) beginblk_mem_rd_busy <= 1'b1;if (flip_lr == 1'b0) blk_mem_raddr <= {blk_mem_raddr[9], {9{1'b0}}};elseblk_mem_raddr <= {blk_mem_raddr[9], {9{1'b1}}};endelse if (blk_mem_rd_busy) begin// Use ping-pong storage hereif (flip_lr == 1'b0) beginif (& blk_mem_raddr[0+:9]) blk_mem_raddr <= {~blk_mem_raddr[9], {9{1'b0}}};else blk_mem_raddr <= {blk_mem_raddr[9], blk_mem_raddr[0+:9]+1'b1};endelse beginif (blk_mem_raddr[0+:9] == 0)blk_mem_raddr <= {~blk_mem_raddr[9], {9{1'b1}}};elseblk_mem_raddr <= {blk_mem_raddr[9], blk_mem_raddr[0+:9]-1'b1};end// Pull down read busy flagif (flip_lr == 1'b0) beginif (& blk_mem_raddr[0+:9]) blk_mem_rd_busy <= 1'b0;endelse beginif (blk_mem_raddr[0+:9] == 0)blk_mem_rd_busy <= 1'b0;endendblk_mem_rden <= blk_mem_rd_busy;blk_mem_rd_vld <= blk_mem_rden;if (blk_mem_rd_busy) beginif (flip_lr == 1'b0) beginif (& blk_mem_raddr[0+:9]) blk_mem_rd_end <= 1'b1;else blk_mem_rd_end <= 1'b0;endelse beginif (blk_mem_raddr[0+:9] == 0)blk_mem_rd_end <= 1'b1;else blk_mem_rd_end <= 1'b0;endendelseblk_mem_rd_end <= 1'b0;end
endalways @(posedge sys_rst or posedge sys_clk) beginif (sys_rst) beginpipeline_out_info <= {32{1'b0}};pipeline_out_data <= {32{1'b0}};pipeline_out_vld  <= 1'b0;pipeline_out_end  <= 1'b0;endelse beginif (pipeline_in_end) pipeline_out_info <= pipeline_in_info;pipeline_out_data <= blk_mem_rdata;pipeline_out_vld  <= blk_mem_rd_vld;pipeline_out_end  <= blk_mem_rd_end;end
end/
always @(posedge sys_rst or posedge sys_clk) beginif (sys_rst) beginddr_cmd_data <= {`DDR_CMD_W{1'b0}};ddr_cmd_vld <= 1'b0;endelse beginif (pipeline_in_end) beginddr_cmd_data[32+:`DDR_BURST_W] <= 8'h7F; // used fixed size here, 512 /4 -1 = 127if (flip_ud == 1'b0)ddr_cmd_data[0+:28] <= {pipeline_in_info[0+:16], 12'd0};elseddr_cmd_data[0+:28] <= {16'd383-pipeline_in_info[0+:16], 12'd0};endif (blk_mem_rd_end) ddr_cmd_vld <= 1'b1;else ddr_cmd_vld <= 1'b0;end
end// vid_ddr_wr_inst: ddr write control module
vid_ddr_wr vid_ddr_wr_inst (.sys_rst          (sys_rst           ), // input 1-bit.sys_clk          (sys_clk           ), // input 1-bit.ddr_init_done    (ddr_init_done     ), // input 1-bit.vid_cmd_data     (ddr_cmd_data      ), // input 40-bit.vid_cmd_vld      (ddr_cmd_vld       ), // input 1-bit.vid_img_data     (blk_mem_rdata     ), // input 32-bit.vid_img_data_vld (blk_mem_rd_vld    ), // input 1-bit.ddr_wr_baseaddr  (ddr_wr_baseaddr   ), // input 27-bit.ddr_wr_addr      (ddr_wr_addr       ), // output 27-bit.ddr_wr_priority  (ddr_wr_priority   ), // output 1-bit.ddr_wr_burstsize (ddr_wr_burstsize  ), // output 8-bit.ddr_wr_req       (ddr_wr_req        ), // output 1-bit.ddr_wr_ack       (ddr_wr_ack        ), // input 1-bit.ddr_wr_end       (ddr_wr_end        ), // input 1-bit.ddr_wr_rden      (ddr_wr_rden       ), // input 1-bit.ddr_wr_q         (ddr_wr_q          ), // output 128-bit.ddr_wr_mask      (ddr_wr_mask       )  // output 16-bit
);
// End of vid_ddr_wr_inst instantiationendmodule

2.3 上板调试

        使用 PyQt5 和 OpenCV 库编写上位机程序,通过串口发送原始图像数据,以及水平翻转、垂直翻转参数。

# -*- Coding: UTF-8 -*-
import cv2
import sys
import struct
import numpy as np
from PyQt5 import Qt, QtGui, QtCore, QtWidgets, QtSerialPortclass mainWindow(Qt.QWidget):def __init__(self, com_port, parent=None):super(mainWindow, self).__init__(parent)self.setFixedSize(530, 384)self.setWindowTitle("PGL OpenCV Tool")self.flip_horizontal = Falseself.flip_vertical = False# 创建标签与按钮self.img_widget = QtWidgets.QLabel()self.btn1 = QtWidgets.QPushButton("打开")self.btn1.clicked.connect(self.getfile)self.btn2 = QtWidgets.QPushButton("关闭")self.btn2.clicked.connect(self.close)self.btn3 = QtWidgets.QPushButton("水平翻转")self.btn3.clicked.connect(self.flip_lr)self.btn4 = QtWidgets.QPushButton("垂直翻转")self.btn4.clicked.connect(self.flip_ud)# 创建布局centralLayout = QtWidgets.QVBoxLayout()centralLayout.addWidget(self.img_widget)bottomLayout = QtWidgets.QHBoxLayout()bottomLayout.addWidget(self.btn1)bottomLayout.addWidget(self.btn2)bottomLayout.addWidget(self.btn3)bottomLayout.addWidget(self.btn4)centralLayout.addLayout(bottomLayout)self.setLayout(centralLayout)# 串口对象self.COM = QtSerialPort.QSerialPort()self.COM.setPortName(com_port)self.COM.setBaudRate(256000)self.open_status = Falseself.row_cnt = 0self.img = Noneself.timer = QtCore.QTimer()self.timer.timeout.connect(self.sendImage)self.startup()def startup(self):"""Write code here to run once"""for com_port in QtSerialPort.QSerialPortInfo.availablePorts():print(com_port.portName())# Try open serial portif not self.COM.open(QtSerialPort.QSerialPort.ReadWrite):self.open_status = Falseprint("Open Serial Port failed.")else:self.open_status = Truedef flip_lr(self):"""水平翻转回调函数"""if self.flip_horizontal == False:self.flip_horizontal = Trueself.btn3.setStyleSheet("QPushButton{color:rgb(128,128,255)}")else:self.flip_horizontal = Falseself.btn3.setStyleSheet("QPushButton{color:rgb(0,0,0)}")def flip_ud(self):"""垂直翻转回调函数"""if self.flip_vertical == False:self.flip_vertical = Trueself.btn4.setStyleSheet("QPushButton{color:rgb(128,128,255)}")else:self.flip_vertical = Falseself.btn4.setStyleSheet("QPushButton{color:rgb(0,0,0)}")def getfile(self):"""获取图像路径"""fname = QtWidgets.QFileDialog.getOpenFileName(self, 'Open file','C:\\Users\\Administrator\\Pictures', "Image files(*.jpg *.png)")self.clipImage(fname[0])self.updateImage()self.sendImage()def clipImage(self, fname):"""读取并裁剪图片至512x384大小"""if fname:img = cv2.imread(fname, cv2.IMREAD_COLOR)img_roi = img[:384,:512,:]print(img_roi.shape)cv2.imwrite('./img_roi.png', img_roi)def updateImage(self):"""显示裁剪后的图像"""self.img_widget.setPixmap(QtGui.QPixmap('./img_roi.png'))self.img = cv2.imread('./img_roi.png')if self.open_status:self.timer.start(100)def sendImage(self):"""通过串口发送图片"""pattern = ">2BH{:d}B".format(512*3)# 获取图像翻转信息flip_flag = 0x00if self.flip_horizontal:flip_flag = flip_flag + 0x10if self.flip_vertical:flip_flag = flip_flag + 0x01# 发送图像数据if self.open_status:if self.row_cnt == 384+3:self.row_cnt = 0self.timer.stop()else:args1 = [0x55, flip_flag, self.row_cnt]args2 = [rgb for rgb in self.img[(self.row_cnt % 384),:].reshape(-1)]send_data = struct.pack(pattern, *(args1+args2))self.row_cnt += 1self.COM.write(send_data)def closeEvent(self, event):super().closeEvent(event)#self.slider_window.close()# 定时器停止self.timer.stop()if self.open_status:self.COM.close() # 关闭串口def main():app = QtWidgets.QApplication(sys.argv)window = mainWindow('COM21')window.show()#for win in (window, window.slider_window):#   win.show()sys.exit(app.exec_())if __name__ == "__main__":main()

        连接 HDMI 线和串口线,选择与发送图像,就可以看到 FPGA 的处理效果了。以下是水平翻转效果。

以下是垂直翻转效果。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/336167.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Flink构造宽表实时入库案例介绍

1. 安装包准备 Flink 1.15.4 安装包 Flink cdc的mysql连接器 Flink sql的sdb连接器 MySQL驱动 SDB驱动 Flink jdbc的mysql连接器 2. 入库流程图 3. Flink安装部署 上传Flink压缩包到服务器&#xff0c;并解压 tar -zxvf flink-1.14.5-bin-scala_2.11.tgz -C /opt/ 复…

基于python热门旅游景点推荐系统+爬虫技术

大数据分析&#xff0c;数据可视化等皆可用。 源码分享。

esp32UART串口外设(Arduino)

通用异步接收器/发送器 &#xff08;UART&#xff09; 介绍 通用异步接收器/发送器 &#xff08;UART&#xff09; 是一种硬件功能&#xff0c;它使用广泛采用的异步串行通信接口&#xff08;如 RS232、RS422 和 RS485&#xff09;处理通信&#xff08;即时序要求和数据成帧&…

Underactuated Robotics - 欠驱动机器人学(二)- 简单摆杆

系列文章目录 前言 一、导言 本章的目标并不高&#xff0c;我们只想了解钟摆的动力学原理。 为什么是摆呢&#xff1f;部分原因是&#xff0c;我们大多数多连杆机器人操纵器的动力学都是大量耦合摆的动力学。此外&#xff0c;单摆的动力学原理非常丰富&#xff0c;足以引入我…

JVM主要的几种垃圾回收算法

1、Java 为什么要实现自动内存管理 &#xff1f; 简化开发过程&#xff1a;通过内存自动管理可以避免手动分配和释放内存的麻烦&#xff0c;减少了内存泄漏和内存错误的风险&#xff0c;让研发能更专注于业务逻辑&#xff0c;不必纠结于内存管理的细节。 提高开发效率&#xff…

【Python程序开发系列】一文总结API的基本概念、功能分类、认证方式、使用方法和开发流程

这是Python程序开发系列原创文章&#xff0c;我的第195篇原创文章。 一、什么是API&#xff1f; API是软件开发中非常重要的概念&#xff0c;它简化了不同组件之间的交互和集成&#xff0c;提供了对其他软件或服务功能的访问和调用方式。 API是应用程序编程接口&#xff08;Ap…

“To-Do Master“ GPTs:重塑任务管理的趣味与效率

有 GPTs 访问权限的可以点击链接进行体验&#xff1a;https://chat.openai.com/g/g-IhGsoyIkP-to-do-master 部署私人的 To-Do Master 教程&#xff1a;https://github.com/Reborn14/To-Do-Master/tree/main 引言 在忙碌的日常生活中&#xff0c;有效地管理日常任务对于提高生…

考虑柔性负荷的综合能源系统低碳经济优化调度【复现】

随着低碳发展进程的不断推进&#xff0c;综合能源系统&#xff08;IES&#xff09;逐渐成为实现减排目标的重要支撑技术。 基于能 源集线器概念&#xff0c;结合需求侧柔性负荷的可平移、可转移、可削减特性&#xff0c;构建了含风光储、燃气轮机、柔性负荷等 在内的 IES 模型。…

IDEA中在Java项目中添加Web模块 与配置tomcat服务器

现有项目添加直接走第二步 生成普通新项目 给项目添加框架支持 勾选 Web Application 选项, 点击OK 得到项目目录结构 , 出现web目录结构, 且web目录文件夹出现小蓝点 web或webapp 没有出现小蓝点 说明web配置没有出现或是手动构建的目录结构 , 在IDE关闭或者迁移项目时会出…

自承载 Self-Host ASP.NET Web API 1 (C#)

本教程介绍如何在控制台应用程序中托管 Web API。 ASP.NET Web API不需要 IIS。 可以在自己的主机进程中自托管 Web API。 创建控制台应用程序项目 启动 Visual Studio&#xff0c;然后从“开始”页中选择“新建项目”。 或者&#xff0c;从“ 文件 ”菜单中选择“ 新建 ”&a…

[VSCode] VSCode 常用快捷键

文章目录 VSCode 源代码编辑器VSCode 常用快捷键分类汇总01 编辑02 导航03 调试04 其他05 重构06 测试07 扩展08 选择09 搜索10 书签11 多光标12 代码片段13 其他 VSCode 源代码编辑器 官网&#xff1a;https://code.visualstudio.com/ 下载地址&#xff1a;https://code.visua…

【Android Studio】APP练手小项目——切换图片APP

本项目效果&#xff1a; 前言&#xff1a;本项目最终实现生成一个安卓APP软件&#xff0c;点击按钮可实现按钮切换图片。项目包含页面布局、功能实现的逻辑代码以及设置APP图标LOGO和自定义APP名称。 关于Android Studio的下载与安装见我的博文&#xff1a;Android Studio 最新…