从Golang的image.Image中检索像素数据,img.At()和rgba.Pix()哪个更快/更好?

huangapple go评论86阅读模式
英文:

Is img.At() or rgba.Pix() faster/better for retrieving pixel data from a Golang image.Image?

问题

从<https://stackoverflow.com/questions/33186783/get-a-pixel-array-from-from-golang-image-image>中阅读答案,我看到有两种获取像素RGBA的方法,一种是通过img.At(),另一种是通过rgba.Pix()

哪种方法更好?是否应该始终使用其中一种,还是有一些情况下应该使用其中一种而不是另一种?

英文:

Reading the answers from <https://stackoverflow.com/questions/33186783/get-a-pixel-array-from-from-golang-image-image>, I saw that there were two methods of pixel RGBA retrieval, via img.At() and rgba.Pix().

Which is better to use? Should one always be used, or are there cases where one should be used over the other and vice versa?

答案1

得分: 5

如果您的程序需要进行计算,并且需要使用大部分甚至全部的像素数据,那么rgba.Pix()的性能要比img.At()好得多。如果您只需要获取图像中单个或少数几个像素的数据,请使用img.At()(在这种情况下,计算rgba.Pix()的开销太高)。

下面是各种测试负载的结果,持续时间是每个样本的平均值(每个样本进行了10次采样)。

方法 1x1 1000x667 3840x2160 1000x667 + 计算 1000x667 只访问5x5像素
img.At() 195ns 30.211071ms 294.885396ms 853.345043ms 42.431μs
rgba.Pix() 719ns 7.786029ms 77.700552ms 836.480063ms 6.791461ms

我们可以看到,在 winy 1x1 图像和我们将 for 循环的上限限制为 5 的图像中,使用img.At()可以获得更快的执行时间。然而,在需要获取每个像素的用例中,rgba.Pix()的性能更好。随着我们对每个像素进行的计算越多,性能的提升就越不明显,因为总时间增加,img.At()rgba.Pix()之间的差异变得不那么明显,就像上表中的“1000x667 + 计算”所示。

这是使用的测试代码:

func main() {
	resp, err := http.Get("IMAGE URL GOES HERE")
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	img, _, err := image.Decode(resp.Body)
	if err != nil {
		panic(err)
	}

	var start time.Time
	var duration time.Duration
	samples := 10
	var sum time.Duration

	fmt.Println("Samples: ", samples)
	sum = time.Duration(0)
	for i := 0; i < samples; i++ {
		start = time.Now()
		usingAt(img)
		duration = time.Since(start)
		sum += duration
	}
	fmt.Println("*** At avg: ", sum/time.Duration(samples))

	sum = time.Duration(0)
	for i := 0; i < samples; i++ {
		start = time.Now()
		usingPix(img)
		duration = time.Since(start)
		sum += duration
	}
	fmt.Println("*** Pix avg: ", sum/time.Duration(samples))
}

func usingAt(img image.Image) {
	bounds := img.Bounds()
	width, height := bounds.Max.X, bounds.Max.Y

	for y := 0; y < height; y++ {
		for x := 0; x < width; x++ {
			r, g, b, _ := img.At(x, y).RGBA()
			_ = uint8(r >> 8)
			_ = uint8(g >> 8)
			_ = uint8(b >> 8)
		}
	}
}

func usingPix(img image.Image, targetColor colorful.Color) {
	bounds := img.Bounds()
	width, height := bounds.Max.X, bounds.Max.Y

	rgba := image.NewRGBA(bounds)
	draw.Draw(rgba, bounds, img, bounds.Min, draw.Src)
	for y := 0; y < height; y++ {
		for x := 0; x < width; x++ {
			index := (y*width + x) * 4
			pix := rgba.Pix[index : index+4]
			_ = pix[0]
			_ = pix[1]
			_ = pix[2]
		}
	}
}

在“1000x667 只访问5x5像素”中,将 for 循环中的heightwidth替换为5和5,限制了访问的像素数量。

在“1000x667 + 计算”中,实际上使用了 RGB 值,通过将每个像素的颜色与目标颜色进行比较,并使用go-colorful的 DE2000 计算来计算颜色距离。

英文:

If your program will be conducting a computation where you need a majority, if not all of the pixel data, then rgba.Pix() significantly outperforms img.At(). If you only need the pixel data of a single or few pixels in an image, use img.At() (the overhead of computing the rgba.Pix() prerequisite is too high in such cases).

Here are the results of various test loads, the durations of which are averaged over 10 samples each.

Method 1x1 1000x667 3840x2160 1000x667 + computation 1000x667 only 5x5 accessed
img.At() 195ns 30.211071ms 294.885396ms 853.345043ms 42.431μs
rgba.Pix() 719ns 7.786029ms 77.700552ms 836.480063ms 6.791461ms

We can see how for the tiny 1x1 image and the image where we limit our for loops to upper bounds of 5, using img.At() results in faster execution time. However, for use cases where every pixel is fetched, rgba.Pix() results in better performance. This improvement in performance is less evident the more computation we do with every pixel, as the total time increases and the difference between img.At() and rgba.Pix() becomes less obvious, as seen in "1000x667 + computation" in the table above.

Here is the test code used:

func main() {
resp, err := http.Get(&quot;IMAGE URL GOES HERE&quot;)
if err != nil {
panic(err)
}
defer resp.Body.Close()
img, _, err := image.Decode(resp.Body)
if err != nil {
panic(err)
}
var start time.Time
var duration time.Duration
samples := 10
var sum time.Duration
fmt.Println(&quot;Samples: &quot;, samples)
sum = time.Duration(0)
for i := 0; i &lt; samples; i++ {
start = time.Now()
usingAt(img)
duration = time.Since(start)
sum += duration
}
fmt.Println(&quot;*** At avg: &quot;, sum/time.Duration(samples))
sum = time.Duration(0)
for i := 0; i &lt; samples; i++ {
start = time.Now()
usingPix(img)
duration = time.Since(start)
sum += duration
}
fmt.Println(&quot;*** Pix avg: &quot;, sum/time.Duration(samples))
}
func usingAt(img image.Image) {
bounds := img.Bounds()
width, height := bounds.Max.X, bounds.Max.Y
for y := 0; y &lt; height; y++ {
for x := 0; x &lt; width; x++ {
r, g, b, _ := img.At(x, y).RGBA()
_ = uint8(r &gt;&gt; 8)
_ = uint8(g &gt;&gt; 8)
_ = uint8(b &gt;&gt; 8)
}
}
}
func usingPix(img image.Image, targetColor colorful.Color) {
bounds := img.Bounds()
width, height := bounds.Max.X, bounds.Max.Y
rgba := image.NewRGBA(bounds)
draw.Draw(rgba, bounds, img, bounds.Min, draw.Src)
for y := 0; y &lt; height; y++ {
for x := 0; x &lt; width; x++ {
index := (y*width + x) * 4
pix := rgba.Pix[index : index+4]
_ = pix[0]
_ = pix[1]
_ = pix[2]
}
}
}

1000x667 only 5x5 accessed replaced the height and width in the for loops with 5 and 5, limiting the number of pixels accessed.

1000x667 + computation actually used the RGB values by comparing each pixel's color distance from a target color with go-colorful's DE2000 calculation.

huangapple
  • 本文由 发表于 2022年1月7日 11:14:12
  • 转载请务必保留本文链接:https://go.coder-hub.com/70616183.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定