0

I have a video of a brick breaking game. Some bricks are in red color. I have to change the red color into black. I am trying to find the location of the red pixel in numpy array and assign black color on those pixels. The code I have provided below turns the red color into black. But the process is so slow that 12s video took more than 5 mins. Is there any faster way to do that?

import numpy as np
import cv2


vid = "input.mp4"
cap = cv2.VideoCapture(vid)

while(True):
    ret, frame = cap.read()
    if ret: 
        for i in zip(*np.where(frame == [0,0,255])):
            frame[i[0], i[1], 0] = 0
            frame[i[0], i[1], 1] = 0
            frame[i[0], i[1], 2] = 0
        cv2.imshow('frame',frame)
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
    else:
        break
cv2.destroyAllWindows()
Sourav
  • 816
  • 3
  • 11
  • 26
  • 1
    Possible duplicate of [How to find the RED color regions using OpenCV?](https://stackoverflow.com/questions/51229126/how-to-find-the-red-color-regions-using-opencv) – Kinght 金 Feb 22 '19 at 01:00

4 Answers4

2

Try this out, read comments in the code for more information.

import cv2
import numpy as np

img = cv2.imread("1.png")

hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# define range of red color in HSV
lower_red = np.array([0,50,50])
upper_red = np.array([10,255,255])
# Threshold the HSV image to get only red colors
mask = cv2.inRange(hsv, lower_red, upper_red)
red_only = cv2.bitwise_and(img,img, mask= mask)

#convert mask to 3-channel image to perform subtract
mask = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR)

res = cv2.subtract(img,mask) #negative values become 0 -> black

cv2.imshow("img",img)
cv2.imshow("mask",mask)
cv2.imshow("red_only",red_only)
cv2.imshow("res",res)
cv2.waitKey()
cv2.destroyAllWindows()

PS. And this method takes almost no time, I've tested on my machine and it takes about 3ms for an image of 1280x720

enter image description here enter image description here enter image description here enter image description here

Ha Bom
  • 2,787
  • 3
  • 15
  • 29
  • Op asks something else – Martin Feb 22 '19 at 07:22
  • Negative values in the final frame become 0 this the red become black. What if I want to convert the red into yellow? – Sourav Feb 22 '19 at 16:43
  • 1
    @Sourav you need to ask questions in your main question. Also you upvoted an Answer that is different than your question. You ask how to change SINGLE color, but you upvote an Answer that 'removes COLOR INTERVAL in HSV'. This Answer doesnt even answers your 'how to change color' but instead tells you 'how to remove color'. As a result, you ask new question. This site is not forum. This site is 'Particular Question' & 'Exact Answer' – Martin Feb 22 '19 at 22:53
0

This works just for gray

(you cant specify channels with color you are replacing)

color_find = [0,0,255]
indexes=np.where(frame == color_find)
frame[indexes]=0 # 0-255 creates [0,0,0] - [255,255,255]

More general attitude is like this

Here you specify RGB axis, and you can replace with any color

red = [0,0,255]
black = [0,0,0]
indexes=np.where(np.all(frame == red,axis = 2))
frame[indexes] = black
Martin
  • 3,333
  • 2
  • 18
  • 39
  • I didn't downvote, but perhaps you could explain what each step is doing here and link the relevant docs (especially the `np.all` call with the `axis` kwarg). I follow, but a newbie might not. Also I'll just add that the `np.where()` call is unnecessary, you can just use the result of `np.all` as a boolean indexer into `frames`. – alkasm Feb 22 '19 at 01:52
  • What are you taking about? The code has 4 lines, 3 of them is variable assignment and yet I should explain each step and provide doc? – Martin Feb 22 '19 at 07:27
  • Again, the part that may be confusing to a newbie is `np.all(frame == red, axis=2)`. And yes, it's best to link documentation wherever applicable. I'm only stating this as the question is very easy to answer already, so if OP doesn't know how to do it, then it would be better to link to the relevant docs. See for e.g. [how to answer](https://stackoverflow.com/help/how-to-answer). – alkasm Feb 22 '19 at 18:34
  • @Martin Can you please explain this bit of code np.where(np.all(frame == red,axis = 2)) What does axis mean here? Is not BGR image comes with three (BGR) 2D np array. If I define the axis =2 , will will indicate the Green channel? – Sourav Feb 22 '19 at 23:15
  • @Sourav Why should I explain code in my question, if you already accepted an Answer? Isnt this question closed? – Martin Feb 22 '19 at 23:28
  • @AlexanderReynolds I do not think there is necessary explanation. Nobody explained it to my and I got it eventually. This is just one line of code... You cant give everything... something must be hidden, otherwise nobody will learn anything – Martin Feb 23 '19 at 01:00
  • got it. @Martin – Sourav Feb 23 '19 at 03:21
0

you can replace your for loop with this:

    # [b,g,r]
    color_in = [0, 0, 255]  # color you want to filter
    color_out = [0, 0, 0]   # color you want to set
    for i in range(3):
        frame[frame[:, :, i] == color_in[i]] = color_out[i]

You can use this for video frame with 3 color channels. Also, you can play around with the conditional operator(replace with >, <, etc.) for more control.

Use like this to filter color ranges:

frame[frame[:, :, i] < color_in[i]] = color_out[i]
the23Effect
  • 580
  • 2
  • 7
  • This is my first post can you please explain why it was downvoted. My answer exactly does what the question asks for: find the red pixels and replace with black and it does it faster so why was it downvoted ? – the23Effect Feb 22 '19 at 11:41
  • And its not just that in the code if you do this you can also filter color ranges: frame[frame[:, :, i] < color_in[i]] = color_out[i] – the23Effect Feb 22 '19 at 11:46
  • I downvoted it. Because it is redundant. You can do this without loops. Also it's not very good way to change color by checking values in 2D array. Sorry if that upset you. I just think is not clean solution – Martin Feb 22 '19 at 17:51
0

Using Ha Bom's code and some parts of my code, the problem has been solved. However, it takes a little bit of time. Processing 12-second video takes around 20-25 sec. The main purpose was to convert red pixels into orange- The code is provided below -

cap = cv2.VideoCapture("input.avi")

while(True):
    ret, frame = cap.read()
    if ret:

        # hsv is better to recognize color, convert the BGR frame to HSV
        hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)

        # in hsv red color located in two region. Create the mask for red color
        # mask the red color and get an grayscale output where red is white
        # everything else are black
        mask1 = cv2.inRange(hsv, (0,50,20), (5,255,255))
        mask2 = cv2.inRange(hsv, (175,50,20), (180,255,255))
        mask = cv2.bitwise_or(mask1, mask2)

        # get the index of the white areas and make them orange in the main frame
        for i in zip(*np.where(mask == 255)):
                frame[i[0], i[1], 0] = 0
                frame[i[0], i[1], 1] = 165
                frame[i[0], i[1], 2] = 255

        # play the new video
        cv2.imshow("res",frame)
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
    else:
        break
cv2.destroyAllWindows()
Sourav
  • 816
  • 3
  • 11
  • 26