From cab03827f004cebd8e0c40af90525500313728ea Mon Sep 17 00:00:00 2001 From: nuknal Date: Wed, 17 Jul 2024 12:45:29 +0800 Subject: [PATCH] constrast enhancement for browser image --- pkg/config/config.go | 12 ++++ pkg/producer/enhancement.go | 124 ++++++++++++++++++++++++++++++++++++ pkg/producer/jpg.go | 41 ++++++++---- 3 files changed, 164 insertions(+), 13 deletions(-) create mode 100644 pkg/producer/enhancement.go diff --git a/pkg/config/config.go b/pkg/config/config.go index 021b45d..cfb9a9a 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -8,6 +8,7 @@ import ( type Config struct { CoRegistration CoRegistrationConfig `yaml:"coregistration" mapstructure:"coregistration"` Radiation RadiationConfig `yaml:"radiation" mapstructure:"radiation"` + BrowserImg BrowserImgConfig `yaml:"browser_img" mapstructure:"browser_img"` LogLevel logrus.Level `yaml:"log_level" mapstructure:"log_level"` } @@ -33,6 +34,12 @@ type RadiationConfig struct { HFBandStopWidth int `yaml:"hf_band_stop_width" mapstructure:"hf_band_stop_width"` } +type BrowserImgConfig struct { + Enhancement string `yaml:"enhancement" mapstructure:"enhancement"` + CumulativeCutLower float64 `yaml:"cumulative_cut_lower" mapstructure:"cumulative_cut_lower"` + CumulativeCutUpper float64 `yaml:"cumulative_cut_upper" mapstructure:"cumulative_cut_upper"` +} + var GCONFIG Config func init() { @@ -58,5 +65,10 @@ func init() { HfRadiusRatio: 0.49, HFBandStopWidth: 24, }, + BrowserImg: BrowserImgConfig{ + Enhancement: "StretchToCumulativeCutMinMax", + CumulativeCutLower: 0.01, + CumulativeCutUpper: 0.99, + }, } } diff --git a/pkg/producer/enhancement.go b/pkg/producer/enhancement.go new file mode 100644 index 0000000..0856752 --- /dev/null +++ b/pkg/producer/enhancement.go @@ -0,0 +1,124 @@ +package producer + +import ( + "gocv.io/x/gocv" +) + +const ( + NoEnhancement = "NoEnhancement" + StretchToMinimumMaximum = "StretchToMinimumMaximum" + StretchToCumulativeCutMinMax = "StretchToCumulativeCutMinMax" +) + +type ContrastEnhancement struct { + minimumValue int + maximumValue int + lookupTableOffset int + lookupTable []int + mRasterDataTypeRange int + enhancementType string + //!< Range is [ min + cumulativeCutLower() * (max - min), min + cumulativeCutUpper() * (max - min) ] + cumulativeCutLower float64 + cumulativeCutUpper float64 +} + +func NewContrastEnhancement(minimumValue, maximumValue int) *ContrastEnhancement { + ce := &ContrastEnhancement{ + enhancementType: StretchToMinimumMaximum, + cumulativeCutLower: 0, + cumulativeCutUpper: 1, + } + ce.minimumValue = minimumValue + int(ce.cumulativeCutLower)*(maximumValue-minimumValue) + ce.maximumValue = minimumValue + int(ce.cumulativeCutUpper)*(maximumValue-minimumValue) + ce.mRasterDataTypeRange = ce.maximumValue - ce.minimumValue + ce.lookupTableOffset = -1 * ce.minimumValue + ce.lookupTable = make([]int, ce.mRasterDataTypeRange+1) + ce.generateLookupTable() + + return ce +} + +func (ce ContrastEnhancement) enhance(value int) int { + if ce.enhancementType != NoEnhancement { + shiftedValue := value + ce.lookupTableOffset + if shiftedValue >= 0 && shiftedValue < ce.mRasterDataTypeRange+1 { + return ce.lookupTable[shiftedValue] + } + return 0 + } + + return ce.linearMinMaxEnhancement(value) +} + +func (ce *ContrastEnhancement) generateLookupTable() { + for i := range ce.lookupTable { + ce.lookupTable[i] = ce.linearMinMaxEnhancement(i - ce.lookupTableOffset) + } +} + +func (ce ContrastEnhancement) linearMinMaxEnhancement(value int) int { + stretchedValue := (float64(value-ce.minimumValue) / float64(ce.mRasterDataTypeRange)) * 255.0 + if stretchedValue < 0 { + return 0 + } else if stretchedValue > 255 { + return 255 + } + return int(stretchedValue) +} + +func cumulativeCountCutEnhancement(img gocv.Mat, cumulativeCutLower, cumulativeCutUpper float64) gocv.Mat { + dataTypeMax := 65536 + // 计算直方图 + hist := make([]int, dataTypeMax) + for y := 0; y < img.Rows(); y++ { + for x := 0; x < img.Cols(); x++ { + hist[uint16(img.GetShortAt(y, x))]++ + } + } + + // 计算累积直方图 + cdf := make([]int, dataTypeMax) + cdf[0] = hist[0] + for i := 1; i < dataTypeMax; i++ { + cdf[i] = cdf[i-1] + hist[i] + } + + // 计算低高阈值 + totalPixels := img.Rows() * img.Cols() + lowCutoff := int(float64(totalPixels) * cumulativeCutLower) + highCutoff := int(float64(totalPixels) * cumulativeCutUpper) + + var lowerBound, upperBound int + for i, v := range cdf { + if v > lowCutoff { + lowerBound = i + break + } + } + for i := dataTypeMax - 1; i >= 0; i-- { + if cdf[i] < highCutoff { + upperBound = i + break + } + } + + // 重新映射灰度值 + enhancedImg := gocv.NewMatWithSize(img.Rows(), img.Cols(), gocv.MatTypeCV8U) + for y := 0; y < img.Rows(); y++ { + for x := 0; x < img.Cols(); x++ { + oldVal := uint16(img.GetShortAt(y, x)) + newVal := (int(oldVal) - lowerBound) * 255 / (upperBound - lowerBound) + if newVal > 255 { + newVal = 255 + } + if newVal < 0 { + newVal = 0 + } + + enhancedImg.SetUCharAt(y, x, uint8(newVal)) + } + } + + return enhancedImg + +} diff --git a/pkg/producer/jpg.go b/pkg/producer/jpg.go index 1b1f08b..b729214 100644 --- a/pkg/producer/jpg.go +++ b/pkg/producer/jpg.go @@ -8,6 +8,7 @@ import ( "github.com/airbusgeo/godal" log "github.com/sirupsen/logrus" "gocv.io/x/gocv" + "starwiz.cn/sjy01/image-proc/pkg/config" ) func GTiffToJPG(ftiff, fjpg string, reversed bool) error { @@ -58,12 +59,24 @@ func GTiffToJPG(ftiff, fjpg string, reversed bool) error { channels := gocv.Split(img) for i, ch := range channels { - // 2. 计算图像的最小值和最大值 - minVal, maxVal, _, _ := gocv.MinMaxLoc(ch) - // 3. 将16位图像线性拉伸到8位图像 - scale := 255.0 / (maxVal - minVal) - shift := -minVal * scale - ch.ConvertToWithParams(&channels[i], gocv.MatTypeCV8U, scale, shift) + switch config.GCONFIG.BrowserImg.Enhancement { + case NoEnhancement: + case StretchToMinimumMaximum: + minVal, maxVal, _, _ := gocv.MinMaxLoc(ch) + ce := NewContrastEnhancement(int(minVal), int(maxVal)) + for y := 0; y < height; y++ { + for x := 0; x < width; x++ { + value := int(uint16(ch.GetShortAt(y, x))) + value = ce.enhance(value) + ch.SetShortAt(y, x, int16(value)) + } + } + case StretchToCumulativeCutMinMax: + channels[i] = cumulativeCountCutEnhancement(ch, + config.GCONFIG.BrowserImg.CumulativeCutLower, + config.GCONFIG.BrowserImg.CumulativeCutUpper) + ch.Close() + } } img8bit := gocv.NewMat() @@ -82,14 +95,16 @@ func GTiffToJPG(ftiff, fjpg string, reversed bool) error { image.Point{X: img8bit.Cols() / 2, Y: img8bit.Rows() / 2}, 0, 0, gocv.InterpolationCubic) + gocv.IMWrite(fjpg, img8bit) + // 7. 应用伽玛校正提升亮度 - gammaCorrected := applyGammaCorrection(img8bit, 1.6) // 伽玛值 - defer gammaCorrected.Close() - ok := gocv.IMWriteWithParams(fjpg, gammaCorrected, []int{gocv.IMWriteJpegOptimize, 1}) - if !ok { - err = fmt.Errorf("error saving %s", fjpg) - return err - } + // gammaCorrected := applyGammaCorrection(img8bit, 1.6) // 伽玛值 + // defer gammaCorrected.Close() + // ok := gocv.IMWriteWithParams(fjpg, gammaCorrected, []int{gocv.IMWriteJpegOptimize, 1}) + // if !ok { + // err = fmt.Errorf("error saving %s", fjpg) + // return err + // } return nil }