-2

I have been trying to calculate the distance between two lines in an image in Python. For example, in the image given below, I want to find the perpendicular distance between the two ends of the yellow block. So far I have been only able to derive the distance between two pixels.

Image of engine

The code I could make was to find the distance between red and blue pixels. I figured I could improve this to make the distance between the two points/lines in this image, but no luck yet.

import numpy as np
from PIL import Image
import math

# Load image and ensure RGB - just in case palettised
im = Image.open("2points.png").convert("RGB")

# Make numpy array from image
npimage = np.array(im)

# Describe what a single red pixel looks like
red = np.array([255,0,0],dtype=np.uint8)

# Find [x,y] coordinates of all red pixels
reds = np.where(np.all((npimage==red),axis=-1))

print(reds)

# Describe what a single blue pixel looks like
blue=np.array([0,0,255],dtype=np.uint8)

# Find [x,y] coordinates of all blue pixels
blues=np.where(np.all((npimage==blue),axis=-1))

print(blues)

dx2 = (blues[0][0]-reds[0][0])**2          # (200-10)^2
dy2 = (blues[1][0]-reds[1][0])**2          # (300-20)^2
distance = math.sqrt(dx2 + dy2)
print(distance)
HansHirse
  • 18,010
  • 10
  • 38
  • 67
Urmi Bose
  • 25
  • 1
  • 8
  • Welcome to Stack Overflow! Please take the [tour](https://stackoverflow.com/tour) and read [How to Ask](https://stackoverflow.com/help/how-to-ask). Please [edit](https://stackoverflow.com/posts/65841870/edit) the post to include your own effort into solving this problem. The latter preferably in code, this is called a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example). – Bilal Jan 22 '21 at 08:48
  • 2
    Maybe you could add a second, annotated image showing exactly what you want to find because it is not very clear what *"the perpendicular distance between the ends of the yellow block* means, given that there are at least 2 and maybe 5 yellow blocks and you don't state what it needs to be perpendicular to. – Mark Setchell Jan 22 '21 at 10:05
  • @MarkSetchell Its just the big yellow block in the middle – Urmi Bose Jan 22 '21 at 10:12
  • So you want the height of the largest yellow rectangle? – Mark Setchell Jan 22 '21 at 10:17
  • @MarkSetchell Yes. Height and Width. – Urmi Bose Jan 22 '21 at 10:30
  • Any reason not to use **OpenCV** which would be simpler? – Mark Setchell Jan 22 '21 at 10:32
  • Is this a learning exercise because there is no real need to write Python for this as it's a one-liner in Terminal with **ImageMagick**? – Mark Setchell Jan 22 '21 at 10:33
  • Also highly suggesting OpenCV for that: Color threshold the image on yellow, find contours, get the largest contour, [`cv2.boxPoints`](https://docs.opencv.org/4.4.0/d3/dc0/group__imgproc__shape.html#gaf78d467e024b4d7936cf9397185d2f5c), and you have width and height. – HansHirse Jan 22 '21 at 11:04
  • @HansHirse I have been trying to work on your suggestion and got started with OpenCV. I managed to learn about contours but I am not able to implement them to extract the largest contour here (yellow). I worked out the boxpoints part separately. Could you help me out here please – Urmi Bose Jan 22 '21 at 14:29
  • @MarkSetchell I have started with OpenCV and i am trying to implement it. Still not successful though – Urmi Bose Jan 22 '21 at 14:31

1 Answers1

2

While preparing this answer, I realized, that my hint regarding cv2.boxPoints was misleading. Of course, I had cv2.boundingRect on my mind – sorry for that!

Nevertheless, here's the full step-by-step approach:

  1. Use cv2.inRange to mask all yellow pixels. Attention: Your image has JPG artifacts, such that you get a lot of noise in the mask, cf. the output:

Mask

  1. Use cv2.findContours to find all contours in the mask. That'll be over 50, due to the many tiny artifacts.

  2. Use Python's max function on the (list of) found contours using cv2.contourArea as key to get the largest contour.

  3. Finally, use cv2.boundingRect to get the bounding rectangle of the contour. That's a tuple (x, y, widht, height). Just use the last two elements, and you have your desired information.

That'd be my code:

import cv2

# Read image with OpenCV
img = cv2.imread('path/to/your/image.ext')

# Mask yellow color (0, 255, 255) in image; Attention: OpenCV uses BGR ordering
yellow_mask = cv2.inRange(img, (0, 255, 255), (0, 255, 255))

# Find contours in yellow mask w.r.t the OpenCV version
cnts = cv2.findContours(yellow_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]

# Get the largest contour
cnt = max(cnts, key=cv2.contourArea)

# Get width and height from bounding rectangle of largest contour
(x, y, w, h) = cv2.boundingRect(cnt)
print('Width:', w, '| Height:', h)

The output

Width: 518 | Height: 320

seems reasonable.

----------------------------------------
System information
----------------------------------------
Platform:      Windows-10-10.0.16299-SP0
Python:        3.8.5
OpenCV:        4.5.1
----------------------------------------
HansHirse
  • 18,010
  • 10
  • 38
  • 67
  • Thank you so much for your answer. Worked out great and I approved your answer :) Quick question, how can I make changes to the range to find other colours, e.g. blue, red and other shades? – Urmi Bose Jan 25 '21 at 09:42
  • @UrmiBose In the `cv2.inRange` call, put the corresponding RGB values of the desired color. Attention: Please use RGB values only, if you have clean, single value colors like pure green `(0, 255, 0)` for example. If you want to detect "all green colors", switch to the HSV color space and use proper ranges, cf. [this earlier answer from me](https://stackoverflow.com/a/55827176/11089932) for a starting point. – HansHirse Jan 25 '21 at 10:01