【数字图像处理】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)是一种值类型数据结构,用于封装不同或相同类型的数据成一个单一的实体。结构体非常适合用来表示轻量级的对象,…...
别再乱选ASCII/HEX了!野火串口调试助手发送接收区配置详解(附实战案例)
串口通信调试实战:ASCII与HEX模式的选择艺术 调试智能家居设备时,你是否遇到过发送"ON"指令毫无反应,接收区却显示一堆乱码的尴尬?这往往不是设备故障,而是串口调试中最常见的模式选择错误。作为嵌入式开发者…...
ESP32-S3实战指南:SPI多设备管理与高效数据传输
1. ESP32-S3的SPI总线基础认知 第一次接触ESP32-S3的SPI总线时,我完全被各种专业术语搞懵了。后来在实际项目中反复折腾才发现,SPI本质上就是个"快递小哥",负责在芯片和外围设备之间搬运数据。ESP32-S3内置了4个这样的"快递站…...
【数值分析】线性方程组求解的MATLAB实战:从高斯消元到追赶法
1. 线性方程组求解的数值方法概述 在工程计算和科学研究中,线性方程组的求解是一个基础而重要的问题。想象一下,你正在设计一座桥梁,需要计算各个节点的受力情况;或者你在分析电路时,需要确定各个支路的电流大小。这些…...
牙科手术显微镜市场:其中中国市场占比超15%
在口腔诊疗向精细化、微创化演进的进程中,牙科手术显微镜作为核心光学放大设备,凭借其高照度、高景深与高清晰度特性,成为提升根管治疗、牙周手术及种植修复等环节精准性的关键工具。该设备集成连续变倍观察、同轴照明、术野调焦及影像记录系…...
Java八股文实践篇:从理论到DeOldify项目中的设计模式应用
Java八股文实践篇:从理论到DeOldify项目中的设计模式应用 每次面试被问到设计模式,是不是都只能背出“单例模式确保一个类只有一个实例”这样的标准答案?背得滚瓜烂熟,但一上手写代码,还是觉得这些模式离自己很远&…...
Ostrakon-VL零售AI降本方案:替代人工巡检,单店年省8万元
Ostrakon-VL零售AI降本方案:替代人工巡检,单店年省8万元 1. 零售巡检的痛点与AI解决方案 在传统零售运营中,门店巡检是一项耗时耗力的日常工作。店长或督导人员需要每天检查: 商品陈列是否整齐货架缺货情况价签是否正确店铺环境…...
为什么你的Polars 2.0清洗脚本在1TB数据下突然卡死?——Lazy Execution陷阱、Chunking边界与并发泄漏三重真相
第一章:为什么你的Polars 2.0清洗脚本在1TB数据下突然卡死?——Lazy Execution陷阱、Chunking边界与并发泄漏三重真相Lazy Execution的隐式延迟引爆内存雪崩 Polars 2.0 默认启用 LazyFrame 模式,所有操作仅构建执行计划,直到调用…...
STM32CubeMX实战指南:DMA驱动USART高效数据传输
1. DMA与USART协作的核心价值 第一次接触STM32的DMA功能时,我正被一个传感器数据采集项目折磨得焦头烂额。当时用传统的中断方式处理串口数据,CPU占用率直接飙到70%,整个系统卡得像老式拨号上网。直到尝试了DMAUSART组合,才真正体…...
renren-fast-vue系统配置中心使用指南:灵活配置与动态切换
renren-fast-vue系统配置中心使用指南:灵活配置与动态切换 【免费下载链接】renren-fast-vue renren-fast-vue基于vue、element-ui构建开发,实现renren-fast后台管理前端功能,提供一套更优的前端解决方案。 项目地址: https://gitcode.com/…...
Splunk Enterprise 9.4.10 (macOS, Linux, Windows) - 机器数据管理和分析
Splunk Enterprise 9.4.10 (macOS, Linux, Windows) - 机器数据管理和分析 安全信息和事件管理 (SIEM)、全面的日志管理和分析平台 请访问原文链接:https://sysin.org/blog/splunk-9/ 查看最新版。原创作品,转载请保留出处。 作者主页:sys…...
