如何在 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
  • CPUIntel i7 6820HQ @ 2.70GHz(相当旧)
  • GPU:不相关,因为我在执行期间没有注意到任何峰值

关于精度测试,所有 (3) 个输出数组都是相同的(没有消息另有说明),这是一张保存的图像: