0

I have a picture with a white background in which there are red, green and blue lines. The red green and blue lines are exclusively of the following values: (255,0,0), (0,255,0) and (0,0,255). The image is now saved as .png and as .jpg. The code should work in both formats. The following code is to detect the colours and return the number of the respective colour pixels. As .png the code works, as .jpg only the green pixels are recognised, but unfortunately not all of them. How can all pixels of all 3 colours also be recognised with .jpg?

import cv2
import numpy as np

img = cv2.imread(r"...\red.jpg")
x,y,z = img.shape
print(x,y,z)

r = np.where((img == (0, 0, 255)).all(axis=2))
redarray = np.array(r)
red = np.size(redarray)
g = np.where((img == (0, 255, 0)).all(axis=2))
greenarray = np.array(g)
green = np.size(greenarray)
bl = np.where((img == (255, 0, 0)).all(axis=2))
bluearray = np.array(bl)
blue = np.size(bluearray)
       

print("red: ", red)
print("green: ", green)
print("blue: ", blue)

martineau
  • 119,623
  • 25
  • 170
  • 301
MsX
  • 21
  • 1
  • 6

2 Answers2

2

Without seeing the images, jpeg images are compressed and if not handled with care pixel values are not the same as the equivalent png image.

Two approaches:

  1. Upon saving the jpeg you might try using a higher resolution/lower compression. It might work. It will also increase the file size.
  2. Instead of searching for the exact values (for example red == (0, 0, 255)), look for pixels under some thresholds. For example define th=10 and look for red pixels in the range of: green <= 0+th & blue <= 0+th & red >= 255-th.

Sorry I don't have a running code under this answer, let me know if you need further assistance with the masking.

itaishz
  • 701
  • 1
  • 4
  • 10
  • I would like to try the second approach. Is this possible to apply this "mask" in the respective np.where(...) statement? Otherwise, the solution would take some time... – MsX Dec 21 '20 at 23:34
  • 1
    According to [this answer](https://stackoverflow.com/a/16343791/14157750), slicing is easier. Try `mask = (img[:, :, 0] <= th) & (img[:, :, 1] <= th) & (img[:, :, 0] >= 255-th)` – itaishz Dec 22 '20 at 13:06
  • 1
    Note that for your question, where you have some lines on a white background, this simple slicing (with some thresholds) will probably work. However, if the intention is to scale it to real images, in my experience this will be hard to achieve. You probably need to google a little bit how to do it properly. For example, transforming the image into hsv space, using built-in opencv2 functions etc. – itaishz Dec 22 '20 at 13:10
  • It should be`mask = (img[:, :, 0] <= th) & (img[:, :, 1] <= th) & (img[:, :, 2] >= 255-th)` isn´t it? – MsX Dec 22 '20 at 15:22
  • 1
    Yep, the last `img[: , :, i]` should be with `i=2` and not `i=0` as in my previous comment. Sorry about that. – itaishz Dec 22 '20 at 15:27
  • I have now tried masking in hsv space. Now the colours are actually recognised in both formats. However, if I count the pixels of each of the three colours, the result is a slightly different number for each of the two formats. But that can hardly be avoided, can it? – MsX Dec 22 '20 at 19:51
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/226321/discussion-between-msx-and-itaishz). – MsX Dec 22 '20 at 19:53
2

The png is lossless format while the jpg is not (even if you use quality of 100).

This is why part of your original "pure" pixels become "impure" after being saved and reread from jpeg format.

BTW, your question states:

The red green and blue lines are exclusively of the following values: (255,0,0), (0,255,0) and (0,0,255)

but the opencv behaviour as well as your code is BGR. so red, for example is (0,0,255)

Lior Cohen
  • 5,570
  • 2
  • 14
  • 30