Vulkan GPU图像处理之幂律(伽马)变换:Kompute框架实战与性能分析
一、定义
- 章节:第3章 灰度变换与空间滤波 → 3.2 基本灰度变换 → 3.2.3 幂律(伽马)变换
- 别名:幂律变换(Power‑Law Transformation)、伽马变换(Gamma Transformation)
公式
[s=crγ] [ s = c r^{\gamma} ][s=crγ]
- (r):输入灰度(归一化到 ([0,1]))
- (s):输出灰度(归一化后再映射回0–255)
- (c>0):比例常数(常用 (c=1))
- (gamma>0):伽马值,控制曲线形状
二、不同γ值的效果
1)γ < 1(如 0.4、0.6、0.8)
- 曲线上凸,在对角线之上
- 提亮图像、扩展暗部、压缩亮部
- 适用:整体偏暗、欠曝、暗部细节缺失的图像
2)γ = 1
- 直线 (s=r),恒等变换,无效果
3)γ > 1(如 1.5、2.0、2.5)
- 曲线下凹,在对角线之下
- 压暗图像、压缩暗部、扩展亮部
- 适用:整体偏亮、过曝、亮部细节缺失的图像
一句话记:
- γ小=提亮、救暗
- γ大=压暗、救亮
[s=crγ] [ s = c r^{\gamma} ][s=crγ]
三、与对数变换的区别
- 对数变换:
[(s=c∗log(1+r))] [(s=c * log(1+r))][(s=c∗log(1+r))]
强扩展暗部、压缩亮部,固定趋势 - 幂律变换:通过调节γ,可模拟对数(γ<1)或反向效果(γ>1),更灵活
四、典型应用
伽马校正(Gamma Correction)
- 显示器/相机/打印机的光电响应是非线性(幂律)
- 显示器通常γ≈2.2,导致图像偏暗;用γ≈0.45校正,使显示线性
欠曝/过曝修复
- 夜景/暗图:γ=0.5~0.8
- 强光/过曝:γ=1.5~2.5
对比度微调
- 不做剧烈拉伸,用γ小幅度调整明暗层次
五、代码实现
kompute实现
shader核心代码
#version 450layout(constant_id=0)const uint WIDTH=3840;layout(constant_id=1)const uint HEIGHT=2160;layout(local_size_x=16, local_size_y=16)in;layout(push_constant)uniform PushConstants{float gamma;}pc;layout(set=0, binding=0)buffer inputBuffer{float inputData[];};layout(set=0, binding=1)writeonly buffer outputBuffer{float outputData[];};voidmain(){uint x=gl_GlobalInvocationID.x;uint y=gl_GlobalInvocationID.y;if(x>=WIDTH||y>=HEIGHT){return;}uint index=y * WIDTH + x;float gray=inputData[index];float c=1.0;float gammaGray=c * pow(gray, pc.gamma);outputData[index]=gammaGray;}C++核心代码
intmain(intargc,char*argv[]){constintchannels=1;std::string inputPath;floatgamma=2.2f;if(argc>1){inputPath=argv[1];}else{inputPath="D:/tengyanbo/repo/kompute/examples/grayscale/build/Release/output.png";}if(argc>2){gamma=std::atof(argv[2]);}std::cout<<"======================================================"<<std::endl;std::cout<<" Image Gamma Transform Processing "<<std::endl;std::cout<<" s = c * r^gamma "<<std::endl;std::cout<<"======================================================"<<std::endl;std::cout<<std::endl;std::cout<<"Input image path: "<<inputPath<<std::endl;std::cout<<"Gamma value: "<<gamma<<std::endl;intimgWidth,imgHeight,imgChannels;unsignedchar*imgData=stbi_load(inputPath.c_str(),&imgWidth,&imgHeight,&imgChannels,0);if(!imgData){std::cout<<"Failed to load input image: "<<stbi_failure_reason()<<std::endl;std::cout<<"Creating test grayscale pattern..."<<std::endl;imgWidth=512;imgHeight=512;imgChannels=1;imgData=(unsignedchar*)malloc(imgWidth*imgHeight);for(inty=0;y<imgHeight;y++){for(intx=0;x<imgWidth;x++){intidx=y*imgWidth+x;imgData[idx]=(unsignedchar)((x*255)/imgWidth);}}}std::cout<<"Image size: "<<imgWidth<<" x "<<imgHeight<<std::endl;std::cout<<"Original image channels: "<<imgChannels<<std::endl;if(imgChannels!=1){std::cout<<"\n[ERROR] 伽马变换仅支持灰度图!"<<std::endl;std::cout<<"当前图像有 "<<imgChannels<<" 个通道, 不是灰度图。"<<std::endl;std::cout<<"请提供单通道灰度图像。"<<std::endl;stbi_image_free(imgData);return1;}std::cout<<"[OK] 检测到灰度图, 开始处理..."<<std::endl;std::cout<<"Input pixel [0] = "<<(int)imgData[0]<<std::endl;floatinputGray=imgData[0]/255.0f;std::vector<float>inputData(imgWidth*imgHeight);for(inti=0;i<imgWidth*imgHeight;i++){inputData[i]=imgData[i]/255.0f;}stbi_image_free(imgData);std::cout<<"Total pixels: "<<(imgWidth*imgHeight)<<", Data size: "<<inputData.size()<<" floats"<<std::endl;try{kp::Manager mgr;kp::Memory::MemoryTypes optimalType=detectOptimalMemoryType(mgr);std::cout<<std::endl;autoinputTensor=mgr.tensorT(inputData,optimalType);autooutputData=std::vector<float>(imgWidth*imgHeight*channels,0.0f);autooutputTensor=mgr.tensorT(outputData,optimalType);std::vector<std::shared_ptr<kp::Memory>>params={inputTensor,outputTensor};std::vector<uint32_t>shaderData=std::vector<uint32_t>(shader::GAMMA_TRANSFORM_COMP_SPV.begin(),shader::GAMMA_TRANSFORM_COMP_SPV.end());kp::Workgroup workgroup={(uint32_t)imgWidth,(uint32_t)imgHeight,1};std::vector<uint32_t>specConstants={(uint32_t)imgWidth,(uint32_t)imgHeight};std::vector<float>pushConstants={gamma};autoalgo=mgr.algorithm(params,shaderData,workgroup,specConstants,pushConstants);std::cout<<"\n========== 执行GPU计算 =========="<<std::endl;autostart=std::chrono::high_resolution_clock::now();mgr.sequence()->record<kp::OpSyncDevice>(params)->record<kp::OpAlgoDispatch>(algo)->record<kp::OpSyncLocal>(params)->eval();autoend=std::chrono::high_resolution_clock::now();autoduration=std::chrono::duration_cast<std::chrono::milliseconds>(end-start).count();std::cout<<"Total GPU processing time: "<<duration<<"ms"<<std::endl;std::cout<<"=============================="<<std::endl;constauto&outputVec=outputTensor->vector();std::cout<<"Output vector size: "<<outputVec.size()<<std::endl;floatc=1.0f;floatexpectedGray=c*std::pow(inputGray,gamma);std::cout<<"Expected gamma transform value: "<<expectedGray<<std::endl;std::cout<<"Output pixel [0] = "<<(int)(outputVec[0]*255)<<std::endl;unsignedchar*outputImg=(unsignedchar*)malloc(imgWidth*imgHeight);for(inti=0;i<imgWidth*imgHeight;i++){outputImg[i]=(unsignedchar)(outputVec[i]*255.0f);}stbi_write_png("output_gamma.png",imgWidth,imgHeight,channels,outputImg,imgWidth*channels);std::cout<<"Output saved to output_gamma.png (grayscale)"<<std::endl;free(outputImg);}catch(conststd::exception&e){std::cerr<<"Error: "<<e.what()<<std::endl;return1;}return0;}当γ=2.2时:
当γ=0.4时:
当γ=1时,也就是原图:
OpenCV实现
importcv2importnumpyasnpdefpower_law_transform(img,gamma=1.0,c=1.0):# 归一化到 [0,1]r=img/255.0# 幂律变换s=c*np.power(r,gamma)# 映射回 [0,255]s=np.clip(s*255,0,255).astype(np.uint8)returns# 读取灰度图img=cv2.imread("test.jpg",0)# 不同γ测试img_bright=power_law_transform(img,gamma=0.5)img_dark=power_law_transform(img,gamma=2.0)六、核心要点
- 公式:(\boldsymbol{s=cr^{\gamma}})
- γ<1:提亮、扩暗、压亮
- γ>1:压暗、缩暗、扩亮
- 与对数变换的差异:幂律可调,对数固定
- 核心应用:伽马校正、欠曝/过曝修复
更多内容,欢迎关注我的微信公众号:半夏之夜的无情剑客
