【数字图像处理】Gamma 变换

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

目录

1. Gamma 变换原理

2. FPGA 布署与实现

2.1 功能与指标定义

2.2 模块设计

2.3 上板调试


1. Gamma 变换原理

        在摄像机成像过程中,人们使用了 Gamma 编码对图像进行处理,这样做的好处是能更好地记录与存储图像。

        采用 Gamma 编码的图像在显示器上显示时,需要进行 Gamma 校正,以还原图像。

Gamma 校正可以用以下变换公式表示:

V_{out} = (V_{in})^{gamma}

其中,V_{in} 是输入图像某一点的亮度值,V_{out} 是输出图像上对应点的亮度值。

(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_uartuart_rx_slice串口接收驱动模块
uart_rx_parse串口数据解析模块,从上位机接收 8bit 原始图像,以及 Gamma 曲线数据
top_vidinvidin_pipeline缓存两行图像数据,并将数据提交到 ddr3 数据调度模块
conv_gammaGamma 变换模块,使用 DPRAM 存储器进行 Gamma 查表
merge_outdvi_timing_genHDMI 视频时序产生模块
dvi_ddr_rd根据 HDMI 控制信号,提交读指令到 ddr3 数据调度模块
dvi_encoderHDMI 输出编码(8b10b 编码)与输出驱动模块

        其中,conv_gamma 模块主要使用 dpram 查表的方式,对原始图像的 RGB 分量分别进行 Gamma 变换,模块代码如下:

`timescale 1 ns/ 1 ps

module conv_gamma (
   // System level
   sys_rst,
   sys_clk,

   // Gamma parameter input
   para_gamma_waddr,
   para_gamma_data,
   para_gamma_wren,

   // Gamma data input and output
   gamma_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 instantiation

always @(posedge sys_rst or posedge sys_clk) begin
   if (sys_rst) begin
      blk_mem_waddr <= {8{1'b0}};
      blk_mem_wdata <= 8'h00;
      blk_mem_wren  <= 1'b0;
   end
   else begin
      blk_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, QtSerialPort

class 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.01
      else:
         float_value = self.slider.value() /100.0
      self.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 = False
      self.row_cnt = 0
      self.img = None
      self.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 port
      if not self.COM.open(QtSerialPort.QSerialPort.ReadWrite):
         self.open_status = False
         print("Open Serial Port failed.")
      else:
         self.open_status = True

   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 transformGamma(self):
      """Gamma变换"""
      if self.slider_window.slider.value() == 0:
         invgamma = 0.01
      else:
         invgamma = self.slider_window.slider.value() /100.0

      gamma = 1/invgamma
      img_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 = 0
            self.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 += 1
            self.COM.write(send_data)

   def sendGamma(self):
      """通过串口发送Gamma曲线"""
      if self.slider_window.slider.value() == 0:
         invgamma = 0.01
      else:
         invgamma = self.slider_window.slider.value() /100.0

      gamma = 1/invgamma
      gamma_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 处理的效果了 ~

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

◢Django 分页+搜索

1、搜索数据 从数据库中获取数据&#xff0c;并进行筛选&#xff0c;xx__contains q作为条件&#xff0c;查找的是xx列中有q的所有数据条 当有多个筛选条件时&#xff0c;将条件变成一个字典&#xff0c;传入 **字典 &#xff0c;ORM会自行翻译并查找。 筛选电话号码这一列…

2021年3月青少年软件编程(Python)等级考试试卷(一级)

2021年3月青少年软件编程&#xff08;Python&#xff09;等级考试试卷&#xff08;一级&#xff09; 分数&#xff1a;100.00 题数&#xff1a;37一、单选题&#xff08;共25题&#xff0c;每题2分&#xff0c;共50分&#xff09;二、判断题&#xff08;共10题&#xff0c;每题…

.NET 8 Video教程介绍(开篇)

教程简介 本文将简单描述视频网站教程&#xff0c;视频网站是一个类似于腾讯视频一样的网站&#xff0c;视频资源用户自己上传&#xff0c;然后提供友好的界面查看视频和搜索视频&#xff0c;并且提供管理页面对于视频进行管理&#xff0c;我们将使用Blazor作为前端&#xff0…

uniapp中实现圆形进度条的方式有哪些?

前言 在uniapp开发小程序或者apk时&#xff0c;页面需要用到一个圆形进度条&#xff08;带文字和百分比的&#xff09;&#xff0c;自己也自定义过一个,但是有一点小问题&#xff0c;咱先展示如何引入插件市场的在介绍自定义的&#xff01;一共四种&#xff0c;但是你需要考虑自…

PPT基础:表格

目录 表格基本功能底纹框线单元格表格美化 表格基本功能 底纹 所在位置&#xff1a;插入图表>>>表设计>>>底纹 底纹&#xff1a;相当于每个单元格的“颜色填充”如何用表格拆分图片 &#xff08;1&#xff09;生成一个表格>>>插入一张图片>>…

高效开发与设计:提效Spring应用的运行效率和生产力 | 京东云技术团队

引言 现状和背景 Spring框架是广泛使用的Java开发框架之一&#xff0c;它提供了强大的功能和灵活性&#xff0c;但在大型应用中&#xff0c;由于Spring框架的复杂性和依赖关系&#xff0c;应用的启动时间和性能可能会受到影响。这可能导致开发过程中的迟缓和开发效率低下。优…

Transformer笔记

Transformer encoder-decoder架构 Encoder&#xff1a;将输入序列转换为一个连续向量空间中的表示。Encoder通常是一个循环神经网络&#xff08;RNN&#xff09;或者卷积神经网络&#xff08;CNN&#xff09;&#xff0c;通过对输入序列中的每个元素进行编码&#xff0c;得到…

[Genode] ARM TrustZone

这是关于读文章ARM TrustZone的记录&#xff0c;原文是英文&#xff0c;刚开始会有点反应不过来&#xff0c;这里大部分是对文章的翻译与提取。 ARM信任区技术 ARM信任区是在 热烈讨论关于X86平台上的可信平台模块&#xff08;TPM&#xff09; 时引入的。。 就像TPM芯片神奇…

nginx学习(3)Nginx 负载均衡

Nginx 负载均衡 实战案例 实现效果 浏览器地址栏输入地址 http://172.31.0.99/oa/a.html&#xff0c;负载均衡效果&#xff0c;平均在 8083 和 8084 端口中&#xff0c;刷新浏览器&#xff0c;显示不同 一、配置 1、先创建2个文件夹&#xff0c;并将apache-tomcat-8.5.87解…

简化业务流程——通知短信API在企业中的应用

引言 通知短信API是一种可以帮助企业简化业务流程的通讯工具。随着移动互联网技术的不断发展&#xff0c;移动通讯已经成为了我们日常生活和企业运营中不可或缺的一部分。通知短信API是一种可以在企业中将移动通讯应用到极致的工具&#xff0c;它可以为企业提供高效便捷的通讯…

[qemu逃逸] XNUCA2019-vexx

前言 这题没有去符合, 题目本身不算难. 用户名: root 密码: goodluck 设备逆向 题目没有去符合, 所以其实没啥好讲了, 就列一些笔者认为关键的地方 这里的定义了两块 mmio 内存区. 然后看下设备实例结构体: 可以看到 QEMUTimer, 所以多半就是劫持 dma_timer 了. 漏洞点在…

【日常总结】java JSON 转 实体类 (含多层嵌套)

一、场景 二、问题 三、解决方案 四、实战 1. 引入maven依赖 2. IEDA 安装lombok 插件 3. 安装 GsonFormPlu 插件 4. 使用 Stage 1&#xff1a;新建类&#xff0c;右键 选择 Generate Stage 2&#xff1a;选择 GsonFormatPlus Stage 3&#xff1a;将json复制其中&…

Linux:详解(yum的使用、vim编辑器命令集合以及gcc/g++编译器的使用)

Linux 软件包管理器 yum 什么是软件包&#xff1a; 在Linux下安装软件, 一个通常的办法是下载到程序的源代码, 并进行编译, 得到可执行程序. 但是这样太麻烦了, 于是有些人把一些常用的软件提前编译好, 做成软件包(可以理解成windows上的安装程序)放在一个服务器上, 通…

图片地址GPS经纬度查询

先打开exif图片查询的网站&#xff1a; 改图宝的&#xff1a;https://www.gaitubao.com/exif图虫de的:EXIF信息查看器 (tuchong.com) 将这个地点&#xff1a;51 deg 30 51.90" N, 0 deg 5 38.73" W 修改为&#xff1a;5130 51.90" N, 05 38.73" W 到谷…

Java JSON字符串替换其中对应的值

代码&#xff1a; public static void main(String[] args) { // String theData crmScene.getData();String theData "[{\"type\":1,\"values\":[\"审批中\",\"未交付\"],\"name\":\"status\"}]"…

听懂未来:AI语音识别技术的进步与实战

目录 一、引言语音识别技术的魅力与挑战语音识别的基本概念技术的进步与应用实际应用的影响 二、语音识别技术的历史1. 初期探索&#xff08;1950s - 1970s&#xff09;早期的实验 2. 隐马尔可夫模型的兴起&#xff08;1980s&#xff09;算法创新 3. 深度神经网络的应用&#x…

php 时区查看和设置

php的时区&#xff0c;关系到相关时间函数的结果 其他相关&#xff1a; linux时区设置&#xff1a;链接 pgsql时区设置&#xff1a; 一、查看可以用的时区列表 新建一个php文件&#xff0c;输入下面程序即可 <?php echo "<pre>"; var_dump(timezone_id…

Linux系统编程 day03 Makefile、gdb、文件IO

Linux系统编程 day03 Makefile、gdb、文件IO 1. Makefile2. gdb3. 文件IO 1. Makefile Makefile文件中定义了一系列规则来指定哪些文件需要先编译&#xff0c;哪些文件需要后编译&#xff0c;哪些文件需要重新编译&#xff0c;甚至更加复杂的功能操作。Makefile就像一个shell脚…

阿里云ack集群升级流程

最近一直在升级过期的ack 集群版本 从1.22升级到1.24.。 参考&#xff1a; 升级流程、方式及所需时间

vue.js 短连接 动态连接

有这么一种场景&#xff0c;我们实现了某个业务&#xff0c;现在需要将这个业务连接对外推广以期实现我们的运营、推广、佣金目的&#xff0c;那么我们如何实现呢&#xff1f; 比如这个页面连接为&#xff1a; https://mp.domain.com/user/creation/editor?spm1&userno12…