【数字图像处理】Gamma 变换
在数字图像处理中,Gamma 变换是一种重要的灰度变换方法,可以用于图像增强与 Gamma 校正。本文主要介绍数字图像 Gamma 变换的基本原理,并记录在紫光同创 PGL22G FPGA 平台的布署与实现过程。
目录
1. Gamma 变换原理
2. FPGA 布署与实现
2.1 功能与指标定义
2.2 模块设计
2.3 上板调试
1. Gamma 变换原理
在摄像机成像过程中,人们使用了 Gamma 编码对图像进行处理,这样做的好处是能更好地记录与存储图像。
采用 Gamma 编码的图像在显示器上显示时,需要进行 Gamma 校正,以还原图像。
Gamma 校正可以用以下变换公式表示:
其中, 是输入图像某一点的亮度值,
是输出图像上对应点的亮度值。
(1)当 0 < gamma < 1 时,图像在低灰度值区域,动态范围变大,整体图像的灰度值变大;
(2)当 gamma > 1 时,图像在高灰度值区域,动态范围变大,整体图像的灰度值变小。
使用 Matlab 进行验证,代码如下:
clc, clear% 读取图像
im = imread('./loopy.png');
im = im2double(im);% gamma变换
invgamma = 2.2;
gamma = 1/invgamma;
im_new = im.^gamma;subplot(121)
imshow(im2uint8(im))
title('原图像')
subplot(122)
imshow(im2uint8(im_new))
title('处理后图像')
参考链接:Understanding Gamma Correction (cambridgeincolour.com)
2. FPGA 布署与实现
2.1 功能与指标定义
使用紫光同创 FPGA 平台实现 Gamma 变换功能,FPGA 需要实现的功能与指标如下:
(1)与电脑的串口通信,用于接收上位机下发的 Gamma 曲线和原始图像,波特率为 256000 Bd/s;
(2)Gamma 变换,使用 FPGA 嵌入式 RAM,实现 Gamma 曲线的缓存与查表;
(3)DDR3 读写控制,将处理前后的图像数据分别写入 DDR3 的不同区域,实现图像的拼接;
(4)HDMI 输出,输出一路 HDMI 信号源,用于将拼接后的图像显示在外接显示器上,分辨率为 1024×768。
2.2 模块设计
主要的设计模块层次与功能说明如下:
模块名称 | 功能说明 | |
top_uart | uart_rx_slice | 串口接收驱动模块 |
uart_rx_parse | 串口数据解析模块,从上位机接收 8bit 原始图像,以及 Gamma 曲线数据 | |
top_vidin | vidin_pipeline | 缓存两行图像数据,并将数据提交到 ddr3 数据调度模块 |
conv_gamma | Gamma 变换模块,使用 DPRAM 存储器进行 Gamma 查表 | |
merge_out | dvi_timing_gen | HDMI 视频时序产生模块 |
dvi_ddr_rd | 根据 HDMI 控制信号,提交读指令到 ddr3 数据调度模块 | |
dvi_encoder | HDMI 输出编码(8b10b 编码)与输出驱动模块 |
其中,conv_gamma 模块主要使用 dpram 查表的方式,对原始图像的 RGB 分量分别进行 Gamma 变换,模块代码如下:
`timescale 1 ns/ 1 psmodule conv_gamma (// System levelsys_rst,sys_clk,// Gamma parameter inputpara_gamma_waddr,para_gamma_data,para_gamma_wren,// Gamma data input and outputgamma_in_data,gamma_out_data
);// IO direction/register definitions
input sys_rst;
input sys_clk;
input [7:0] para_gamma_waddr;
input [7:0] para_gamma_data;
input para_gamma_wren;
input [23:0] gamma_in_data;
output [23:0] gamma_out_data;// internal signal declarations
reg [7:0] blk_mem_waddr;
reg [7:0] blk_mem_wdata;
reg blk_mem_wren;// gamma_dpram_inst_r: Block dpram for gamma data buffer
blk_mem_256x8b_gamma gamma_dpram_inst_r (.wr_data (blk_mem_wdata ), // input 8-bit.wr_addr (blk_mem_waddr ), // input 8-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 (gamma_in_data[2*8+:8] ), // input 8-bit.rd_data (gamma_out_data[2*8+:8] ), // output 8-bit.rd_clk (sys_clk ), // input 1-bit.rd_rst (sys_rst ) // input 1-bit
);
// End of gamma_dpram_inst_r instantiation// gamma_dpram_inst_g: Block dpram for gamma data buffer
blk_mem_256x8b_gamma gamma_dpram_inst_g (.wr_data (blk_mem_wdata ), // input 8-bit.wr_addr (blk_mem_waddr ), // input 8-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 (gamma_in_data[1*8+:8] ), // input 8-bit.rd_data (gamma_out_data[1*8+:8] ), // output 8-bit.rd_clk (sys_clk ), // input 1-bit.rd_rst (sys_rst ) // input 1-bit
);
// End of gamma_dpram_inst_g instantiation// gamma_dpram_inst_b: Block dpram for gamma data buffer
blk_mem_256x8b_gamma gamma_dpram_inst_b (.wr_data (blk_mem_wdata ), // input 8-bit.wr_addr (blk_mem_waddr ), // input 8-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 (gamma_in_data[0*8+:8] ), // input 8-bit.rd_data (gamma_out_data[0*8+:8] ), // output 8-bit.rd_clk (sys_clk ), // input 1-bit.rd_rst (sys_rst ) // input 1-bit
);
// End of gamma_dpram_inst_b instantiationalways @(posedge sys_rst or posedge sys_clk) beginif (sys_rst) beginblk_mem_waddr <= {8{1'b0}};blk_mem_wdata <= 8'h00;blk_mem_wren <= 1'b0;endelse beginblk_mem_waddr <= para_gamma_waddr;blk_mem_wdata <= para_gamma_data;blk_mem_wren <= para_gamma_wren;end
end
endmodule
2.3 上板调试
使用 PyQt5 和 OpenCV 库编写上位机程序,通过串口发送 Gamma 曲线和原始图像数据,代码如下:
# -*- Coding: UTF-8 -*-
import cv2
import sys
import struct
import numpy as np
import pyqtgraph as pg
from PyQt5 import Qt, QtGui, QtCore, QtWidgets, QtSerialPortclass sliderWindow(Qt.QWidget):def __init__(self, parent=None):super(sliderWindow, self).__init__(parent)self.setGeometry(1250, 320, 400, 400)self.setWindowTitle("Slider Window")# 创建绘图窗口self.plot_graph = pg.PlotWidget()self.plot_graph.setBackground('#303030')self.plot_graph.setXRange(0,1)self.plot_graph.setYRange(0,1)self.plot_graph.showGrid(x=True, y=True)gray = np.linspace(0, 1, 255)gamma = np.array(np.power(gray, 1))self.pen = pg.mkPen(color=(255, 255, 255), width=5, style=QtCore.Qt.SolidLine)self.plot_graph.plot(gray, gamma)# 创建底部滑动条self.label = QtWidgets.QLabel("1.00")self.slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)self.slider.setMinimum(20)self.slider.setMaximum(400)self.slider.setValue(100)#self.slider.setSingleStep(1)self.slider.setTickInterval(10)self.slider.setTickPosition(QtWidgets.QSlider.TicksBelow)self.slider.valueChanged.connect(self.valueChanged)bottomLayout = QtWidgets.QHBoxLayout()bottomLayout.addWidget(self.label)bottomLayout.addWidget(self.slider)# 创建中心布局centralLayout = QtWidgets.QVBoxLayout()centralLayout.addWidget(self.plot_graph)centralLayout.addLayout(bottomLayout)self.setLayout(centralLayout)def valueChanged(self):"""更新参数值"""if self.slider.value() == 0:float_value = 0.01else:float_value = self.slider.value() /100.0self.label.setText("{:.2f}".format(float_value))self.updatePlot(float_value)def updatePlot(self, gamma):gray = np.linspace(0,1,255)gray_gamma = np.array(np.power(gray, 1/gamma))self.plot_graph.clear()self.plot_graph.plot(gray, gray_gamma)class 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.img_widget = QtWidgets.QLabel()self.btn1 = QtWidgets.QPushButton("打开")self.btn1.clicked.connect(self.getfile)self.btn2 = QtWidgets.QPushButton("关闭")self.btn2.clicked.connect(self.close)# 创建布局centralLayout = QtWidgets.QVBoxLayout()centralLayout.addWidget(self.img_widget)bottomLayout = QtWidgets.QHBoxLayout()bottomLayout.addWidget(self.btn1)bottomLayout.addWidget(self.btn2)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"""self.slider_window = sliderWindow()self.slider_window.slider.valueChanged.connect(self.transformGamma)self.slider_window.slider.valueChanged.connect(self.sendGamma)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 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 transformGamma(self):"""Gamma变换"""if self.slider_window.slider.value() == 0:invgamma = 0.01else:invgamma = self.slider_window.slider.value() /100.0gamma = 1/invgammaimg_trans = np.array(np.power(self.img/255, gamma)*255, dtype=np.uint8)cv2.imwrite('./img_gamma.png', img_trans)self.img_widget.setPixmap(QtGui.QPixmap('./img_gamma.png'))def updateImage(self):"""显示裁剪后的图像"""self.img = cv2.imread('./img_roi.png')# 判断显示原图像,还是Gamma变换后的图像if self.slider_window.slider.value() == 100:self.img_widget.setPixmap(QtGui.QPixmap('./img_roi.png'))else:self.transformGamma()if self.open_status:self.timer.start(100)def sendImage(self):"""通过串口发送图片"""pattern = ">2H{:d}B".format(512*3)if self.open_status:if self.row_cnt == 384:self.row_cnt = 0self.timer.stop()else:args1 = [0x5500, self.row_cnt]args2 = [rgb for rgb in self.img[self.row_cnt,:].reshape(-1)]send_data = struct.pack(pattern, *(args1+args2))self.row_cnt += 1self.COM.write(send_data)def sendGamma(self):"""通过串口发送Gamma曲线"""if self.slider_window.slider.value() == 0:invgamma = 0.01else:invgamma = self.slider_window.slider.value() /100.0gamma = 1/invgammagamma_f = lambda x: np.uint8(np.floor(np.power(x/255, gamma)*255))pattern = ">1H{:d}B".format(256)if self.open_status:args1 = [0xAA00]args2 = [gamma_f(x) for x in range(256)]send_data = struct.pack(pattern, *(args1+args2))self.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')for win in (window, window.slider_window):win.show()sys.exit(app.exec_())if __name__ == "__main__":main()
连接串口线与 HDMI 线,拖动滑动条改变 Gamma 值,上位机程序会自动发送 Gamma 曲线到开发板,然后发送要显示的图像,就可以看到 FPGA 处理的效果了 ~
相关文章:

【数字图像处理】Gamma 变换
在数字图像处理中,Gamma 变换是一种重要的灰度变换方法,可以用于图像增强与 Gamma 校正。本文主要介绍数字图像 Gamma 变换的基本原理,并记录在紫光同创 PGL22G FPGA 平台的布署与实现过程。 目录 1. Gamma 变换原理 2. FPGA 布署与实现 2…...

ChatGPT + DALL·E 3
参考链接: https://chat.xutongbao.top/...

【AI视野·今日Robot 机器人论文速览 第六十三期】Thu, 26 Oct 2023
AI视野今日CS.Robotics 机器人学论文速览 Fri, 27 Oct 2023 Totally 27 papers 👉上期速览✈更多精彩请移步主页 Daily Robotics Papers 6-DoF Stability Field via Diffusion Models Authors Takuma Yoneda, Tianchong Jiang, Gregory Shakhnarovich, Matthew R. …...

测试Bard和ChatGPT关于双休的法规和推理
Bard是试验品,chatgpt是3.5版的。 首先带着问题,借助网络搜索,从政府官方网站等权威网站进行确认,已知正确答案的情况下,再来印证两个大语言模型的优劣。 想要了解的问题是,在中国,跟法定工作…...
py查询第三方库的路径
在Python中,你可以使用pkg_resources模块来查询第三方库的路径。这个模块提供了许多有用的函数来处理Python包和资源。 以下是一个简单的示例,展示如何查询第三方库的路径: import pkg_resources# 指定要查询的包名 package_name "第…...

LeetCode(16)接雨水【数组/字符串】【困难】
目录 1.题目2.答案3.提交结果截图 链接: 42. 接雨水 1.题目 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。 示例 1: 输入:height [0,1,0,2,1,0,1,3,2,1,2,1] 输出&…...

Kotlin 知识体系
Kotlin 知识体系 1、Kotlin 文档2、Kotlin 基础3、桌面应用程序4、Android 与 iOS 应用程序 1、Kotlin 文档 Kotlin 是一门现代但已成熟的编程语言,旨在让开发人员更幸福快乐。 它简洁、安全、可与 Java 及其他语言互操作,并提供了多种方式在多个平台间复…...

深度学习之基于YoloV5-Pose的人体姿态检测可视化系统
欢迎大家点赞、收藏、关注、评论啦 ,由于篇幅有限,只展示了部分核心代码。 文章目录 一项目简介 深度学习之基于 YOLOv5-Pose 的人体姿态检测可视化系统介绍YOLOv5-Pose 简介系统特点系统架构使用方法 二、功能三、系统四. 总结 一项目简介 深度学习之基…...

为什么Go是后端开发的未来
近年来,Go 编程语言的流行度迅速增加。Go 最初由 Google 开发,迅速成为后端开发中最受欢迎的语言之一,特别是在分布式系统和微服务的开发中。本文将讨论为什么 Go 是后端开发的未来。 Go 简介 Go,又称为 Golang,是由…...

Linux输入设备应用编程(键盘,按键,触摸屏,鼠标)
目录 一 输入设备编程介绍 1.1 什么是输入设备呢? 1.2 什么是输入设备的应用编程? 1.3 input子系统 1.4 数据读取流程 1.5 应用程序如何解析数据 1.5.1 按键类事件: 1.5.2 相对位移事件 1.5.3 绝对位移事件 二 读取 struct input_e…...

【Axure教程】滑动内容选择器
滑动内容选择器通常是一种用户界面组件,允许用户通过滑动手势在一组内容之间进行选择。这种组件可以在移动应用程序或网页中使用,以提供直观的图片选择体验。 那今天就教大家如何用中继器制作一个滑动内容选择器,我们会以滑动选择电影为案例…...
vite+vue3使用@路径,报错处理
报错原因:未配置 符号为指定路径别名,直接使用导致 处理方法: 安装path模块: npm install --save-dev types/node修改vite.config.ts import { defineConfig } from vite import vue from vitejs/plugin-vue import path from…...

[开源]基于 AI 大语言模型 API 实现的 AI 助手全套开源解决方案
原文:[开源]基于 AI 大语言模型 API 实现的 AI 助手全套开源解决方案 一飞开源,介绍创意、新奇、有趣、实用的开源应用、系统、软件、硬件及技术,一个探索、发现、分享、使用与互动交流的开源技术社区平台。致力于打造活力开源社区࿰…...

2023年中国中端连锁酒店分类、市场规模及主要企业市占率[图]
中端连锁酒店行业是指定位于中档酒店市场、具有全国统一的品牌形象识别系统、全国统一的运营体系、会员体系和营销体系的酒店。中端酒店通常提供舒适、标准化的房间设施和服务,价格较为合理,符合广大消费者的需求。其价格略高于经济型酒店,但…...

mac下vue-cli从2.9.6升级到最新版本
由于mac之前安装了 vue 2.9.6 的版本,现在想升级到最新版本,用官方给的命令: npm uninstall vue-cli -g 发现不行。 1、究其原因:从vue-cli 3.0版本开始原来的npm install -g vue-cli 安装的都是旧版,最高到2.9.6。安…...

【cpolar】搭建我的世界Java版服务器,公网远程联机
🎥 个人主页:深鱼~🔥收录专栏:cpolar🌄欢迎 👍点赞✍评论⭐收藏 目录 前言: 1. 搭建我的世界服务器 1.1 服务器安装java环境 1.2 配置服务端 2. 测试局域网联机 3. 公网远程联机 3.1 安…...
Redis数据类型–Geospatial 地理空间
目录 前言 命令 1、geoadd 1)格式 2)实例 2、geopos 1)格式 2)实例 3、geodist 1)格式 2)实例 4、georadius 1)格式 2)实例 前言 Redis 3.2 中增加了对GEO类型的支持。GEO…...
LeetCode 面试题 16.26. 计算器
文章目录 一、题目二、C# 题解 一、题目 给定一个包含正整数、加()、减(-)、乘(*)、除(/)的算数表达式(括号除外),计算其结果。 表达式仅包含非负整数,, - ,*,/ 四种运算符和空格 。 整数除法仅保留整数部分。 示例 …...

15篇MyBatis-Plus系列集合篇「值得收藏学习」
历史文章(文章累计490) 《国内最全的Spring Boot系列之一》 《国内最全的Spring Boot系列之二》 《国内最全的Spring Boot系列之三》 《国内最全的Spring Boot系列之四》 《国内最全的Spring Boot系列之五》 《国内最全的Spring Boot系列之六》 M…...
C#入门(6): 结构体、ref struct
文章目录 定义结构体实例化结构体结构体的值类型特性结构体和类的区别限制ref structref return C# 中的结构体(Struct)是一种值类型数据结构,用于封装不同或相同类型的数据成一个单一的实体。结构体非常适合用来表示轻量级的对象,…...
Qt Http Server模块功能及架构
Qt Http Server 是 Qt 6.0 中引入的一个新模块,它提供了一个轻量级的 HTTP 服务器实现,主要用于构建基于 HTTP 的应用程序和服务。 功能介绍: 主要功能 HTTP服务器功能: 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...

[10-3]软件I2C读写MPU6050 江协科技学习笔记(16个知识点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...

零基础设计模式——行为型模式 - 责任链模式
第四部分:行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习!行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想:使多个对象都有机会处…...

SpringTask-03.入门案例
一.入门案例 启动类: package com.sky;import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCach…...

优选算法第十二讲:队列 + 宽搜 优先级队列
优选算法第十二讲:队列 宽搜 && 优先级队列 1.N叉树的层序遍历2.二叉树的锯齿型层序遍历3.二叉树最大宽度4.在每个树行中找最大值5.优先级队列 -- 最后一块石头的重量6.数据流中的第K大元素7.前K个高频单词8.数据流的中位数 1.N叉树的层序遍历 2.二叉树的锯…...

Reasoning over Uncertain Text by Generative Large Language Models
https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829 1. 概述 文本中的不确定性在许多语境中传达,从日常对话到特定领域的文档(例如医学文档)(Heritage 2013;Landmark、Gulbrandsen 和 Svenevei…...

C++使用 new 来创建动态数组
问题: 不能使用变量定义数组大小 原因: 这是因为数组在内存中是连续存储的,编译器需要在编译阶段就确定数组的大小,以便正确地分配内存空间。如果允许使用变量来定义数组的大小,那么编译器就无法在编译时确定数组的大…...
Git常用命令完全指南:从入门到精通
Git常用命令完全指南:从入门到精通 一、基础配置命令 1. 用户信息配置 # 设置全局用户名 git config --global user.name "你的名字"# 设置全局邮箱 git config --global user.email "你的邮箱example.com"# 查看所有配置 git config --list…...

解析奥地利 XARION激光超声检测系统:无膜光学麦克风 + 无耦合剂的技术协同优势及多元应用
在工业制造领域,无损检测(NDT)的精度与效率直接影响产品质量与生产安全。奥地利 XARION开发的激光超声精密检测系统,以非接触式光学麦克风技术为核心,打破传统检测瓶颈,为半导体、航空航天、汽车制造等行业提供了高灵敏…...
SpringAI实战:ChatModel智能对话全解
一、引言:Spring AI 与 Chat Model 的核心价值 🚀 在 Java 生态中集成大模型能力,Spring AI 提供了高效的解决方案 🤖。其中 Chat Model 作为核心交互组件,通过标准化接口简化了与大语言模型(LLM࿰…...