1. 概述
在图片处理中,光线照明和去除噪点是两个常见的操作。本文将介绍如何使用Golang进行这两个操作。
2. 光线照明
2.1 图像的通道
在介绍光线照明之前,需要了解一下图像的通道。一个图像可以包含多个通道,常见的有三个通道:红色、绿色和蓝色。我们可以将一幅图像看成一个三维矩阵,其中每个元素表示一个像素点的强度值。对于RGB图像,每个元素包含三个值,分别为R、G、B通道的强度值。
图像通道是进行图片处理的基础。
package main
import (
"fmt"
"image"
"image/color"
"image/jpeg"
"os"
)
func main() {
// 读取图片
file, err := os.Open("test.jpg")
if err != nil {
panic(err)
}
defer file.Close()
img, err := jpeg.Decode(file)
if err != nil {
panic(err)
}
// 获取图片的宽和高
bounds := img.Bounds()
width, height := bounds.Max.X, bounds.Max.Y
// 遍历每个像素,并处理图像通道
for i := 0; i < width; i++ {
for j := 0; j < height; j++ {
r, g, b, _ := img.At(i, j).RGBA()
// 处理图像通道
// ...
newColor := color.RGBA{uint8(r), uint8(g), uint8(b), 255}
img.Set(i, j, newColor)
}
}
// 保存图片
newFile, err := os.Create("new.jpg")
if err != nil {
panic(err)
}
defer newFile.Close()
err = jpeg.Encode(newFile, img, &jpeg.Options{Quality: 100})
if err != nil {
panic(err)
}
fmt.Println("图片处理完成!")
}
2.2 简单的光线照明
简单的光线照明是指通过增加或减少图像通道的强度值来改变图像的亮度。下面的代码演示了如何将图像的亮度增加50%:
package main
import (
"fmt"
"image"
"image/color"
"image/jpeg"
"os"
)
func main() {
// 读取图片
file, err := os.Open("test.jpg")
if err != nil {
panic(err)
}
defer file.Close()
img, err := jpeg.Decode(file)
if err != nil {
panic(err)
}
// 获取图片的宽和高
bounds := img.Bounds()
width, height := bounds.Max.X, bounds.Max.Y
// 遍历每个像素,并处理图像通道
for i := 0; i < width; i++ {
for j := 0; j < height; j++ {
r, g, b, a := img.At(i, j).RGBA()
r = uint32(float64(r) * 1.5)
g = uint32(float64(g) * 1.5)
b = uint32(float64(b) * 1.5)
newColor := color.RGBA{uint8(r >> 8), uint8(g >> 8), uint8(b >> 8), uint8(a >> 8)}
img.Set(i, j, newColor)
}
}
// 保存图片
newFile, err := os.Create("new.jpg")
if err != nil {
panic(err)
}
defer newFile.Close()
err = jpeg.Encode(newFile, img, &jpeg.Options{Quality: 100})
if err != nil {
panic(err)
}
fmt.Println("图片处理完成!")
}
需要注意:
通道值必须在0~255的范围内。
因为通道值是uint32类型,所以在进行运算之前需要先将其转换为float64类型。
处理后的通道值必须在0~65535的范围内。
2.3 高级光线照明
高级光线照明是指通过计算光线对物体的影响来模拟真实的光照效果。下面的代码演示了如何使用Golang进行光线照明:
package main
import (
"fmt"
"image"
"image/color"
"image/jpeg"
"math"
"os"
)
// 向量类型
type Vector struct {
X, Y, Z float64
}
// 计算两个向量的叉积
func Cross(v1, v2 Vector) Vector {
return Vector{
X: v1.Y*v2.Z - v1.Z*v2.Y,
Y: v1.Z*v2.X - v1.X*v2.Z,
Z: v1.X*v2.Y - v1.Y*v2.X,
}
}
// 向量相减
func Sub(v1, v2 Vector) Vector {
return Vector{
X: v1.X - v2.X,
Y: v1.Y - v2.Y,
Z: v1.Z - v2.Z,
}
}
// 向量点积
func Dot(v1, v2 Vector) float64 {
return v1.X*v2.X + v1.Y*v2.Y + v1.Z*v2.Z
}
// 向量模长
func Length(v Vector) float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y + v.Z*v.Z)
}
// 向量标准化
func Normalize(v Vector) Vector {
l := Length(v)
return Vector{
X: v.X / l,
Y: v.Y / l,
Z: v.Z / l,
}
}
// 球体数据类型
type Sphere struct {
Center Vector
Radius float64
}
// 计算射线与球体的交点
func (s Sphere) Intersect(rayPos, rayDir Vector) (float64, Vector) {
oc := Sub(rayPos, s.Center)
k1 := Dot(rayDir, rayDir)
k2 := 2.0 * Dot(oc, rayDir)
k3 := Dot(oc, oc) - s.Radius*s.Radius
discriminant := k2*k2 - 4.0*k1*k3
if discriminant < 0.0 {
return -1.0, Vector{}
}
t1 := (-k2 + math.Sqrt(discriminant)) / (2.0 * k1)
t2 := (-k2 - math.Sqrt(discriminant)) / (2.0 * k1)
if t1 > 0.0 && t2 > 0.0 {
if t1 < t2 {
p := Sub(rayPos, Vector{rayDir.X * t1, rayDir.Y * t1, rayDir.Z * t1})
norm := Normalize(Sub(p, s.Center))
return t1, norm
} else {
p := Sub(rayPos, Vector{rayDir.X * t2, rayDir.Y * t2, rayDir.Z * t2})
norm := Normalize(Sub(p, s.Center))
return t2, norm
}
} else if t1 > 0.0 {
p := Sub(rayPos, Vector{rayDir.X * t1, rayDir.Y * t1, rayDir.Z * t1})
norm := Normalize(Sub(p, s.Center))
return t1, norm
} else if t2 > 0.0 {
p := Sub(rayPos, Vector{rayDir.X * t2, rayDir.Y * t2, rayDir.Z * t2})
norm := Normalize(Sub(p, s.Center))
return t2, norm
}
return -1.0, Vector{}
}
// 场景类型
type Scene struct {
Spheres []Sphere
}
// 光线跟踪函数
func trace(rayPos, rayDir Vector, scene Scene) color.RGBA {
// 设置背景颜色
bgColor := color.RGBA{0, 0, 0, 255}
// 计算场景中的所有球体与射线的交点
tmin := math.Inf(+1)
var hitSphere Sphere
for _, sphere := range scene.Spheres {
t, _ := sphere.Intersect(rayPos, rayDir)
if t > 0.0 && t < tmin {
tmin = t
hitSphere = sphere
}
}
// 如果没有相交,则返回背景颜色
if tmin == math.Inf(+1) {
return bgColor
}
// 计算表面颜色
color := color.RGBA{255, 255, 255, 255}
lightPos := Vector{0.0, 2.0, -4.0}
lightColor := color.RGBA{255, 255, 255, 255}
p := Vector{rayPos.X + rayDir.X*tmin, rayPos.Y + rayDir.Y*tmin, rayPos.Z + rayDir.Z*tmin}
n := hitSphere.Intersect(rayPos, rayDir)[1]
ld := Normalize(Sub(lightPos, p))
vd := Normalize(Sub(Vector{0.0, 0.0, 0.0}, p))
halfVec := Normalize(Vector{ld.X + vd.X, ld.Y + vd.Y, ld.Z + vd.Z})
diffuse := lightColor
specular := lightColor
a := 0.1
ambient := color.RGBA{uint8(a * float64(color.R)), uint8(a * float64(color.G)), uint8(a * float64(color.B)), 255}
if Dot(ld, n) < 0.0 {
diffuse.R, diffuse.G, diffuse.B = 0, 0, 0
specular.R, specular.G, specular.B = 0, 0, 0
} else {
diffuseFactor := Dot(ld, n)
diffuse.R = uint8(float64(diffuse.R) * diffuseFactor)
diffuse.G = uint8(float64(diffuse.G) * diffuseFactor)
diffuse.B = uint8(float64(diffuse.B) * diffuseFactor)
specularFactor := math.Pow(Dot(halfVec, n), 20.0) * 255
specular.R = uint8(float64(specular.R) * specularFactor)
specular.G = uint8(float64(specular.G) * specularFactor)
specular.B = uint8(float64(specular.B) * specularFactor)
}
color.R = uint8(float64(diffuse.R+ambient.R) / 2.0)
color.G = uint8(float64(diffuse.G+ambient.G) / 2.0)
color.B = uint8(float64(diffuse.B+ambient.B) / 2.0)
color.R += specular.R
color.G += specular.G
color.B += specular.B
if color.R > 255 {
color.R = 255
}
if color.G > 255 {
color.G = 255
}
if color.B > 255 {
color.B = 255
}
return color
}
func main() {
// 创建场景
scene := Scene{
Spheres: []Sphere{
Sphere{Vector{2.0, 0.0, -10.0}, 2.0},
Sphere{Vector{0.0, 0.0, -10.0}, 2.0},
Sphere{Vector{-2.0, 0.0, -10.0}, 2.0},
},
}
// 创建画布
width, height := 640, 480
img := image.NewRGBA(image.Rect(0, 0, width, height))
// 设置相机参数
var eyePos Vector = Vector{0.0, 0.0, 0.0}
var viewDir Vector = Vector{0.0, 0.0, -1.0}
var upVector Vector = Vector{0.0, 1.0, 0.0}
var fov float64 = math.Pi * 60.0 / 180.0
var aspect float64 = float64(width) / float64(height)
var tan_fov_2 float64 = math.Tan(fov / 2.0)
var rightVector Vector = Normalize(Cross(viewDir, upVector))
var upCorrectedVector Vector = Normalize(Cross(rightVector, viewDir))
var pixAspect float64 = (1.0 / aspect) * (0.5 / tan_fov_2)
// 遍历画布上每个像素,计算对应的颜色
for x := 0; x < width; x++ {
for y := 0; y < height; y++ {
rayDirX := rightVector.X*(((float64(x)+0.5)/float64(width))-0.5) +
upCorrectedVector.X*(((float64(y)+0.5)/float64(height))-0.5) +
viewDir.X*pixAspect
rayDirY := rightVector.Y*(((float64(x)+0.5)/float64(width))-0.5) +
upCorrectedVector.Y*(((float64(y)+0.5)/float64(height))-0.5) +
viewDir.Y*pixAspect
rayDirZ := rightVector.Z*(((float64(x)+0.5)/float64(width))-0.5) +
upCorrectedVector.Z*(((float64(y)+0.5)/float64(height))-0.5) +
viewDir.Z*pixAspect
rayDir := Normalize(Vector{rayDirX, rayDirY, rayDirZ})
c := trace(eyePos, rayDir, scene)
img.Set(x, y, c)
}
}
// 保存图片
newFile, err := os.Create("new.jpg")
if err != nil {
panic(err)
}
defer newFile.Close()
err = jpeg.Encode(newFile, img, &jpeg.Options{Quality: 100})
if err != nil {
panic(err)
}
fmt.Println("图片处理完成!")
}
3. 去除噪点
去除噪点是指将图像中的噪点、斑点和纹理等细小部分进行平滑处理,使其更加清晰。下面的代码演示了如何使用高斯模糊算法来去除噪点:
package main
import (
"fmt"
"image"
"image/color"
"image/draw"
"image/jpeg"
"math"
"os"
)
// 构建高斯滤波器
func makeGaussianFilter(sigma float64) []float64 {
size := int(math.Ceil(3*sigma)) + 1
filter := make([]float64, size)
sum := 0.0
for i := 0; i < size; i++ {
x := float64(i) - float64(size-1)/2.0
filter[i] = math.Exp(-(x*x)/(2*sigma*sigma)) / (math.Sqrt(2*math.Pi) * sigma)
sum += filter[i]
}
// 归一化
for i := 0; i < size; i++ {
filter[i] /= sum
}
return filter
}
// 高斯滤波函数
func gaussianFilter(img *image.NRGBA, sigma float64) *image.NRGBA {
// 根据sigma构建高斯滤波器
filter := makeGaussianFilter(sigma)
// 取得滤波器尺寸
size := len(filter)
// 创建和源图像相同的空白图像
w := img.Bounds().Max.X
h := img.Bounds().Max.Y
result := image.NewNRGBA(image.Rect(0, 0, w, h))
// 水平方向滤波
for y := 0; y < h; y++ {
for x := 0; x < w; x++ {
sumR, sumG, sumB, sumA := 0.0, 0.0, 0.0, 0.0
for i := 0; i < size; i++ {
srcX := x - size/2 + i
if srcX < 0 {