英文:
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 循环中的height
和width
替换为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("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 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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论