CLAHE

CLAHE 使用的一些经验

直方图包含了什么信息

两张我觉得不错的图像

两张我觉得有雾或者曝光不足的图像

单从直方图无法准确判断出一张图质量如何

一张好的图片往往拥有如下特征:

最暗值从0开始,最大值接近或者是255,每一个亮度都有一定分布。

不好的图片往往有如下特征:

某个亮度(0 或者 255)有很多很多像素

0附近亮度空缺,也就是全图缺少暗点

画面主体(人物)或者总画面平均亮度过低,过高

什么是CLAHE

原理

在一定限制下,将输入的直方图尽可能的均衡,让其每个亮度拥有差不多个数的像素。

这个限制是指,不能将映射折线的斜率过大,也就是不能将某个亮度过度放大或者缩小。

实现方式(不考虑分块)

  1. 计算输入图像的直方图

  2. 根据clip- limited 裁剪掉超过 clip-limited * total pixel / 256的像素

  3. 将裁减掉的像素均分给每一个亮度,形成新的直方图

  4. 计算新直方图的累计分布曲线(LUT)

  5. 该曲线就是CLAHE的映射曲线,使用该曲线对原图进行映射

算法本身缺陷

好的直方图不一定是均衡的。尤其是分块之后,小块内直方图不均衡很正常。

由于clip- limited的存在,算法对那种个别亮度拥有过多像素的case不合适

clip- limited选择与图像及其效果

理论上,当图像亮度分布十分均匀,每个亮度都有1/256 * totalpixel的时候,直方图均衡化算法无效,但实际中的图片哪怕曝光,色彩内容都很好很丰富,还是会存在一些不均匀的。

原图\clip limit 0.2 0.4 0.8 1 1.4 1.8 2.5
比较均匀 无改变 逐渐增加 逐渐增加 逐渐增加 逐渐增加 程度相同 程度相同
轻微不匀 逐渐增加 逐渐增加 逐渐增加 逐渐增加 逐渐增加 逐渐增加 程度相同
不均 逐渐增加 程度相同
严重不均 逐渐增加

output_yuv_clahe_04_1_histograms.jpg

output_yuv_clahe_1_1_histograms.jpg

output_yuv_clahe_2_1_histograms.jpg

随着clip limited的增加(0.4 1 2)直方图分布看似越来越均衡,但是画面质量却在下降

直观效果

当图片某段曝光缺失(比如过暗,过亮),那clahe会稍微拉会一些平均亮度

当图片主体还算正常,但是暗部亮度像素相对较少(这种情况很常见,也许原图是好图片),clahe会随着clip limited的增大,更夸张的把中暗部压暗,中亮度提亮,拉伸对比度。

由于映射是离散的,当(a-1)/255 * total < CFD(a) < (a+1)/255 * total时 a不会发生变化;当CFD(a-1) = (a-1)/255 * total , CDF(a) = (a+1)/255 * total 时,也就是a亮度有很多像素,a会被映射到a+1 而a-1还保持a-1,a亮度本身出现像素空缺。

YUV full vs limited 601 vs 709

RGB

RGB 不区分 601 709 或者 full limited, 对于sRGB来说 最亮就应该是255, 最暗就应该是0。肤色要贴近真实场景,和真实场景没有色调偏移。

RGB 2 YUV

在rgb转化成YUV的时候,存在不同的转化公式,可以将YUV转化成 full 或者 limited,色彩空间也可以是601 或者 709

1. RGB 2 YUV BT.601 full(标清)
  • 适用于 SDTV(480i/576i)、DVD 等。

  • 转换矩阵

    Y=0.299R+0.587G+0.114B

    U=−0.169R−0.331G+0.500B+128

    V=0.500R−0.419G−0.081B+128​

  • 色度(UV)范围:0-255(Full Range 下)。

2. RGB 2 YUV BT.709 full(高清)
  • 适用于 HDTV(720p/1080p)、蓝光、流媒体等。

  • 转换矩阵

    Y=0.2126R+0.7152G+0.0722B

    U=−0.1146R−0.3854G+0.500B+128

    V=0.500R−0.4542G−0.0458B+128​

  • 色度(UV)范围:0-255(Full Range 下)。

3. BT.601 Limited Range

Y=(0.299R+0.587G+0.114B)×219​/255+16

U=(−0.169R−0.331G+0.500B)×224​/255+128

V=(0.500R−0.419G−0.081B)×224​/255​+128​

注:UV 的 +128 偏移在 Limited Range 下会自动调整为 16-240。

4. BT.709 Limited Range

​Y=(0.2126R+0.7152G+0.0722B)×219/255​+16

U=(−0.1146R−0.3854G+0.500B)×224/255​+128

V=(0.500R−0.4542G−0.0458B)×224/255​+128​

为什么存在 limited
  • 广播电视行业(BT.601/BT.709/BT.2020)
    早期的模拟电视信号为了减少噪声干扰和留出余量(如同步信号),将 YUV 的亮度(Y)限制在 16~235,色度(UV)限制在 16~240(8-bit 下),而非完整的 0~255。这一规范被数字电视(如 DVD、蓝光、有线电视)沿用,称为 Limited Range 或 TV Range

  • 硬件设备优化:部分显示设备(如老式 CRT)的电路设计基于 Limited Range,直接兼容更高效。

为什么有601 709两种色彩空间
  • BT.601(1982年制定)
    针对 标清电视(SDTV) 设计,分辨率主要为 480i/576i(NTSC/PAL),用于早期的 CRT 电视和 DVD 时代。

    • 色彩空间基于当时模拟电视的技术限制,色度采样通常为 4:2:2 或 4:2:0

    • 主要目标:兼容模拟信号,优化带宽效率。

  • BT.709(1990年制定,后更新)
    针对 高清电视(HDTV) 设计,分辨率支持 720p/1080i/1080p,用于蓝光、数字电视和流媒体。

    • 色彩空间适应更高分辨率和数字显示设备(如液晶、等离子)。

    • 主要目标:提升色彩表现,匹配现代显示技术。

YUV2RGB

YUV有四种格式 分别是 full/limited 601/709,如果要转化成正确的RGB,需要采用对应的公式转化,如果采用的公式错误,可能带来色彩偏差。

常见色彩偏差

如 BT.601 内容在 BT.709 显示下偏绿或饱和度过高

709的内容在601下播放可能偏红,紫

full的数据在limited的模式下播放,暗部亮部分会损失,对比度过强

limited的数据在full的模式下播放,色彩昏暗,亮度无法达到255

git merge vs rebase

git 修改之前的某次commit 注释和常见问题和解决方法_git修改某次commit的comment-CSDN博客

git rebase 用法详解与工作原理 | Shall We Code?

git merge

有两个分支 branchA branchB ,初始时,branchA在C1 节点 基于此节点,拉出branchB

以下操作按照时间顺序

对branchB进行修改 C2

对branchA进行修改 C3

对branchB进行修改 C4

将branchB merge 到branch A

可以看出merge的树是以不同branch体现的,不能完整体现时间顺序

git rebase

此时 reset一下merge操作:

随后将branchB 通过rebase的方式合并到branchA

可以看出 先将branchA的head移动到最新的branchB,再将branchA的修改添加回branchA。合并操作不会产生新的节点

对于单独一个分支的rebase

假如我们对当前分支的某次历史提交执行 rebase,其结果就是会将这次提交之后的所有提交重新应用在当前分支,在交互模式下,即允许我们对这些提交进行更改。

git rebase -i HEAD~4

可以看到后面的提交都变了ID

git rebase -i HEAD~6

多rebase几个,但是修改还是修改同样的地方,那还是修改后的节点ID改变

利用rebase 修改历史提交的comment

对于一直使用rebase操作的代码,修改历史comment十分简单,

比如我想修改branchB C2 那次的log

git rebase -i HEAD~3

这件事不会影响branchB 也不会影响branchA的提交顺序。但是会导致branchA重新增加comment的提交以及之后的提交的hashID改变

对于使用merge的代码进行message修改,就比较复杂

此时要修改branchB 中C2 节点 9d3282c 的名字

git rebase -i HEAD~3

似乎列出的是branchA分支上最后三次的操作,当然也包含branchB上的

会失败,原因是:rebase之后相当于把branchB C2 C4两个节点的内容加入到 branchA C3这个节点后,在对比banechB C2 9d3282c 和 22279e0时 出现了不一致的问题。在此猜测 能merge成功是因为merge对比的是8f4cda7 和 22279e0。

先abort这次rebase, 恢复原样

git rebase -i HEAD~2 去修改C4节点 message 8f4cda7 也不行, 感觉是branchA已经包含了branchB那些修改。再搞个干净的分支。

新来一个分支 验证merge的代码如何修改message

现在想修改branchC newnode2 0b21277 那次message

git rebase -i HEAD~2

首先 修改了该提交的message, 其次,也运行了rebase。 将branchC的改动接到了branchA后面 (注意 如果是rebase branchC 那么是把branchA改动接到C后面。 但是我们这里运行rebase head 也就是rebase branchA 那么 就是把C的改动接到A后面。

反正本地rebase 不对劲就–abort

intptr VS int32 or int64

intptr int32
在32位机器下等同于int32 占32位,
在64位机器下等同于int64 占64位
占32位
指针偏移的时候用intptr
比如intptr strideS; pSrc += strideS
此时strideS 存储的数还是一样大小,也不会超出int32的范围,至少写汇编的时候能注意到这里
但是存储地址的时候 就得用intptr address = &(int a)
计数的时候用int32就够了。
比如for (int32 i = 0; i < (int32)120;i++)
申请内存大小的时候 buffersize也是用int32
运算的时候也是int32

CMake 简单用法

编译:

编译时,编译器需要的是语法的正确,函数与变量的声明的正确。对于后者,通常是你需要告诉编译器头文件的所在位置(头文件中应该只是声明,而定义应该放在C/C++文件中),只要所有的语法正确,编译器就可以编译出中间目标文件。一般来说,每个源文件都应该对应于一个中间目标文件( .o 文件或 .obj 文件)。

链接:

链接时,主要是链接函数和全局变量。所以,我们可以使用这些中间目标文件( .o 文件或 .obj 文件)来链接我们的应用程序。链接器并不管函数所在的源文件,只管函数的中间目标文件(Object File),在大多数时候,由于源文件太多,编译生成的中间目标文件太多,而在链接时需要明显地指出中间目标文件名,这对于编译很不方便。所以,我们要给中间目标文件打个包,在Windows下这种包叫“库文件”(Library File),也就是 .lib 文件,在UNIX下,是Archive File,也就是 .a 文件。

CMake 定位

其他Make 工具 CMake
GNU Make ,QT 的 qmake ,微软的 MS nmake,BSD Make(pmake),Makepp CMake
对应着不同的Makefile 采用平台统一的CMakeLists.txt 生成自适应的Makefile
make Makefile 编译 :不同产商的make各不相同,也有不同的语法,但其本质都是在 “文件依赖性”上做文章 make Makefile 编译

CMake用法

Makefile

工程编译规则,描述了整个工程的编译和链接等规则。其中包含了哪些文件需要编译,哪些文件不需要编译,哪些需要先编译,哪些需要重建等。

在生成makefile这步时 会进行语法检查么 会进行.cpp 到.o 么 ?不会。makefile只是编译规则 还没开始编译。makefile命令中就包含了调用gcc(也可以是别的编译器)去编译某个源文件的命令。

Make

make工具就根据makefile中的命令进行编译和链接的。生成.o 这类文件,再进一步链接生成.exe这类文件。

参考

Makefile 简介 - Makefile基本概念介绍 - Makefile 简明教程 | 宅学部落

概述 &mdash; 跟我一起写Makefile 1.0 文档

Linux Makefile:使用CMake生成Makefile文件_cmake写makefile-CSDN博客

常见操作的耗时

在c release层面

分辨率 H*W 1280 720 耗时(MS) 包含基本操作
3*3 avg filter 普通写法 1.02 读写 +: 8  / :1
5*5 avg filter 普通写法 2.54 读写 + 24 /:1
3*3 winsum avg filter 0.93 读写 +: 2 /: 1 other prepare for window
5*5 winsum avg filter 0.94 读写 +: 2 /: 1 other prepare for window
2 to 1 down sample 0.23 读写
1/4 H*W upsample (video filter) 1.52 读写 +:4  / :5
1/16 H*W upsample(video filter) 1.51 读写 +:4  / :5
1/4 H*W upsample (specialRatio_bilinear up) 0.60 读写 +:4  / :1
1/16 H*W upsample (specialRatio_bilinear up) 0.44 读写 +:4  / :1
3*3 avg filter 1/4 HxW 普通写法 0.26
5*5 avg filter 1/4 HxW winsum 0.24

手写单指令多数据 armv8 加速效果

分辨率 H*W 1280 720 耗时(MS) 包含基本操作
3*3 avg filter 普通写法
5*5 avg filter 普通写法
3*3 winsum avg filter 0.13
5*5 winsum avg filter 0.14
2 to 1 down sample
1/4 H*W upsample (video filter)
1/16 H*W upsample(video filter)
1/4 H*W upsample (specialRatio_bilinear up)
1/16 H*W upsample (specialRatio_bilinear up)
3*3 avg filter 256x144 winsum 0.01
5*5 avg filter  256x144 winsum 0.01

单指令多数据常用操作 intrinsic

mla 乘加或者乘减比分开做要快

循环费时

arm intrinsic 指令查询技巧

查询指令的网址

ARM Neon Intrinsics各函数介绍_arm neon intrinsic 函数_June_Hou的博客-CSDN博客

https://developer.arm.com/architectures/instruction-sets/intrinsics/#q=vmovl_high_u16

总的来说

正常指令 操作64位 指令后加q了的话128位

长指令 指令后面加l ,结果比两个操作数要长

宽指令 指令后面加w 两种数据类型不一样相加减等 生成的数据类型取较宽的那个

窄指令 指令后面加hn 对应的长指令 加减之后 结果比原本两个一样类型的输入要窄

饱和指令 指令后面加q 自动限制在数据范围内

基本运算

乘法 mul

乘加 mla

加法 add

减法 sub

位移 shl shr 

取大小 min max

绝对值 vabs 

取反 vneg

和查表有关的:tb 但只支持表格很小?unit8x8这种table + index

比较

vceq (vector compare equal)

vcge a>=b? (Greater equal

vcle   a<=b? 

Vcgt a>b greater than

Vclt a<b

Vcage |a| >= |b| (vector compare absolute greater equal

r = |a - b| vabd vector absolute difference 

R = a + |b -c| vaba absolute difference and accumulate

位运算

r = a|(~b) vorn_s8   or not

R = ~a & b vbic_s8 bitwise clear

R = a ^ b veor_s8 exclusive or

R = a | b vorr_s8 or

R = a & b vand_s8 and

R = ~a vmvn_s8

赋值or读写

vdup_n_s8 duplicate a scalar into every element of vector ??? Dup mov???

Dup是把一个数据复制多份的关键词

Ld 则是正常load 的关键词

Mov是 拿到一些高位 或者地位 就是操作数和结果位宽不同?(好像也不一定 偶尔和dup等价?

Get是 把unit816 拆成uint88 x2 这种 位宽相同 并行个数不同 vget_high vget_low

cvt convert数据类型

combine 把8个*2 合并成16个

int8x16_t vcombine_s(int8x8_t a,int8x8_t b)

create 从uint64 create uint8x8

banding artifact

banding 现象

单帧

图像平坦渐变区域,出现条带状变化

多帧

随着时间推进,条带状变化移动

banding 造成原因和解决办法

量化

色彩空间转化时,中间空间的量化: RGB 2 YUV 2 RGB

中间运算中的量化: Y‘ = kY + mU + nV

尽量避免中间运算的量化,尤其避免中间运算的多步量化

拉伸

拉伸会导致原来+1 的数据+2 Y‘ = kY k>1

压缩会导致原来不规则(-1 -2 随机)变化的数据 规则的-1 Y‘ = kY k<1

不合理的拉伸公式:UV = f(y), 导致在RGB域无法连续变化

避免映射曲线的斜率过大或者过小,注意多步变化相当于曲线相乘,进一步增大斜率

过于规则

平坦区域如果变化过于规则,视觉也能看到banding

加噪音

多帧

随着时间的推移,输入在渐变,但是经过上述的异常引入,表现出banding 移动