如何在 python OpenCV 中逐像素有效地遍历图像?
How to efficiently loop over an image pixel by pixel in python OpenCV?
我想做的是使用每个像素值逐个像素地遍历一个图像,在另一个相应的图像中画一个圆。
我的做法如下:
it = np.nditer(pixels, flags=['multi_index'])
while not it.finished:
y, x = it.multi_index
color = it[0]
it.iternext()
center = (x*20 + 10, y*20 + 10) # corresponding circle center
cv2.circle(circles, center, int(8 * color/255), 255, -1)
这样循环有点慢。我尝试添加 numba 的 @njit 装饰器,但显然它与 opencv 有问题。
输入图像为 32x32 像素
它们映射到 32x32 圆圈的输出图像
每个圆圈都绘制在一个 20x20 像素的正方形内
即输出图像为640x640像素
一张图片转换成圆形大约需要 100 毫秒,我希望将其降低到 30 毫秒或更低
有什么建议吗?
时间:
- 处理图纸
- 可能选项的数量不超过常识值(本例中:256)
- 速度很重要(我想一直都是这样)
- 没有其他限制阻止这种方法
最好的方法是“缓存”绘图(预先绘制它们(或根据所需的开销按需绘制)在另一个数组中),并且当绘图通常应该发生时,只需从中获取适当的绘图缓存并将其放置在目标区域(如@ChristophRackwitz 在其中一条评论中所述),这是一个非常快的 NumPy 操作(与绘图相比)。
附带说明一下,这是一种通用方法,不一定限于绘图。
但是您声称得到的结果:~100 毫秒 每张 32x32 图像( 640x640圈一个),对我来说没有任何意义(因为OpenCV也很快,1024个圈应该没什么大不了的),所以我创建了一个程序来说服自己。
code00.py:
#!/usr/bin/env python
import itertools as its
import sys
import time
import cv2
import numpy as np
def draw_img_orig(arr_in, arr_out, *args):
factor = round(arr_out.shape[0] / arr_in.shape[0])
factor_2 = factor // 2
it = np.nditer(arr_in, flags=["multi_index"])
while not it.finished:
y, x = it.multi_index
color = it[0]
it.iternext()
center = (x * factor + factor_2, y * factor + factor_2) # corresponding circle center
cv2.circle(arr_out, center, int(8 * color / 255), 255, -1)
def draw_img_regular_iter(arr_in, arr_out, *args):
factor = round(arr_out.shape[0] / arr_in.shape[0])
factor_2 = factor // 2
for row_idx, row in enumerate(arr_in):
for col_idx, col in enumerate(row):
cv2.circle(arr_out, (col_idx * factor + factor_2, row_idx * factor + factor_2), int(8 * col / 255), 255, -1)
def draw_img_cache(arr_in, arr_out, *args):
factor = round(arr_out.shape[0] / arr_in.shape[0])
it = np.nditer(arr_in, flags=["multi_index"])
while not it.finished:
y, x = it.multi_index
yf = y * factor
xf = x *factor
arr_out[yf: yf + factor, xf: xf + factor] = args[0][it[0]]
it.iternext()
def generate_input_images(shape, count, dtype=np.uint8):
return np.random.randint(256, size=(count,) + shape, dtype=dtype)
def generate_circles(shape, dtype=np.uint8, func=lambda x: int(8 * x / 255), color=255):
ret = np.zeros((256,) + shape, dtype=dtype)
cy = shape[0] // 2
cx = shape[1] // 2
for idx, arr in enumerate(ret):
cv2.circle(arr, (cx, cy), func(idx), color, -1)
return ret
def test_draw(imgs_in, img_out, count, draw_func, *draw_func_args):
print("\nTesting {:s}".format(draw_func.__name__))
start = time.time()
for i, e in enumerate(its.cycle(range(imgs_in.shape[0]))):
draw_func(imgs_in[e], img_out, *draw_func_args)
if i >= count:
break
print("Took {:.3f} seconds ({:d} images)".format(time.time() - start, count))
def test_speed(shape_in, shape_out, dtype=np.uint8):
imgs_in = generate_input_images(shape_in, 50, dtype=dtype)
#print(imgs_in.shape, imgs_in)
img_out = np.zeros(shape_out, dtype=dtype)
circles = generate_circles((shape_out[0] // shape_in[0], shape_out[1] // shape_in[1]))
count = 250
test_draw(imgs_in, img_out, count, draw_img_orig)
test_draw(imgs_in, img_out, count, draw_img_regular_iter)
test_draw(imgs_in, img_out, count, draw_img_cache, circles)
def test_accuracy(shape_in, shape_out, dtype=np.uint8):
img_in = np.arange(np.product(shape_in), dtype=dtype).reshape(shape_in)
circles = generate_circles((shape_out[0] // shape_in[0], shape_out[1] // shape_in[1]))
data = (
(draw_img_orig, "orig.png", None),
(draw_img_regular_iter, "regit.png", None),
(draw_img_cache, "cache.png", circles),
)
imgs_out = [np.zeros(shape_out, dtype=dtype) for _ in range(len(data))]
for idx, (draw_func, out_name, other_arg) in enumerate(data):
draw_func(img_in, imgs_out[idx], other_arg)
cv2.imwrite(out_name, imgs_out[idx])
for idx, img in enumerate(imgs_out[1:], start=1):
if not np.array_equal(img, imgs_out[0]):
print("Image index different: {:d}".format(idx))
def main(*argv):
dt = np.uint8
shape_in = (32, 32)
factor_io = 20
shape_out = tuple(i * factor_io for i in shape_in)
test_speed(shape_in, shape_out, dtype=dt)
test_accuracy(shape_in, shape_out, dtype=dt)
if __name__ == "__main__":
print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
64 if sys.maxsize > 0x100000000 else 32, sys.platform))
rc = main(*sys.argv[1:])
print("\nDone.")
sys.exit(rc)
备注:
除了你使用 np.nditer 的实现(我把它放在一个叫做 draw_img_orig[= 的函数中95=]),我又创建了 2 个:
迭代输入数组Pythonicly (draw_img_regular_iter)
一个使用缓存的圈子,并通过 np.nditer 进行迭代(draw_img_cache)
就测试而言,有 2 个 - 每个都在 3 个(以上)方法中的每一个上执行:
速度:测量处理大量图像所花费的时间
准确性:测量包含区间 [0, 255][=101] 的 32x32 输入的输出=](4次)
输出:
[cfati@CFATI-5510-0:e:\Work\Dev\Whosebug\q071818080]> sopr.bat
### Set shorter prompt to better fit when pasted in Whosebug (or other) pages ###
[prompt]> dir /b
code00.py
[prompt]> "e:\Work\Dev\VEnvs\py_pc064_03.09_test0\Scripts\python.exe" code00.py
Python 3.9.9 (tags/v3.9.9:ccb0e6a, Nov 15 2021, 18:08:50) [MSC v.1929 64 bit (AMD64)] 064bit on win32
Testing draw_img_orig
Took 0.908 seconds (250 images)
Testing draw_img_regular_iter
Took 1.061 seconds (250 images)
Testing draw_img_cache
Took 0.426 seconds (250 images)
Done.
[prompt]>
[prompt]> dir /b
cache.png
code00.py
orig.png
regit.png
上面是速度测试结果:正如所见,你的方法用了不到一秒的时间来处理 250 张图像!!!所以我是对的,我没有知道你的缓慢来自哪里,但它不是来自这里(也许你的测量有误?)。
常规方法有点慢,而缓存的方法 ~2X 更快。
我 运行 我笔记本电脑上的代码:
- 赢得 10 pc064
- CPU:Intel i7 6820HQ @ 2.70GHz(相当旧)
- GPU:不相关,因为我在执行期间没有注意到任何峰值
关于精度测试,所有 (3) 个输出数组都是相同的(没有消息另有说明),这是一张保存的图像:
我想做的是使用每个像素值逐个像素地遍历一个图像,在另一个相应的图像中画一个圆。
it = np.nditer(pixels, flags=['multi_index'])
while not it.finished:
y, x = it.multi_index
color = it[0]
it.iternext()
center = (x*20 + 10, y*20 + 10) # corresponding circle center
cv2.circle(circles, center, int(8 * color/255), 255, -1)
这样循环有点慢。我尝试添加 numba 的 @njit 装饰器,但显然它与 opencv 有问题。
输入图像为 32x32 像素 它们映射到 32x32 圆圈的输出图像 每个圆圈都绘制在一个 20x20 像素的正方形内 即输出图像为640x640像素
一张图片转换成圆形大约需要 100 毫秒,我希望将其降低到 30 毫秒或更低
有什么建议吗?
时间:
- 处理图纸
- 可能选项的数量不超过常识值(本例中:256)
- 速度很重要(我想一直都是这样)
- 没有其他限制阻止这种方法
最好的方法是“缓存”绘图(预先绘制它们(或根据所需的开销按需绘制)在另一个数组中),并且当绘图通常应该发生时,只需从中获取适当的绘图缓存并将其放置在目标区域(如@ChristophRackwitz 在其中一条评论中所述),这是一个非常快的 NumPy 操作(与绘图相比)。
附带说明一下,这是一种通用方法,不一定限于绘图。
但是您声称得到的结果:~100 毫秒 每张 32x32 图像( 640x640圈一个),对我来说没有任何意义(因为OpenCV也很快,1024个圈应该没什么大不了的),所以我创建了一个程序来说服自己。
code00.py:
#!/usr/bin/env python
import itertools as its
import sys
import time
import cv2
import numpy as np
def draw_img_orig(arr_in, arr_out, *args):
factor = round(arr_out.shape[0] / arr_in.shape[0])
factor_2 = factor // 2
it = np.nditer(arr_in, flags=["multi_index"])
while not it.finished:
y, x = it.multi_index
color = it[0]
it.iternext()
center = (x * factor + factor_2, y * factor + factor_2) # corresponding circle center
cv2.circle(arr_out, center, int(8 * color / 255), 255, -1)
def draw_img_regular_iter(arr_in, arr_out, *args):
factor = round(arr_out.shape[0] / arr_in.shape[0])
factor_2 = factor // 2
for row_idx, row in enumerate(arr_in):
for col_idx, col in enumerate(row):
cv2.circle(arr_out, (col_idx * factor + factor_2, row_idx * factor + factor_2), int(8 * col / 255), 255, -1)
def draw_img_cache(arr_in, arr_out, *args):
factor = round(arr_out.shape[0] / arr_in.shape[0])
it = np.nditer(arr_in, flags=["multi_index"])
while not it.finished:
y, x = it.multi_index
yf = y * factor
xf = x *factor
arr_out[yf: yf + factor, xf: xf + factor] = args[0][it[0]]
it.iternext()
def generate_input_images(shape, count, dtype=np.uint8):
return np.random.randint(256, size=(count,) + shape, dtype=dtype)
def generate_circles(shape, dtype=np.uint8, func=lambda x: int(8 * x / 255), color=255):
ret = np.zeros((256,) + shape, dtype=dtype)
cy = shape[0] // 2
cx = shape[1] // 2
for idx, arr in enumerate(ret):
cv2.circle(arr, (cx, cy), func(idx), color, -1)
return ret
def test_draw(imgs_in, img_out, count, draw_func, *draw_func_args):
print("\nTesting {:s}".format(draw_func.__name__))
start = time.time()
for i, e in enumerate(its.cycle(range(imgs_in.shape[0]))):
draw_func(imgs_in[e], img_out, *draw_func_args)
if i >= count:
break
print("Took {:.3f} seconds ({:d} images)".format(time.time() - start, count))
def test_speed(shape_in, shape_out, dtype=np.uint8):
imgs_in = generate_input_images(shape_in, 50, dtype=dtype)
#print(imgs_in.shape, imgs_in)
img_out = np.zeros(shape_out, dtype=dtype)
circles = generate_circles((shape_out[0] // shape_in[0], shape_out[1] // shape_in[1]))
count = 250
test_draw(imgs_in, img_out, count, draw_img_orig)
test_draw(imgs_in, img_out, count, draw_img_regular_iter)
test_draw(imgs_in, img_out, count, draw_img_cache, circles)
def test_accuracy(shape_in, shape_out, dtype=np.uint8):
img_in = np.arange(np.product(shape_in), dtype=dtype).reshape(shape_in)
circles = generate_circles((shape_out[0] // shape_in[0], shape_out[1] // shape_in[1]))
data = (
(draw_img_orig, "orig.png", None),
(draw_img_regular_iter, "regit.png", None),
(draw_img_cache, "cache.png", circles),
)
imgs_out = [np.zeros(shape_out, dtype=dtype) for _ in range(len(data))]
for idx, (draw_func, out_name, other_arg) in enumerate(data):
draw_func(img_in, imgs_out[idx], other_arg)
cv2.imwrite(out_name, imgs_out[idx])
for idx, img in enumerate(imgs_out[1:], start=1):
if not np.array_equal(img, imgs_out[0]):
print("Image index different: {:d}".format(idx))
def main(*argv):
dt = np.uint8
shape_in = (32, 32)
factor_io = 20
shape_out = tuple(i * factor_io for i in shape_in)
test_speed(shape_in, shape_out, dtype=dt)
test_accuracy(shape_in, shape_out, dtype=dt)
if __name__ == "__main__":
print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
64 if sys.maxsize > 0x100000000 else 32, sys.platform))
rc = main(*sys.argv[1:])
print("\nDone.")
sys.exit(rc)
备注:
除了你使用 np.nditer 的实现(我把它放在一个叫做 draw_img_orig[= 的函数中95=]),我又创建了 2 个:
迭代输入数组Pythonicly (draw_img_regular_iter)
一个使用缓存的圈子,并通过 np.nditer 进行迭代(draw_img_cache)
就测试而言,有 2 个 - 每个都在 3 个(以上)方法中的每一个上执行:
速度:测量处理大量图像所花费的时间
准确性:测量包含区间 [0, 255][=101] 的 32x32 输入的输出=](4次)
输出:
[cfati@CFATI-5510-0:e:\Work\Dev\Whosebug\q071818080]> sopr.bat ### Set shorter prompt to better fit when pasted in Whosebug (or other) pages ### [prompt]> dir /b code00.py [prompt]> "e:\Work\Dev\VEnvs\py_pc064_03.09_test0\Scripts\python.exe" code00.py Python 3.9.9 (tags/v3.9.9:ccb0e6a, Nov 15 2021, 18:08:50) [MSC v.1929 64 bit (AMD64)] 064bit on win32 Testing draw_img_orig Took 0.908 seconds (250 images) Testing draw_img_regular_iter Took 1.061 seconds (250 images) Testing draw_img_cache Took 0.426 seconds (250 images) Done. [prompt]> [prompt]> dir /b cache.png code00.py orig.png regit.png
上面是速度测试结果:正如所见,你的方法用了不到一秒的时间来处理 250 张图像!!!所以我是对的,我没有知道你的缓慢来自哪里,但它不是来自这里(也许你的测量有误?)。
常规方法有点慢,而缓存的方法 ~2X 更快。
我 运行 我笔记本电脑上的代码:
- 赢得 10 pc064
- CPU:Intel i7 6820HQ @ 2.70GHz(相当旧)
- GPU:不相关,因为我在执行期间没有注意到任何峰值
关于精度测试,所有 (3) 个输出数组都是相同的(没有消息另有说明),这是一张保存的图像: