DFT 带阻滤波器

This commit is contained in:
nuknal
2024-06-21 12:47:14 +08:00
parent e13038474a
commit d99b8a9740
16 changed files with 292 additions and 309 deletions

View File

@@ -1,6 +1,7 @@
package rrc
import (
"os"
"testing"
"github.com/airbusgeo/godal"
@@ -9,11 +10,14 @@ import (
)
func TestFilter(t *testing.T) {
os.MkdirAll("/Users/lan/workspace/sjy01/image-proc/data/fft", 0755)
godal.RegisterAll()
tif := "/Users/lan/workspace/sjy01/image-proc/data/052022-no-rrc/010/PAN/SJY01_PAN_20240520_115428_052022_103_010_L1A.tiff"
tif = "/Users/lan/workspace/sjy01/image-proc/data/051622/007/PAN/SJY01_PAN_20240516_101236_051622_096_007_L1A.tiff"
// tif = "/Users/lan/workspace/sjy01/image-proc/data/051622/007/PAN/SJY01_PAN_20240516_101236_051622_096_007_L1A.tiff"
// tif = "/Users/lan/workspace/sjy01/image-proc/data/060622-mm/003/PAN/SJY01_PAN_20240606_012004_060622_065_003_L1A.tiff"
// tif = "/Users/lan/workspace/sjy01/image-proc/data/052022-no-rrc/010/MSS/SJY01_MSS_20240520_115428_052022_103_010_L1A.tiff"
tif = "/Users/lan/workspace/sjy01/image-proc/data/052022-no-rrc/006/PAN/SJY01_PAN_20240520_115428_052022_103_006_L1A.tiff"
tif = "/Users/lan/workspace/sjy01/image-proc/data/060622-RRC/002/PAN/SJY01_PAN_20240606_012004_060622_065_002_L1A.tiff"
ds, err := godal.Open(tif)
if err != nil {
t.Error(err)
@@ -37,7 +41,23 @@ func TestFilter(t *testing.T) {
}
}
m0 := HFNoiseFilter(img, float64(img.Cols())*0.45)
m0 := HFNoiseFilter(img, float64(img.Cols())*0.49)
defer m0.Close()
utils.SavePanToGDALGTiff(m0, 0, 0, "/Users/lan/workspace/sjy01/image-proc/data/fft/filtered.tiff", 1.3)
}
func TestMomentMatching(t *testing.T) {
raw := "/Users/lan/workspace/sjy01/preprocessing/demo/output/051622/SJY01_PAN_20240516_101236_051622_096.RAW"
data, err := os.ReadFile(raw)
if err != nil {
t.Error(err)
}
height := len(data) / (9344 * 2)
img, err := gocv.NewMatFromBytes(height, 9344, gocv.MatTypeCV16U, data)
if err != nil {
t.Error(err)
}
DoMomentMatching(img)
}

View File

@@ -1,12 +1,5 @@
package rrc
import (
"runtime"
"github.com/dustin/go-humanize"
log "github.com/sirupsen/logrus"
)
func searchVL(V []float64, Sik float64) int {
left, right := 0, len(V)-2
@@ -31,16 +24,3 @@ func searchVL(V []float64, Sik float64) int {
// Return -1 if no such position is found
return -1
}
var lastTotalFreed uint64
func printMemStats() {
var m runtime.MemStats
runtime.ReadMemStats(&m)
log.Printf("[Memory] Alloc = %v TotalAlloc=%v Just Freed = %v Sys = %v NumGc=%v",
humanize.Bytes(m.Alloc),
humanize.Bytes(m.TotalAlloc),
humanize.Bytes(((m.TotalAlloc - m.Alloc) - lastTotalFreed)),
humanize.Bytes(m.Sys), m.NumGC)
lastTotalFreed = m.TotalAlloc - m.Alloc
}

View File

@@ -4,69 +4,74 @@ import (
"image"
"math"
"github.com/sirupsen/logrus"
log "github.com/sirupsen/logrus"
"gocv.io/x/gocv"
"starwiz.cn/sjy01/image-proc/pkg/config"
)
// filter high frequence noise
func HFNoiseFilter(img gocv.Mat, radius float64) gocv.Mat {
logrus.Println("applying hf noise filter...")
imgFloat := gocv.NewMat()
img.ConvertTo(&imgFloat, gocv.MatTypeCV32F)
// rows, cols := img.Rows(), img.Cols()
// utils.SavePanToGDALGTiff(img, 0, 0, "/Users/lan/workspace/sjy01/image-proc/data/fft/original.tiff", 1.3)
minGrayVal, maxGrayVal, _, _ := gocv.MinMaxLoc(img)
log.Println("minGrayVal:", minGrayVal, "maxGrayVal:", maxGrayVal)
// 将图像转换为32位浮点型
img32f := gocv.NewMat()
img.ConvertTo(&img32f, gocv.MatTypeCV32F)
defer img32f.Close()
// 扩展图像尺寸使其宽高为2的幂次方
// optRows := gocv.GetOptimalDFTSize(img32f.Rows())
// optCols := gocv.GetOptimalDFTSize(img32f.Cols())
// padded := gocv.NewMatWithSize(optRows, optCols, gocv.MatTypeCV32F)
// defer padded.Close()
// gocv.CopyMakeBorder(img32f, &padded,
// 0, optRows-img32f.Rows(), 0, optCols-img32f.Cols(),
// gocv.BorderConstant, color.RGBA{0, 0, 0, 0})
// 获取图像尺寸
rows, cols := img32f.Rows(), img32f.Cols()
// zeros := gocv.NewMatFromScalar(gocv.NewScalar(0, 0, 0, 0), gocv.MatTypeCV32F)
// 执行 DFT
DFT := gocv.NewMat()
defer DFT.Close()
planes := gocv.NewMatWithSize(rows, cols, gocv.MatTypeCV32F)
img32f.ConvertTo(&planes, gocv.MatTypeCV32F)
gocv.Merge([]gocv.Mat{planes, gocv.Zeros(rows, cols, gocv.MatTypeCV32F)}, &DFT)
gocv.Merge([]gocv.Mat{img32f, gocv.Zeros(rows, cols, gocv.MatTypeCV32F)}, &DFT)
gocv.DFT(DFT, &DFT, gocv.DftComplexOutput)
fftshift(&DFT)
// 将复数部分分离并计算振幅光谱
pss := gocv.Split(DFT)
// SaveDFT(DFT, "/Users/lan/workspace/sjy01/image-proc/data/fft/dft.tiff")
// 应用滤波器
for y := 0; y < rows; y++ {
for x := 0; x < cols; x++ {
if math.Sqrt(math.Pow(float64(x-cols/2), 2)+math.Pow(float64(y-rows/2), 2)) > float64(radius) {
// if math.Abs(float64(x-cols/2)) < float64(radius) || math.Abs(float64(y-rows/2)) < float64(radius) {
pss[0].SetFloatAt(y, x, 0.0)
pss[1].SetFloatAt(y, x, 0.0)
}
}
}
gocv.Merge(pss, &DFT)
// SaveDFT(DFT, "/Users/lan/workspace/sjy01/image-proc/data/fft/dft_filtered.tiff")
// 中心化频谱
fftshift(&DFT)
// visualizeDFT(DFT, "/Users/lan/workspace/sjy01/image-proc/data/fft/dft.jpg")
// 低通滤波器
pss := gocv.Split(DFT)
defer pss[0].Close()
defer pss[1].Close()
filter := gocv.NewMatWithSize(rows, cols, gocv.MatTypeCV32F)
defer filter.Close()
// createLowPassFilter(&filter, radius)
// createBandBlockFilter(&filter, radius, radius+0.015*float64(cols))
createFixedBlockFilter(&filter, config.GCONFIG.Radiation.HFBandStopWidth)
gocv.Multiply(pss[0], filter, &pss[0])
gocv.Multiply(pss[1], filter, &pss[1])
gocv.Merge(pss, &DFT)
// visualizeDFT(DFT, "/Users/lan/workspace/sjy01/image-proc/data/fft/dft_filtered.jpg")
// 频谱位置还原
fftshift(&DFT)
// 执行 IDFT
IDFT := gocv.NewMat()
gocv.DFT(DFT, &IDFT, gocv.DftInverse|gocv.DftRealOutput)
// 归一化到0-65535
gocv.Normalize(IDFT, &IDFT, 0, 8192, gocv.NormMinMax)
// 归一化到原始图像范围
gocv.Normalize(IDFT, &IDFT, float64(minGrayVal), float64(maxGrayVal), gocv.NormMinMax)
IDFT.ConvertTo(&IDFT, gocv.MatTypeCV16U)
// 保存 IDFT 结果
return IDFT
}
// 创建低通滤波器
func createLowPassFilter(filter *gocv.Mat, radius int) {
func createLowPassFilter(filter *gocv.Mat, radius float64) {
rows, cols := filter.Rows(), filter.Cols()
center := image.Pt(cols/2, rows/2)
for y := 0; y < rows; y++ {
@@ -81,6 +86,38 @@ func createLowPassFilter(filter *gocv.Mat, radius int) {
}
}
// 创建带阻滤波器
func createBandBlockFilter(filter *gocv.Mat, radius0, radius1 float64) {
rows, cols := filter.Rows(), filter.Cols()
center := image.Pt(cols/2, rows/2)
for y := 0; y < rows; y++ {
for x := 0; x < cols; x++ {
dist := math.Sqrt(math.Pow(float64(x-center.X), 2) + math.Pow(float64(y-center.Y), 2))
if dist <= float64(radius0) || dist >= float64(radius1) {
filter.SetFloatAt(y, x, 1.0)
} else {
filter.SetFloatAt(y, x, 0.0)
}
}
}
}
func createFixedBlockFilter(filter *gocv.Mat, ksize int) {
rows, cols := filter.Rows(), filter.Cols()
center := image.Pt(cols/2, rows/2)
for y := 0; y < rows; y++ {
for x := 0; x < cols; x++ {
if (x >= center.X-ksize/2 && x <= center.X+ksize/2 &&
(y < int(ksize) || y >= rows-int(ksize))) ||
((x < ksize || x > cols-ksize) && (y >= center.Y-ksize/2 && y <= center.Y+ksize/2)) {
filter.SetFloatAt(y, x, 0.0)
} else {
filter.SetFloatAt(y, x, 1.0)
}
}
}
}
// fftshift 将频率直流分量移动到图像中心
func fftshift(input *gocv.Mat) {
cx, cy := input.Cols()/2, input.Rows()/2
@@ -100,19 +137,25 @@ func fftshift(input *gocv.Mat) {
tmp.CopyTo(&q2)
}
func SaveDFT(DFT gocv.Mat, file string) {
func visualizeDFT(DFT gocv.Mat, file string) {
pss := gocv.Split(DFT)
planes := gocv.NewMat()
defer planes.Close()
gocv.Magnitude(pss[0], pss[1], &planes)
// 进行对数尺度变换,便于可视化
ones := gocv.NewMatFromScalar(gocv.NewScalar(1, 0, 0, 0), gocv.MatTypeCV64F)
defer ones.Close()
gocv.Add(planes, ones, &planes)
gocv.Log(planes, &planes)
// 归一化
gocv.Normalize(planes, &planes, 0, 1, gocv.NormMinMax)
gocv.Normalize(planes, &planes, 0, 255, gocv.NormMinMax)
planes.ConvertTo(&planes, gocv.MatTypeCV8U)
out := gocv.NewMatWithSize(planes.Rows()/4, planes.Cols()/4, gocv.MatTypeCV8U)
defer out.Close()
gocv.Resize(planes, &out, image.Point{planes.Cols() / 4, planes.Rows() / 4}, 0, 0, gocv.InterpolationLinear)
// 保存DFT结果
gocv.IMWrite(file, planes)
gocv.IMWrite(file, out)
}

View File

@@ -8,6 +8,8 @@ import (
"gocv.io/x/gocv"
)
// FIXME: still need to implement the HFNoiseFilterFFT function
func HFNoiseFilterFFT(input gocv.Mat, radius int) gocv.Mat {
// 将灰度图像转换为浮点数数组
height := input.Rows()

View File

@@ -10,10 +10,11 @@ import (
// Destriping multisensor imagery with moment matching [Gadallah, 2000]
func DoMomentMatching(originalImg gocv.Mat) {
probes := originalImg.Cols()
log.Printf("do moment matching for %d probes", probes)
log.Printf("do moment matching for %d probes, %d rows", probes, originalImg.Rows())
// 第i个探元的像元均值和标准差
means := make([]float64, probes)
stds := make([]float64, probes)
// 计算每个探元的均值和标准差
for x := 0; x < originalImg.Cols(); x++ {
var total int64
@@ -34,16 +35,17 @@ func DoMomentMatching(originalImg gocv.Mat) {
}
// 列参考值和列参考标准差
var mu float64
var sig float64
var mu, sig float64
for x := 0; x < probes; x++ {
mu += means[x]
sig += stds[x]
}
mu = mu / float64(probes)
sig = sig / float64(probes)
log.Printf("mean reference value: %f, std reference value: %f", mu, sig)
// 修正 DN_adjusted[i] = (DN[i] - means[i]) *sig/stds[i]+mu
for x := 0; x < originalImg.Cols(); x++ {
for y := 0; y < originalImg.Rows(); y++ {

View File

@@ -10,6 +10,7 @@ import (
log "github.com/sirupsen/logrus"
"gocv.io/x/gocv"
"starwiz.cn/sjy01/image-proc/pkg/utils"
)
// Relative Radiation Correction
@@ -49,7 +50,7 @@ func (rrc *RRC) Close() {}
// 统计探元灰度的累积概率密度
func (rrc *RRC) StatisticalPAN(dsfile string) error {
printMemStats()
utils.PrintMemStats()
f, err := os.Open(dsfile)
if err != nil {
return err
@@ -100,7 +101,7 @@ func (rrc *RRC) StatisticalPAN(dsfile string) error {
rrc.Histograms[0].sum([]*ProbeHistogram{&hist})
hist.free()
runtime.GC()
printMemStats()
utils.PrintMemStats()
mutex.Unlock()
@@ -118,7 +119,7 @@ func (rrc *RRC) StatisticalPAN(dsfile string) error {
}
func (rrc *RRC) StatisticalMSS(dsfile string) error {
printMemStats()
utils.PrintMemStats()
f, err := os.Open(dsfile)
if err != nil {
return err
@@ -179,7 +180,7 @@ func (rrc *RRC) StatisticalMSS(dsfile string) error {
rrc.Histograms[i+1].sum([]*ProbeHistogram{&hist})
hist.free()
runtime.GC()
printMemStats()
utils.PrintMemStats()
mutex.Unlock()
}