1

I am trying to do a Sepia-Filter using OpenCV & filter2D with a kernel, however from what I see is that the results that I get this way are differing from a manual implementation with a nested for-loop. My understanding of the cv.filter2D seems to be "off" and I hope someone can enlighten me where my thinking goes haywire...

The image is a small 10 x 10 pixel image (simple to check on the content) with some blue and white. The approach for OpenCV is to create a kernel with the appropriate weights for the pixel, however I guess I understood that wrongly. The kernel has this format:

kernel = np.array([[0.272, 0.534, 0.131],
                   [0.349, 0.686, 0.168],
                   [0.393, 0.769, 0.189]])

return cv2.filter2D(image, -1, kernel)

Running an image through that results in some bright yellow instead of the expected brown-tone. Doing the manual approach using the mentioned nested for-loops I do it this way:

dimensions = image.shape
newR=np.zeros([dimensions[0],dimensions[1]])
newG=np.zeros([dimensions[0],dimensions[1]])
newB=np.zeros([dimensions[0],dimensions[1]])

for x in range(0, dimensions[0]):
    for y in range(0, dimensions[1]):
        newR[x,y] = int(0.272*r[x,y]+0.534*g[x,y]+0.131*b[x,y])
        newG[x,y] = int(0.349*r[x,y]+0.686*g[x,y]+0.168*b[x,y])
        newB[x,y] = int(0.393*r[x,y]+0.769*g[x,y]+0.189*b[x,y])
        if newR[x,y]>255:
            newR[x,y]=255
        if newG[x,y]>255:
            newG[x,y]=255
        if newB[x,y]>255:
            newB[x,y]=255
 output_man = cv2.merge ( (newR, newG, newB) )

This then creates an image of the expected brownish colors.

I was expecting the filter2D to do a convolution of the pixels of the original image the same way as my manual code but it seems I am missing something and would hope for some pointers.

Originally I was reading in a *.png but for a runnable example I am creating the image in a separate function. This here is a full python-script that shows the two different results:

import cv2
import numpy as np
import scipy


def create_image():
    r_arr= np.array(
    [[255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
     [255, 255, 255, 255, 255, 255, 255, 255, 255,   0],
     [255, 255, 255, 255, 255, 255, 255,   0,   0,   0],
     [255, 255, 255, 255, 255,   0,   0,   0,   0,   0],
     [255, 255, 255, 255,   0,   0,   0,   0,   0,   0],
     [255, 255,   0,   0,   0,   0,   0,   0,   0,   0],
     [255,   0,   0,   0,   0,   0,   0,   0,   0,   0],
     [  0,   0,   0,   0,   0,   0,   0,   0, 255, 255],
     [  0,   0,   0,   0,   0,   0,   0, 255, 255, 255],
     [  0,   0,   0,   0,   0, 255, 255, 255, 255, 255]]
    , dtype = np.uint8)
    g_arr= np.array(
    [[255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
     [255, 255, 255, 255, 255, 255, 255, 255, 255, 162],
     [255, 255, 255, 255, 255, 255, 255, 162, 162, 162],
     [255, 255, 255, 255, 255, 162, 162, 162, 162, 162],
     [255, 255, 255, 255, 162, 162, 162, 162, 162, 162],
     [255, 255, 162, 162, 162, 162, 162, 162, 162, 162],
     [255, 162, 162, 162, 162, 162, 162, 162, 162, 162],
     [162, 162, 162, 162, 162, 162, 162, 162, 255, 255],
     [162, 162, 162, 162, 162, 162, 162, 255, 255, 255],
     [162, 162, 162, 162, 162, 255, 255, 255, 255, 255]]
    ,dtype = np.uint8)
    b_arr= np.array(
    [[255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
     [255, 255, 255, 255, 255, 255, 255, 255, 255, 232],
     [255, 255, 255, 255, 255, 255, 255, 232, 232, 232],
     [255, 255, 255, 255, 255, 232, 232, 232, 232, 232],
     [255, 255, 255, 255, 232, 232, 232, 232, 232, 232],
     [255, 255, 232, 232, 232, 232, 232, 232, 232, 232],
     [255, 232, 232, 232, 232, 232, 232, 232, 232, 232],
     [232, 232, 232, 232, 232, 232, 232, 232, 255, 255],
     [232, 232, 232, 232, 232, 232, 232, 255, 255, 255],
     [232, 232, 232, 232, 232, 255, 255, 255, 255, 255]]
    ,dtype = np.uint8)
    art_image=cv2.merge((r_arr, g_arr, b_arr))
    return art_image

def sepia(image):
    #Calculations for every pixel:
    # new R-value = tr = int(0.393 * r + 0.769 * g + 0.189 * b) but maxed at 255
    # new G-value = tg = int(0.349 * r + 0.686 * g + 0.168 * b) but maxed at 255
    # new B-value = tb = int(0.272 * r + 0.534 * g + 0.131 * b) but maxed at 255

    #This block is original for RGB
    kernel = np.array([[0.272, 0.534, 0.131],
                       [0.349, 0.686, 0.168],
                       [0.393, 0.769, 0.189]])

    return cv2.filter2D(image, -1, kernel)

######################################################################
######################################################################
######################################################################

#image = cv2.imread("10x10.png")
image = create_image()
dimensions = image.shape
b, g, r = cv2.split(image)

output_image=sepia(image)

dimensions = image.shape
newR=np.zeros([dimensions[0],dimensions[1]])
newG=np.zeros([dimensions[0],dimensions[1]])
newB=np.zeros([dimensions[0],dimensions[1]])

for x in range(0, dimensions[0]):
    for y in range(0, dimensions[1]):
        newR[x,y] = int(0.272*r[x,y]+0.534*g[x,y]+0.131*b[x,y])
        newG[x,y] = int(0.349*r[x,y]+0.686*g[x,y]+0.168*b[x,y])
        newB[x,y] = int(0.393*r[x,y]+0.769*g[x,y]+0.189*b[x,y])
        if newR[x,y]>255:
            newR[x,y]=255
        if newG[x,y]>255:
            newG[x,y]=255
        if newB[x,y]>255:
            newB[x,y]=255

output_man = cv2.merge ( (newR, newG, newB) )

b2, g2, r2 = cv2.split(output_image)
print(r)
print("--------")
print(g)
print("--------")
print(b)
print("--------")
print("--------")
print(r2)
print("--------")
print(g2)
print("--------")
print(b2)
print("--------")
print("--------")
print(newR)
print("--------")
print(newG)
print("--------")
print(newB)
print("--------")


cv2.imwrite("10x10_m.png", output_man)
cv2.imwrite("10x10_f.png", output_image)
ye_ol_man
  • 37
  • 6
  • 3
    `filter2D` works on each colour channel independently, and calculates value of new pixel from surrounding pixels of the same channel. In your implementation, the value in each channel is calculated from all 3 colours in the same position. What you want to do is not convolution, `filter2d` won't help you. – Dan Mašek May 25 '20 at 11:17
  • @DanMašek Thanks for that heads-up, that already makes sense. By any chance, are you aware of a openCV-function that could be used here and which would work on all three channels? – ye_ol_man May 25 '20 at 11:27
  • Nothing premade. https://stackoverflow.com/questions/22081423/apply-transformation-matrix-to-pixels-in-opencv-image – Dan Mašek May 25 '20 at 11:36
  • 1
    @DanMašek: One thing I found to work now with colors that I would agree to is cv2.transform. Still slight deviation from the manual approach, however "close enough". Again thanks for your pointer! – ye_ol_man May 25 '20 at 11:48

0 Answers0