【数字图像处理】Gamma 变换

news2024/11/26 8:52:04

在数字图像处理中,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 处理的效果了 ~

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

unity-模块卸载重新安装

unity-模块卸载重新安装 发现模块错误&#xff1f;发现不可以卸载重装&#xff1f;... 依据以下步骤试试&#xff1a; 1. 删除模块文件夹&#xff08;以安卓模块为例&#xff09; 2. 找见编辑器模块json 3. 找见所有安卓相关模块修改selected为false&#xff1a;"sel…

2023年中国地产SaaS分类、产业链及市场规模分析[图]

SaaS是一种基于云计算技术&#xff0c;通过订阅的方式向互联网向客户提供访问权限以获取计算资源的一项软件即服务。地产SaaS则是SaaS的具体应用&#xff0c;提供了一个线上平台&#xff0c;用于协助房地产供应商与购房者、建筑承建商、材料供应商及房地产资产管理公司之间的协…

Kotlin学习——hello kotlin 函数function 变量 类 + 泛型 + 继承

Kotlin 是一门现代但已成熟的编程语言&#xff0c;旨在让开发人员更幸福快乐。 它简洁、安全、可与 Java 及其他语言互操作&#xff0c;并提供了多种方式在多个平台间复用代码&#xff0c;以实现高效编程。 https://play.kotlinlang.org/byExample/01_introduction/02_Functio…

5G与中国的海

今年国庆假期&#xff0c;香港迎来了阔别5年的国庆维港烟花汇演 10月1日晚上9点&#xff0c;“HKT x FWD 2023 年国庆烟花汇演”在维多利亚港上空上演。在23分钟时间里&#xff0c;燃放了超过3万枚烟花。而与以往维港烟花秀不同的是&#xff0c;为了让更多民众欣赏这次表演&…

【canvas】了解canvas,并实现会议预定记录钟表盘、页面水印

初识canvas Canvas 有什么用 Canvas 允许使用直线、曲线、矩形、圆形等基本图形绘制出复杂的图形 Canvas 可以加载图像&#xff0c;并进行各种处理&#xff0c;如裁剪、缩放、旋转等操作 Canvas 可以通过 JavaScript 控制&#xff0c;所以你可以利用帧动画原理&#xff0c;…

【C++】​——多态性与模板(其一)

&#x1f383;个人专栏&#xff1a; &#x1f42c; 算法设计与分析&#xff1a;算法设计与分析_IT闫的博客-CSDN博客 &#x1f433;Java基础&#xff1a;Java基础_IT闫的博客-CSDN博客 &#x1f40b;c语言&#xff1a;c语言_IT闫的博客-CSDN博客 &#x1f41f;MySQL&#xff1a…

2023年中国稀土精密加工分类、市场规模及发展趋势分析[图]

稀土精密加工行业是指通过精密加工技术对稀土材料进行加工、制造和加工成品的一种行业。稀土精密加工行业主要包括稀土材料的提取、分离、纯化、加工和制造等环节&#xff0c;其中加工和制造是该行业的核心环节。稀土材料是指具有特殊物理、化学和磁性等性质的一类元素&#xf…

Oracle for Windows安装和配置——Oracle for Windows net配置

2.3. Oracle for Windows net配置 2.3.1. Oracle net配置 2.3.1.1. Oracle net简介 前述章节中,我们只是安装了数据库软件,创建了数据库,测试在服务器本地连接查询数据库。但还不能通过网络远程连接访问数据库,因为我们还没配置用来远程连接访问该数据库的组件Oracle ne…

数字IC前端学习笔记:时钟切换电路

相关阅读 数字IC前端https://blog.csdn.net/weixin_45791458/category_12173698.html?spm1001.2014.3001.5482 有些时候我们需要在系统运行时切换系统时钟&#xff0c;最简单的方法就是使用一个MUX&#xff08;数据选择器&#xff09;选择输出的时钟&#xff0c;如下代码片所…

提取图像文本的 5 大 Python 库

引言 光学字符识别是一个古老但依然具有挑战性的问题&#xff0c;涉及从非结构化数据中&#xff08;包括图像和PDF文档&#xff09;检测和识别文本。它在银行、电子商务和社交媒体内容管理等领域具有广泛的应用。 但与数据科学中的每个主题一样&#xff0c;尝试学习如何解决OC…

YOLOv8改进 | EIoU、SIoU、WIoU、DIoU、FocusIoU等二十余种损失函数

一、本文介绍 这篇文章介绍了YOLOv8的重大改进&#xff0c;特别是在损失函数方面的创新。它不仅包括了多种IoU损失函数的改进和变体&#xff0c;如SIoU、WIoU、GIoU、DIoU、EIOU、CIoU&#xff0c;还融合了“Focus”思想&#xff0c;创造了一系列新的损失函数。这些组合形式的…

OpenAI 地震!首席执行官被解雇,背后的原因是?

11月17日&#xff0c;ChatGPT的制造商OpenAI表示&#xff0c;经过审查后发现联合创始人兼首席执行官 Sam Altman与董事会“沟通时并不一贯坦诚”&#xff0c;因此公司已经决定解雇他。这家人工智能&#xff08;AI&#xff09;公司在一份声明中表示&#xff1a;“董事会不再相信…

美团外卖9元每周星期一开工外卖红包优惠券怎么领取?

美团外卖9元周一开工红包活动时间是什么时候&#xff1f; 美团外卖9元周一开工红包优惠券是指每周星期一可以领取的美团外卖红包优惠券&#xff0c;在美团外卖周一开工红包领取活动时间内可领取到9元周一开工美团外卖红包优惠券&#xff1b;&#xff08;温馨提醒&#xff1a;如…

2023年中国全自动烘干机产业链、产能及发展趋势分析[图]

全自动烘干机设备是工业化生产制造过程中不可缺少的一种机械设备设备&#xff0c;它广泛应用于工业化工原料加工中药材烘干、农副产品加工等&#xff0c;因此制造了多种干燥设备&#xff0c;目前有多层网带干燥机、热泵干燥机、微波干燥机和冷冻干燥机四种自动干燥机&#xff0…

jetbrains ai 提示该地区不可用的百分百解决方案,亲测有效

问题 申请 jetbrains 的 ai assistant 白名单已经通过&#xff0c;但是在使用 ai assistant 的过程中提示 The usage of the service is not permitted in your location ,我所在的地区是中国&#xff0c;目前该插件是对中国大陆关闭的。 刚开始我怀疑是代理的问题&#xff…

ckplayer自己定义风格播放器的开发记录

CKplayer是一款基于Flash和HTML5技术的开源视频播放器&#xff0c;支持多种格式的音视频播放&#xff0c;并且具有优秀的兼容性和扩展性。 它不仅可以在网页上播放本地或者网络上的视频&#xff0c;还可以通过代码嵌入到网页中&#xff0c;实现更加个性化的播放效果。CKplayer…

【cpolar】搭建我的世界Java版服务器,公网远程联机

&#x1f3a5; 个人主页&#xff1a;深鱼~&#x1f525;收录专栏&#xff1a;cpolar&#x1f304;欢迎 &#x1f44d;点赞✍评论⭐收藏 目录 前言&#xff1a; 1. 搭建我的世界服务器 1.1 服务器安装java环境 1.2 配置服务端 2. 测试局域网联机 3. 公网远程联机 3.1 安…

【Java程序员面试专栏 专业技能篇】Java SE核心面试指引(一):基础知识考察

关于Java SE部分的核心知识进行一网打尽,包括四部分:基础知识考察、面向对象思想、核心机制策略、Java新特性,通过一篇文章串联面试重点,并且帮助加强日常基础知识的理解,全局思维导图如下所示 本篇Blog为第一部分:基础知识考察,子节点表示追问或同级提问 基本概念 …

数据仓库高级面试题

数仓高内聚低耦合是怎么做的 定义 高内聚&#xff1a;强调模块内部的相对独立性&#xff0c;要求模块内部的元素尽可能的完成一个功能&#xff0c;不混杂其他功能&#xff0c;从而使模块保持简洁&#xff0c;易于理解和管理。 低耦合&#xff1a;模块之间的耦合度要尽可能的…

壹基金为爱同行走进绿水青山,为乡村儿童送去健康水

壹基金为爱同行公益践行活动发起于2013年,截至2022年底,累计有63,319名线下参与队员,走过了8个城市。2023年,为爱同行的“壹家人”再次出发,走进“绿水青山就是金山银山”理念诞生地——浙江安吉余村,徒步18公里,为乡村儿童喝上足量、干净的饮用水筹集善款。本次活动获得了当地…