Per-pixel operations in Python

Thanks for all the ideas, guys! @crackwitz’s suggestions were particularly helpful.

I tested most of the methods; here is a wrap-up:

  • Map/reduce/filter: not really applicable. Most of the time the images aren’t reduced, and often we need to manipulate array indexes, which is impossible with these methods (or with other matrix operators)
  • Pyton wrapper for C code: very interesting solution, but unfortunately you need to create an OpenCV module - and putting application-specific code in a library is a bad idea. However it would be great if there was a simple wrapper to create a python header and a .so file from a C++ code
  • Cython - this is the best solution. The time-critical Python code gets translated to C (and binary code if necessary), and imported.

As I didn’t find any simple example on Cython/OpenCV, I’m attaching my simple testing code below. It is mostly based on this tutorial. Note that this is my first experiment, so probably it can still be optimized/simplified, butI still hope this can help!
My results on a 10MP photo: OpenCV: 0.003s; Numpy: 0.023s Python loops: 12.9s[!!!] Cython loops: 0.01s

thresholding.py

import cv2
import numpy as np
import time

# import and compile cython code
import pyximport
pyximport.install()
import fastthreshold


def pythonthresh(gray):
    res = np.zeros(gray.shape, np.uint8)
    for y in range(gray.shape[0]):
        for x in range(gray.shape[1]):
            res[y, x] = 255 if gray[y, x] > th else 0
    return res


# Open file and convert to grayscale
filename = "IMG_02506.jpg" # change this
img = cv2.imread(filename)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
th = 128

# OpenCV thresholding
t1 = time.time()
res1 = cv2.threshold(gray, th, 255, cv2.THRESH_BINARY)
t2 = time.time()
print(" ------ CV2 thresholding: %s seconds -------" % (t2-t1))

# Numpy thresholding
# probably can be optimized, the multiplication takes time
res3 = (gray > th) * 255
t3 = time.time()
print(" ----- Numpy thresholding: %s seconds ------" % (t3-t2))

# iterating through the array using for loops; function above
res2 = pythonthresh(gray)
t4 = time.time()
print(" ---  Per pixel thresholding: %s seconds ---" % (t4-t3))

# fast iteration using cython
out = np.zeros(gray.shape, np.uint8)
fastthreshold.fastthreshold(th, gray, out)
t5 = time.time()
print(" ---- Cython thresholding: %s seconds ------" % (t5-t4))

fastthreshold.pyx

#cython: language_level=3

cimport cython

@cython.boundscheck(False)
@cython.wraparound(False)

cpdef fastthreshold(int th, unsigned char[:,:] gray,unsigned char[:,:] output):
    cpdef int x,y
    for y in range(gray.shape[0]):
        for x in range(gray.shape[1]):
            output[y,x] = 255 if gray[y,x]>th else 0
1 Like