Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to crop a region of small pixels in an image using OpenCv in Python

I would like to crop out a region full of small curves in an image.

The original image is like the following: enter image description here

Using the opening morphing, I can remove most of the noises. The result is like: enter image description here

I tried to use dilate to connect these pixels in my desired region, but the result is not satisfactory.

Is there any function in opencv that can locate this region?

like image 665
John Avatar asked Sep 11 '19 09:09

John


2 Answers

You are on the right track, here's an approach using morphological transformations

  • Convert image to grayscale and Gaussian blur
  • Otsu's threshold
  • Perform morphological operations
  • Find contours and filter using maximum area
  • Extract ROI

The idea is to connect the desired region into a single contour then filter using maximum area. This way, we can grab the region as one piece. Here's the detected area

Afterwards, we can extract the region with Numpy slicing

import cv2

image = cv2.imread('1.jpg')
original = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (9,9), 0)
thresh = cv2.threshold(gray,0,255,cv2.THRESH_OTSU + cv2.THRESH_BINARY)[1]

kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2,2))
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
dilate_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9,9))
dilate = cv2.dilate(opening, dilate_kernel, iterations=5)

cnts = cv2.findContours(dilate, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)

for c in cnts:
    x,y,w,h = cv2.boundingRect(c)
    cv2.rectangle(image, (x, y), (x + w, y + h), (36,255,12), 2)
    ROI = original[y:y+h, x:x+w]
    break

cv2.imshow('thresh', thresh)
cv2.imshow('opening', opening)
cv2.imshow('dilate', dilate)
cv2.imshow('image', image)
cv2.imshow('ROI', ROI)
cv2.waitKey(0)
like image 136
nathancy Avatar answered Sep 19 '22 16:09

nathancy


Here's my approach using NumPy's sum. Just sum the pixel values along the x and y axis individually, set up some thresholds for the minimum number of pixels describing the desired area, and obtain proper column and row indices.

Let's have a look at the following code:

import cv2
import numpy as np
from matplotlib import pyplot as plt

# Read input image; get shape
img = cv2.imread('images/UKf5Z.jpg', cv2.IMREAD_GRAYSCALE)
w, h = img.shape[0:2]

# Threshold to prevent JPG artifacts
_, img = cv2.threshold(img, 240, 255, cv2.THRESH_BINARY)

# Sum pixels along x and y axis
xSum = np.sum(img / 255, axis=0)
ySum = np.sum(img / 255, axis=1)

# Visualize curves
plt.plot(xSum)
plt.plot(ySum)
plt.show()

# Set up thresholds
xThr = 15
yThr = 15

# Find proper row indices
tmp = np.argwhere(xSum > xThr)
tmp = tmp[np.where((tmp > 20) & (tmp < w - 20))]
x1 = tmp[0]
x2 = tmp[-1]

# Find proper column indices
tmp = np.argwhere(ySum > yThr)
tmp = tmp[np.where((tmp > 20) & (tmp < h - 20))]
y1 = tmp[0]
y2 = tmp[-1]

# Visualize result
out = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
cv2.rectangle(out, (x1, y1), (x2, y2), (0, 0, 255), 4)
cv2.imshow('out', out)
cv2.waitKey(0)

The curves of the summations look like this (just for visualization purposes):

Curves

And, for visualization I just drew a red rectangle described by the found indices.

Output

As you can see, I manually excluded some "border" area of 20 pixels, since there are some larger artifacts. Depending on the location of your desired area, this may be sufficient. Otherwise, your approach using morphological opening should be kept.

Hope that helps!


EDIT: As suggested by Mark in his answer, using mean instead of sum avoids adaptations regarding varying image dimensions. Changing the code appropriately is left to the reader. :-)

like image 30
HansHirse Avatar answered Sep 19 '22 16:09

HansHirse