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

@@ -3,6 +3,7 @@ package main
import ( import (
"os" "os"
"path/filepath" "path/filepath"
"runtime"
"strings" "strings"
"github.com/airbusgeo/godal" "github.com/airbusgeo/godal"
@@ -74,6 +75,9 @@ var procCmd = &cobra.Command{
} }
reg.SaveScenesToTiff(panScenes, mssScenes) reg.SaveScenesToTiff(panScenes, mssScenes)
producer.CleanScenes(panScenes)
producer.CleanScenes(mssScenes)
runtime.GC()
if saveStrip { if saveStrip {
reg.SaveOriginalPanToGDALGTiff(reg.Params.PanTiffFile) reg.SaveOriginalPanToGDALGTiff(reg.Params.PanTiffFile)

20
config/config.yaml Normal file
View File

@@ -0,0 +1,20 @@
log_level: 5
coregistration:
mss_bands: 4
pixel_bytes: 2
mss_width: 2336
pan_width: 9344
cr_block_nw: 8
cr_block_nh: 4
overlapped_block_lines: 3000
cr_resample_method: "down_sample_pan"
fus_band_order: "RGB"
radiation:
pan_remove_hf_noise: true
mss_remove_hf_noise: true
hf_radius_ratio: 0.49
min_hist_level: 0.3
max_hist_level: 0.6
hf_band_stop_width: 24 # 带阻滤波器宽度
scene_moment_matching: false

View File

@@ -3,7 +3,8 @@ package config
import "github.com/sirupsen/logrus" import "github.com/sirupsen/logrus"
type Config struct { type Config struct {
CRConfig CoRegistrationConfig `yaml:"cr_config" mapstructure:"cr_config"` CoRegistration CoRegistrationConfig `yaml:"coregistration" mapstructure:"coregistration"`
Radiation RadiationConfig `yaml:"radiation" mapstructure:"radiation"`
LogLevel logrus.Level `yaml:"log_level" mapstructure:"log_level"` LogLevel logrus.Level `yaml:"log_level" mapstructure:"log_level"`
} }
@@ -16,7 +17,17 @@ type CoRegistrationConfig struct {
CRBlockNH int `yaml:"cr_block_nh" mapstructure:"cr_block_nh"` CRBlockNH int `yaml:"cr_block_nh" mapstructure:"cr_block_nh"`
OverlappedBlockLines int `yaml:"overlapped_block_lines" mapstructure:"overlapped_block_lines"` OverlappedBlockLines int `yaml:"overlapped_block_lines" mapstructure:"overlapped_block_lines"`
CRResampleMethod string `yaml:"cr_resample_method" mapstructure:"cr_resample_method"` CRResampleMethod string `yaml:"cr_resample_method" mapstructure:"cr_resample_method"`
FUSBandOrder string `yaml:"fu_band_order" mapstructure:"fu_band_order"` FUSBandOrder string `yaml:"fus_band_order" mapstructure:"fus_band_order"`
}
type RadiationConfig struct {
PANRemoveHfNoise bool `yaml:"pan_remove_hf_noise" mapstructure:"pan_remove_hf_noise"`
MSSRemoveHfNoise bool `yaml:"mss_remove_hf_noise" mapstructure:"mss_remove_hf_noise"`
SceneMomentMatching bool `yaml:"scene_moment_matching" mapstructure:"scene_moment_matching"`
HfRadiusRatio float64 `yaml:"hf_radius_ratio" mapstructure:"hf_radius_ratio"`
MinHistLevel float64 `yaml:"min_hist_level" mapstructure:"min_hist_level"`
MaxHistLevel float64 `yaml:"max_hist_level" mapstructure:"max_hist_level"`
HFBandStopWidth int `yaml:"hf_band_stop_width" mapstructure:"hf_band_stop_width"`
} }
var GCONFIG Config var GCONFIG Config
@@ -24,7 +35,7 @@ var GCONFIG Config
func init() { func init() {
GCONFIG = Config{ GCONFIG = Config{
LogLevel: logrus.DebugLevel, LogLevel: logrus.DebugLevel,
CRConfig: CoRegistrationConfig{ CoRegistration: CoRegistrationConfig{
MssBands: 4, MssBands: 4,
PixelBytes: 2, PixelBytes: 2,
PanWidth: 9344, PanWidth: 9344,
@@ -35,5 +46,14 @@ func init() {
CRResampleMethod: "down_sample_pan", CRResampleMethod: "down_sample_pan",
FUSBandOrder: "RGB", FUSBandOrder: "RGB",
}, },
Radiation: RadiationConfig{
PANRemoveHfNoise: true,
MSSRemoveHfNoise: true,
SceneMomentMatching: false,
MinHistLevel: 0.3,
MaxHistLevel: 0.6,
HfRadiusRatio: 0.49,
HFBandStopWidth: 24,
},
} }
} }

View File

@@ -113,6 +113,13 @@ func (r *Registrator) SetSceneBoundary(scene *Scene) (topLeft, bottomRight orb.P
xResolution := W0 / float64(scene.Width) xResolution := W0 / float64(scene.Width)
yResolution := H0 / float64(scene.Height) yResolution := H0 / float64(scene.Height)
scene.Meta.Gsd = math.Min(xResolution, yResolution) scene.Meta.Gsd = math.Min(xResolution, yResolution)
// FIXME: 临时设置分辨率
if scene.Meta.Gsd < 2 {
scene.Meta.Gsd = 1.3
} else {
scene.Meta.Gsd = 5.2
}
log.Debug("resolution x: ", xResolution) log.Debug("resolution x: ", xResolution)
log.Debug("resolution y: ", yResolution) log.Debug("resolution y: ", yResolution)

View File

@@ -1,78 +0,0 @@
package producer
import (
"image"
"math"
log "github.com/sirupsen/logrus"
"gocv.io/x/gocv"
)
func PANFilter(panImage16UC1 gocv.Mat) gocv.Mat {
log.Println("Applying PAN filter...")
// 转换为浮点数类型进行傅里叶变换
imgFloat := gocv.NewMat()
panImage16UC1.ConvertTo(&imgFloat, gocv.MatTypeCV32F)
// 使用傅里叶变换
planes := gocv.NewMat()
gocv.Merge([]gocv.Mat{imgFloat, gocv.NewMatWithSize(imgFloat.Rows(), imgFloat.Cols(), gocv.MatTypeCV32F)}, &planes)
dft := gocv.NewMat()
gocv.DFT(planes, &dft, gocv.DftComplexOutput)
// 转换DFT图像使低频分量位于中心
dftShifted := shiftDFT(dft)
// 创建掩膜
rows, cols := panImage16UC1.Rows(), panImage16UC1.Cols()
crow, ccol := rows/2, cols/2
mask := gocv.NewMatWithSize(rows, cols, gocv.MatTypeCV32FC2)
mask.SetTo(gocv.NewScalar(1.0, 1.0, 0.0, 0.0))
r := 30 // 调整这个参数来改变滤波效果
for i := 0; i < rows; i++ {
for j := 0; j < cols; j++ {
if math.Abs(float64(i-crow)) <= float64(r) && math.Abs(float64(j-ccol)) <= float64(r) {
mask.SetFloatAt(i, j*2, 0)
mask.SetFloatAt(i, j*2+1, 0)
}
}
}
// 应用掩膜并进行反向DFT
filtered := gocv.NewMat()
gocv.MulSpectrums(dftShifted, mask, &filtered, 0)
filteredShifted := shiftDFT(filtered)
idft := gocv.NewMat()
gocv.DFT(filteredShifted, &idft, gocv.DftInverse|gocv.DftRealOutput)
// 标准化图像到0-65535范围
gocv.Normalize(idft, &idft, 0, 65535, gocv.NormMinMax)
idft.ConvertTo(&idft, gocv.MatTypeCV16U)
return idft
}
// shiftDFT 将DFT结果进行频谱平移
func shiftDFT(src gocv.Mat) gocv.Mat {
rows, cols := src.Rows(), src.Cols()
crow, ccol := rows/2, cols/2
q0 := src.Region(image.Rect(0, 0, ccol, crow))
q1 := src.Region(image.Rect(ccol, 0, cols, crow))
q2 := src.Region(image.Rect(0, crow, ccol, rows))
q3 := src.Region(image.Rect(ccol, crow, cols, rows))
tmp := gocv.NewMatWithSize(crow, ccol, src.Type())
q0.CopyTo(&tmp)
q3.CopyTo(&q0)
tmp.CopyTo(&q3)
q1.CopyTo(&tmp)
q2.CopyTo(&q1)
tmp.CopyTo(&q2)
return src
}

View File

@@ -10,10 +10,11 @@ import (
"github.com/airbusgeo/godal" "github.com/airbusgeo/godal"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"gocv.io/x/gocv" "gocv.io/x/gocv"
"starwiz.cn/sjy01/image-proc/pkg/utils"
) )
func (r *Registrator) SaveOriginalPanToGDALGTiff(tiffFile string) error { func (r *Registrator) SaveOriginalPanToGDALGTiff(tiffFile string) error {
err := savePanToGDALGTiff(r.PanImage, 0, 0, tiffFile, PanResolution) err := utils.SavePanToGDALGTiff(r.PanImage, 0, 0, tiffFile, PanResolution)
if err != nil { if err != nil {
return err return err
} }
@@ -21,53 +22,11 @@ func (r *Registrator) SaveOriginalPanToGDALGTiff(tiffFile string) error {
return nil return nil
} }
func savePanToGDALGTiff(pan gocv.Mat, topLeftX, topLeftY float64, tiffFile string, resolution float64) error {
// log.Println("Saving PAN image to TIFF file:", tiffFile)
width := pan.Cols()
height := pan.Rows()
ds, err := godal.Create(godal.GTiff, tiffFile, 1, godal.UInt16, width, height)
if err != nil {
log.Error("Error creating TIFF file: ", err)
return err
}
defer ds.Close()
setGeoTransform(ds, topLeftX, topLeftY, resolution)
ds.SetMetadata("NBITS", "16")
// 将通道的数据转换为uint16数组
data := make([]uint16, width*height)
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
data[y*width+x] = uint16(pan.GetShortAt(y, x))
}
}
band := ds.Bands()[0]
band.SetColorInterp(godal.CIGray)
err = band.IO(godal.IOWrite,
0, 0,
data,
width, height,
godal.PixelSpacing(2),
godal.LineSpacing(width*2))
if err != nil {
log.Error("Failed to write data to band:", err)
return err
}
log.Info("Saved pan image to ", tiffFile)
return nil
}
func (r *Registrator) SaveRegisteredMssToGDALGTiff(tiffFile string) error { func (r *Registrator) SaveRegisteredMssToGDALGTiff(tiffFile string) error {
r.rgbirImage = gocv.NewMat() r.rgbirImage = gocv.NewMat()
gocv.Merge(r.registeredMssImages[:], &r.rgbirImage) gocv.Merge(r.registeredMssImages[:], &r.rgbirImage)
err := SaveBGRToGDALGTiff(r.rgbirImage, err := utils.SaveBGRToGDALGTiff(r.rgbirImage,
4, 0, 0, MssResolution, 4, 0, 0, MssResolution,
[]godal.ColorInterp{godal.CIBlue, godal.CIGreen, godal.CIRed, godal.CIUndefined}, []godal.ColorInterp{godal.CIBlue, godal.CIGreen, godal.CIRed, godal.CIUndefined},
tiffFile) tiffFile)
@@ -78,63 +37,6 @@ func (r *Registrator) SaveRegisteredMssToGDALGTiff(tiffFile string) error {
return nil return nil
} }
func SaveBGRToGDALGTiff(bgr gocv.Mat,
bands int,
topLeftX, topLeftY float64,
resolution float64,
colorInterps []godal.ColorInterp, tiffFile string) error {
width := bgr.Cols()
height := bgr.Rows()
// 创建一个二维切片来存储图像数据
data := make([][]uint16, bands)
for i := range data {
data[i] = make([]uint16, width*height)
}
// 从gocv.Mat中提取数据
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
for b := 0; b < bands; b++ {
data[b][y*width+x] = uint16(bgr.GetShortAt(y, x*bands+b))
}
}
}
ds, err := godal.Create(godal.GTiff,
tiffFile,
bands,
godal.UInt16,
width, height)
if err != nil {
log.Error("Error creating TIFF file: ", err)
return err
}
defer ds.Close()
// ds.SetMetadata("NBITS", "16")
setGeoTransform(ds, topLeftX, topLeftY, resolution)
for b := 0; b < bands; b++ {
band := ds.Bands()[b]
band.SetColorInterp(colorInterps[b])
err := band.IO(godal.IOWrite,
0, 0,
data[b],
width, height,
godal.PixelSpacing(2),
godal.LineSpacing(width*2))
if err != nil {
log.Error("Failed to write data to band:", err)
return err
}
}
log.Info("Saved BGR MSS to ", tiffFile)
return nil
}
func (r *Registrator) BytesToRaw(mssData []byte, filePath string) error { func (r *Registrator) BytesToRaw(mssData []byte, filePath string) error {
f, err := os.OpenFile(filePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0777) f, err := os.OpenFile(filePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0777)
if err != nil { if err != nil {

View File

@@ -13,7 +13,9 @@ import (
"github.com/paulmach/orb/geojson" "github.com/paulmach/orb/geojson"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"gocv.io/x/gocv" "gocv.io/x/gocv"
"starwiz.cn/sjy01/image-proc/pkg/config"
"starwiz.cn/sjy01/image-proc/pkg/rrc" "starwiz.cn/sjy01/image-proc/pkg/rrc"
"starwiz.cn/sjy01/image-proc/pkg/utils"
) )
type Scene struct { type Scene struct {
@@ -34,6 +36,12 @@ func (s *Scene) Cleanup() {
} }
} }
func CleanScenes(scenes []*Scene) {
for _, scene := range scenes {
scene.Cleanup()
}
}
// 对 PAN 和 配准后的MSS 在 Y 方向进行分景景之间有25%的重叠 // 对 PAN 和 配准后的MSS 在 Y 方向进行分景景之间有25%的重叠
// 默认分景大小: // 默认分景大小:
// MSS 2336 * 2336 - 1764 // MSS 2336 * 2336 - 1764
@@ -67,9 +75,18 @@ func (r *Registrator) SubScenes() (panScenes []*Scene, mssScenes []*Scene, err e
scene.SceneId = fmt.Sprintf("%s_%03d", name, i+1) scene.SceneId = fmt.Sprintf("%s_%03d", name, i+1)
mat := r.PanImage.Region(image.Rect(0, i*hPAN, 9344, y1)) mat := r.PanImage.Region(image.Rect(0, i*hPAN, 9344, y1))
matFiltered := rrc.HFNoiseFilter(mat, float64(mat.Cols())*0.45) if config.GCONFIG.Radiation.PANRemoveHfNoise {
scene.Mat = append(scene.Mat, matFiltered) log.Println("applying hf noise filter on", scene.SceneId)
matFiltered := rrc.HFNoiseFilter(mat, float64(mat.Cols())*config.GCONFIG.Radiation.HfRadiusRatio)
mat.Close() mat.Close()
mat = matFiltered
}
if config.GCONFIG.Radiation.SceneMomentMatching {
rrc.DoMomentMatching(mat)
}
scene.Mat = append(scene.Mat, mat)
panScenes = append(panScenes, scene) panScenes = append(panScenes, scene)
} }
@@ -95,16 +112,26 @@ func (r *Registrator) SubScenes() (panScenes []*Scene, mssScenes []*Scene, err e
continue continue
} }
for band := 0; band < 4; band++ {
mat := r.registeredMssImages[band].Region(image.Rect(0, i*hMSS, 2336, y1))
matFiltered := rrc.HFNoiseFilter(mat, float64(mat.Cols())*0.45)
mat.Close()
scene.Mat = append(scene.Mat, matFiltered)
}
name := filepath.Base(r.Params.MssTiffFile) name := filepath.Base(r.Params.MssTiffFile)
name = strings.TrimSuffix(name, ".tiff") name = strings.TrimSuffix(name, ".tiff")
scene.SceneId = fmt.Sprintf("%s_%03d", name, i+1) scene.SceneId = fmt.Sprintf("%s_%03d", name, i+1)
for band := 0; band < 4; band++ {
mat := r.registeredMssImages[band].Region(image.Rect(0, i*hMSS, 2336, y1))
if config.GCONFIG.Radiation.MSSRemoveHfNoise {
log.Println("applying hf noise filter on", scene.SceneId)
matFiltered := rrc.HFNoiseFilter(mat, float64(mat.Cols())*config.GCONFIG.Radiation.HfRadiusRatio/4)
mat.Close()
mat = matFiltered
}
if config.GCONFIG.Radiation.SceneMomentMatching {
rrc.DoMomentMatching(mat)
}
scene.Mat = append(scene.Mat, mat)
}
mssScenes = append(mssScenes, scene) mssScenes = append(mssScenes, scene)
} }
@@ -127,7 +154,7 @@ func (r *Registrator) SaveScenesToTiff(panScenes []*Scene, mssScenes []*Scene) e
scene.Meta = r.makeProductMeta(scene) scene.Meta = r.makeProductMeta(scene)
r.SetSceneBoundary(scene) r.SetSceneBoundary(scene)
err := savePanToGDALGTiff(scene.Mat[0], err := utils.SavePanToGDALGTiff(scene.Mat[0],
scene.Meta.Corners.UpperLeft.Longitude, scene.Meta.Corners.UpperLeft.Longitude,
scene.Meta.Corners.UpperLeft.Latitude, scene.Meta.Corners.UpperLeft.Latitude,
scene.Tiff, scene.Tiff,
@@ -175,7 +202,7 @@ func (r *Registrator) SaveScenesToTiff(panScenes []*Scene, mssScenes []*Scene) e
r.SetSceneBoundary(scene) r.SetSceneBoundary(scene)
rgbirImage, _ := r.MergeMSSToBGRNIR(scene.Mat) rgbirImage, _ := r.MergeMSSToBGRNIR(scene.Mat)
err := SaveBGRToGDALGTiff(rgbirImage, err := utils.SaveBGRToGDALGTiff(rgbirImage,
4, 4,
scene.Meta.Corners.UpperLeft.Longitude, scene.Meta.Corners.UpperLeft.Longitude,
scene.Meta.Corners.UpperLeft.Latitude, scene.Meta.Corners.UpperLeft.Latitude,

View File

@@ -2,55 +2,10 @@ package producer
import ( import (
"os" "os"
"time"
"github.com/airbusgeo/godal"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"starwiz.cn/sjy01/image-proc/pkg/calculator"
) )
func setGeoTransform(ds *godal.Dataset, topLeftLng, topLeftLat, resolution float64) {
// 转换分辨率(米到度)
resLat := resolution / calculator.MetersPerDegreeLatitude
resLng := calculator.MetersToDegreesLongitude(resolution, topLeftLat)
// 设置地理变换(假设左上角坐标为(0,0)PAN每个像素分辨率为1.2米)
geotransform := [6]float64{
topLeftLng, // top left x
resLng, // w-e pixel resolution
0, // rotation, 0 if image is "north up"
topLeftLat, // top left y
0, // rotation, 0 if image is "north up"
-resLat, // n-s pixel resolution (negative value)
}
err := ds.SetGeoTransform(geotransform)
if err != nil {
log.Errorf("Failed to set GeoTransform: %v", err)
return
}
// 设置投影为 WGS84
srs, err := godal.NewSpatialRefFromEPSG(4326) // gdal.CreateSpatialReference("")
if err != nil {
log.Errorf("Failed to set spatial reference: %v", err)
return
}
projWKT, err := srs.WKT()
if err != nil {
log.Errorf("Failed to convert spatial reference to WKT: %v", err)
return
}
err = ds.SetProjection(projWKT)
if err != nil {
log.Errorf("Failed to set projection: %v", err)
}
// 设置一些常见的元数据(可选)
ds.SetMetadata("TIFFTAG_DATETIME", time.Now().String())
ds.SetMetadata("TIFFTAG_SOFTWARE", "StarWiz-SJY01-IMAGE-PROC")
}
func sizeOfFile(file string) int64 { func sizeOfFile(file string) int64 {
fileInfo, err := os.Stat(file) fileInfo, err := os.Stat(file)
if err != nil { if err != nil {

View File

@@ -1,6 +1,7 @@
package rrc package rrc
import ( import (
"os"
"testing" "testing"
"github.com/airbusgeo/godal" "github.com/airbusgeo/godal"
@@ -9,11 +10,14 @@ import (
) )
func TestFilter(t *testing.T) { func TestFilter(t *testing.T) {
os.MkdirAll("/Users/lan/workspace/sjy01/image-proc/data/fft", 0755)
godal.RegisterAll() 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/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/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) ds, err := godal.Open(tif)
if err != nil { if err != nil {
t.Error(err) 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() defer m0.Close()
utils.SavePanToGDALGTiff(m0, 0, 0, "/Users/lan/workspace/sjy01/image-proc/data/fft/filtered.tiff", 1.3) 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 package rrc
import (
"runtime"
"github.com/dustin/go-humanize"
log "github.com/sirupsen/logrus"
)
func searchVL(V []float64, Sik float64) int { func searchVL(V []float64, Sik float64) int {
left, right := 0, len(V)-2 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 if no such position is found
return -1 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" "image"
"math" "math"
"github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"gocv.io/x/gocv" "gocv.io/x/gocv"
"starwiz.cn/sjy01/image-proc/pkg/config"
) )
// filter high frequence noise // filter high frequence noise
func HFNoiseFilter(img gocv.Mat, radius float64) gocv.Mat { func HFNoiseFilter(img gocv.Mat, radius float64) gocv.Mat {
logrus.Println("applying hf noise filter...") minGrayVal, maxGrayVal, _, _ := gocv.MinMaxLoc(img)
imgFloat := gocv.NewMat() log.Println("minGrayVal:", minGrayVal, "maxGrayVal:", maxGrayVal)
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)
// 将图像转换为32位浮点型 // 将图像转换为32位浮点型
img32f := gocv.NewMat() img32f := gocv.NewMat()
img.ConvertTo(&img32f, gocv.MatTypeCV32F) img.ConvertTo(&img32f, gocv.MatTypeCV32F)
defer img32f.Close() 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() rows, cols := img32f.Rows(), img32f.Cols()
// zeros := gocv.NewMatFromScalar(gocv.NewScalar(0, 0, 0, 0), gocv.MatTypeCV32F)
// 执行 DFT // 执行 DFT
DFT := gocv.NewMat() DFT := gocv.NewMat()
defer DFT.Close() defer DFT.Close()
gocv.Merge([]gocv.Mat{img32f, gocv.Zeros(rows, cols, gocv.MatTypeCV32F)}, &DFT)
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.DFT(DFT, &DFT, gocv.DftComplexOutput) 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) 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
IDFT := gocv.NewMat() IDFT := gocv.NewMat()
gocv.DFT(DFT, &IDFT, gocv.DftInverse|gocv.DftRealOutput) 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.ConvertTo(&IDFT, gocv.MatTypeCV16U)
// 保存 IDFT 结果
return IDFT return IDFT
} }
// 创建低通滤波器 // 创建低通滤波器
func createLowPassFilter(filter *gocv.Mat, radius int) { func createLowPassFilter(filter *gocv.Mat, radius float64) {
rows, cols := filter.Rows(), filter.Cols() rows, cols := filter.Rows(), filter.Cols()
center := image.Pt(cols/2, rows/2) center := image.Pt(cols/2, rows/2)
for y := 0; y < rows; y++ { 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 将频率直流分量移动到图像中心 // fftshift 将频率直流分量移动到图像中心
func fftshift(input *gocv.Mat) { func fftshift(input *gocv.Mat) {
cx, cy := input.Cols()/2, input.Rows()/2 cx, cy := input.Cols()/2, input.Rows()/2
@@ -100,19 +137,25 @@ func fftshift(input *gocv.Mat) {
tmp.CopyTo(&q2) tmp.CopyTo(&q2)
} }
func SaveDFT(DFT gocv.Mat, file string) { func visualizeDFT(DFT gocv.Mat, file string) {
pss := gocv.Split(DFT) pss := gocv.Split(DFT)
planes := gocv.NewMat() planes := gocv.NewMat()
defer planes.Close()
gocv.Magnitude(pss[0], pss[1], &planes) gocv.Magnitude(pss[0], pss[1], &planes)
// 进行对数尺度变换,便于可视化 // 进行对数尺度变换,便于可视化
ones := gocv.NewMatFromScalar(gocv.NewScalar(1, 0, 0, 0), gocv.MatTypeCV64F) ones := gocv.NewMatFromScalar(gocv.NewScalar(1, 0, 0, 0), gocv.MatTypeCV64F)
defer ones.Close()
gocv.Add(planes, ones, &planes) gocv.Add(planes, ones, &planes)
gocv.Log(planes, &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结果 // 保存DFT结果
gocv.IMWrite(file, planes) gocv.IMWrite(file, out)
} }

View File

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

View File

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

View File

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

21
pkg/utils/memory.go Normal file
View File

@@ -0,0 +1,21 @@
package utils
import (
"log"
"runtime"
"github.com/dustin/go-humanize"
)
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

@@ -47,7 +47,64 @@ func SavePanToGDALGTiff(pan gocv.Mat, topLeftX, topLeftY float64, tiffFile strin
return err return err
} }
log.Info("Saved pan image to ", tiffFile) log.Info("Saved image to ", tiffFile)
return nil
}
func SaveBGRToGDALGTiff(bgr gocv.Mat,
bands int,
topLeftX, topLeftY float64,
resolution float64,
colorInterps []godal.ColorInterp, tiffFile string) error {
width := bgr.Cols()
height := bgr.Rows()
// 创建一个二维切片来存储图像数据
data := make([][]uint16, bands)
for i := range data {
data[i] = make([]uint16, width*height)
}
// 从gocv.Mat中提取数据
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
for b := 0; b < bands; b++ {
data[b][y*width+x] = uint16(bgr.GetShortAt(y, x*bands+b))
}
}
}
ds, err := godal.Create(godal.GTiff,
tiffFile,
bands,
godal.UInt16,
width, height)
if err != nil {
log.Error("Error creating TIFF file: ", err)
return err
}
defer ds.Close()
// ds.SetMetadata("NBITS", "16")
setGeoTransform(ds, topLeftX, topLeftY, resolution)
for b := 0; b < bands; b++ {
band := ds.Bands()[b]
band.SetColorInterp(colorInterps[b])
err := band.IO(godal.IOWrite,
0, 0,
data[b],
width, height,
godal.PixelSpacing(2),
godal.LineSpacing(width*2))
if err != nil {
log.Error("Failed to write data to band:", err)
return err
}
}
log.Info("Saved BGR MSS to ", tiffFile)
return nil return nil
} }