Say I have an image as follows and I would like to crop some sub-images of the retina only.
So the first step I took is to find a mask
# Find Mask
img = imread(folder + 'test.jpg')
blur = cv2.GaussianBlur(img, (3, 3), cv2.BORDER_DEFAULT)
edged = cv2.Canny(blur, 10, 250)
# threshold
thresh = cv2.threshold(edged, 128, 255, cv2.THRESH_BINARY)[1]
# apply close morphology
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
# get bounding box coordinates from the one filled external contour
filled = np.zeros_like(thresh)
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
max_cnt = max(cnts, key=cv2.contourArea)
x,y,w,h = cv2.boundingRect(max_cnt)
cv2.drawContours(filled, [max_cnt], 0, 255, -1)
# crop filled contour image
mask = filled.copy()
cv2_imshow(mask)
Here is the mask found:
Then I wish to crop subimages of 50 * 50 within the inner area of a mask
Here is some code I found online:
# This gives the retina only
new_image = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
new_image[:,:,3] = mask[:,:]
imshow(new_image)
Below is closer to what I want. There are two sub-images that are within the mask. But what I really want is to crop maybe 5 sub-images randomly that are within the mask. Is there a way that I can do that? Or how can I check the cropped images are within the mask?
def img_to_grid(img):
# ww = [[i.min(), i.max()] for i in np.array_split(range(img.shape[0]),row)]
# hh = [[i.min(), i.max()] for i in np.array_split(range(img.shape[1]),col)]
print(img.shape[0], img.shape[1])
ww = [[i, i+100-1] for i in range(0, img.shape[0],100)]
print(ww)
hh = [[i, i+100-1] for i in range(0, img.shape[1],100)]
print(hh)
grid = [img[j:jj,i:ii,:] for j,jj in ww for i,ii in hh]
print((j, jj, i, ii) for j,jj in ww for i,ii in hh)
return grid, len(ww), len(hh)
def plot_grid(grid,row,col,h=5,w=5):
fig, ax = plt.subplots(nrows=row, ncols=col)
[axi.set_axis_off() for axi in ax.ravel()]
fig.set_figheight(h)
fig.set_figwidth(w)
c = 0
for row in ax:
for col in row:
col.imshow(np.flip(grid[c],axis=-1))
c+=1
plt.show()
grid , r,c = img_to_grid(img)
plot_grid(grid,r,c)
If you want to extract really random sub-images that is not necessary inscribed in a circle, you can just get some coords within the non-zero mask bounding box, check if it contains only ones, and save them, if so.
sub_imgs = []
# Define minimum width and height for the cropped region
min_width = 32
min_height = 32
# Define the number of sub-images
sub_img_count = 5
while len(sub_imgs) < sub_img_count:
x1 = np.random.randint(x, x + w)
y1 = np.random.randint(y, y + h)
maxw = w - (x1 - x)
maxh = h - (y1 - y)
# Skip if maximum possible sizes don't meet the requirement
if not (maxw > min_width and maxh > min_height):
continue
width = np.random.randint(min_width, maxw)
height = np.random.randint(min_height, maxh)
# Extract the region only if it is within the mask
if np.all(mask[y1: y1 + height, x1: x1 + width]):
sub_img = img[y1: y1 + height, x1: x1 + width, :]
sub_imgs.append(sub_img)
# Save the cropped regions
for i, sub_img in enumerate(sub_imgs):
cv2.imwrite(f'{i + 1}.jpg', sub_img)
The cropped sub-image example
Related
I am new to OpenCV and I am not even sure how to tackle this problem. I have this image of 500x500 pixel with red dots and white lines in it.
Considering each red dot as center and could I draw a fixed bounding box of 25X25 size around the red dot? I need to identify every red dot in the image.
Note: condition is that I need to find a bounding box of fixed size (25x25) and the red dot must be in the center of the bounding box.
Any help would be appreciated. Thank you in advance.
Another solution, using numpy slicing to get the red channel, where to create a mask of the red dots and cv2.findContours to get the bounding rectangles of the dots. We can use this info to draw the new 25 x 25 rectangles:
# Imports
import cv2
import numpy as np
# Read image
imagePath = "C://opencvImages//"
inputImage = cv2.imread(imagePath + "oHk9s.png")
# Deep copy for results:
inputImageCopy = inputImage.copy()
# Slice the Red channel from the image:
r = inputImage[:, :, 2]
# Convert type to unsigned integer (8 bit):
r = np.where(r == 237, 255, 0).astype("uint8")
# Extract blobs (the red dots are all the white pixels in this mask):
contours, _ = cv2.findContours(r, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
# Store bounding rectangles here:
boundingRectangles = []
# Loop through the blobs and draw a 25 x 25 green rectangle around them:
for c in contours:
# Get dot bounding box:
x, y, w, h = cv2.boundingRect(c)
# Set new bounding box dimensions:
boxWidth = 25
boxHeight = 25
# Center rectangle around blob:
boxX = int(x + 0.5 * (w - boxWidth))
boxY = int(y + 0.5 * (h - boxHeight))
# Store data:
boundingRectangles.append((boxX, boxY, boxWidth, boxHeight))
# Draw and show new bounding rectangles
color = (0, 255, 0)
cv2.rectangle(inputImageCopy, (boxX, boxY), (boxX + boxWidth, boxY + boxHeight), color, 2)
cv2.imshow("Boxes", inputImageCopy)
cv2.waitKey(0)
Additionally, I've stored the top left coordinate, width and height of the rectangles in the boundingRectangles list. This is the output:
Here is how you can use an HSV mask to mask out everything in your image except for the red pixels:
import cv2
import numpy as np
def draw_box(img, cnt):
x, y, w, h = cv2.boundingRect(cnt)
half_w = w // 2
half_h = h // 2
x1 = x + half_h - 12
x2 = x + half_h + 13
y1 = y + half_w - 12
y2 = y + half_w + 13
cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0))
img = cv2.imread("red_dots.png")
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
ranges = np.array([[100, 0, 0], [179, 255, 255]])
mask = cv2.inRange(img_hsv, *ranges)
img_masked = cv2.bitwise_and(img, img, mask=mask)
img_gray = cv2.cvtColor(img_masked, cv2.COLOR_BGR2GRAY)
contours, _ = cv2.findContours(img_gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
for cnt in contours:
draw_box(img, cnt)
cv2.imshow("Image", img)
cv2.waitKey(0)
Output:
Notice at this part of the draw_box() function:
x1 = x + half_h - 12
x2 = x + half_h + 13
y1 = y + half_w - 12
y2 = y + half_w + 13
Ideally, instead of - 12 and + 13, it should be - 12.5 and + 12.5, but there cannot be half pixels in OpenCV, or an error would be thrown.
I'm having an image and i want to crop only the number inside with out the lines bounding around image. Here is the sample image:
As you can see there is 2 lines on the right and bottom of the image, i want to crop only number 8, if nothing inside, i will return None for the result, but with my code, it also return the area which include these line. So is there anyway to fix this?
Result i got:
Or with empty image, i got:
Here is my code:
import cv2
def process(image, readFile=True, returnType='binary'):
out_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Performing OTSU threshold
ret, thresh1 = cv2.threshold(out_gray, 220, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY_INV)
# Specify structure shape and kernel size.
# Kernel size increases or decreases the area
# of the rectangle to be detected.
# A smaller value like (10, 10) will detect
# each word instead of a sentence.
rect_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (8,8))
# Appplying dilation on the threshold image
dilation = cv2.dilate(thresh1, rect_kernel, iterations = 1)
# Finding contours
contours, hierarchy = cv2.findContours(dilation, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
if len(contours) <= 0:
return False
contours, boundingBoxes = sort_contours(contours, 'left-to-right')
# Creating a copy of image
im2 = img.copy()
idata = 0
# Looping through the identified contours
# Then rectangular part is cropped and passed on
# to pytesseract for extracting text from it
# Extracted text is then written into the text file
i = 1
minHeight = 10
minWidth = 10
result = []
for cnt in contours:
x, y, w, h = cv2.boundingRect(cnt)
# Drawing a rectangle on copied image
rect = cv2.rectangle(im2, (x, (y-1)), (x + w, y + h + 1), (0, 255, 0), 1)
cv2.imwrite(f'{constant.__RESULT_PATH}/cutted123withbounding.png', im2)
# Cropping the text block for giving input to OCR
cropped = im2[y:y + h, x:x + w]
if w >= minWidth and h >= minHeight:
result.append(thresh1)
i = i + 1
return result
I'm putting together an image processing tool to follow the deformation of a part using images. The part has rectangular markers that get detected with image segmentation and cv2.findContours function. Contour centers are then used to calculate distances and to bend radiuses. Everything seems to work fine, but I found out that the contours aren't sorted how I would like to sort them when reviewing results.
The part is repeatedly bent, and the contours are positioned in a circle.
I found this article that describes the sorting horizontally and vertically:
https://www.pyimagesearch.com/2015/04/20/sorting-contours-using-python-and-opencv/
Does anyone have any idea how to sort the contours in a clockwise direction?
The code is below.
import os
import exifread
import cv2
import numpy as np
import scipy
from matplotlib import pyplot as plt
import imutils
import pandas as pd
#---------- INPUT ----------
# Define the image filename
img_filename = 'frame397.jpg'
img_path = img_filename
# Define values for cropping
x = 0
y = 200
w = 1200
h = 800
# Define color values for segmentation
# the values can be probed with GIMP
h1 = 0
s1 = 70
v1 = 120
h2 = 255
s2 = 255
v2 = 255
red_lower = np.array([h1,s1,v1])
red_upper = np.array([h2,s2,v2])
# Define desired area size
# desired area size is pixel count - use GIMP for probe
s1 = 500
s2 = 10000
#---------- PROCESS IMAGES ----------
# Create an empty dataframe for storing results
# in shape of (image_name,time,angle,angle_smooth,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11)
# Define the results dataframe shape and column names
results_df = pd.DataFrame(columns=['image_name','alpha','r1','r2','r3','r4','r5','r6','r7','r8','r9','r10','r11',
'center_dist1', 'center_dist2','center_dist3','center_dist4',
'center_dist5','center_dist6','center_dist7','center_dist8',
'center_dist9','center_dist10','center_dist11'])
# Open image, make it black and white and find contours
img = cv2.imread(img_path)
crop = img[y:y+h, x:x+w]
blur = cv2.blur(crop,(2,2))
hsv = cv2.cvtColor(blur,cv2.COLOR_BGR2HSV)
mask = cv2.inRange(hsv, red_lower, red_upper)
mask_copy = mask.copy()
cnts = cv2.findContours(mask_copy,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
#print cnts
x = []
y = []
# Loop through contours, calculate the centers and prepare the
#contours and contour centers display
#define the font for the text on the image
font = cv2.FONT_HERSHEY_SIMPLEX
for cnt in cnts:
area = cv2.contourArea(cnt)
moment = cv2.moments(cnt)
if s1<area<s2:
print area
c_x = int(moment["m10"]/moment["m00"])
c_y = int(moment["m01"]/moment["m00"])
#draw contours
cv2.drawContours(crop, cnt, -1, (0,255,0),3)
#draw a circle in the center of every contour, -1 is for thickness, this means
#that the cirlce will get filled in
cv2.circle(crop, (c_x,c_y), 10, (0,255,0),-1)
#display center coordinates on the image
string = str(c_x) + ',' + str(c_y)
cv2.putText(crop,string,(c_x,c_y),font,0.5,(255,255,255),2)
x.append(float(c_x))
y.append(float(c_y))
print (c_x, c_y)
print x
print y
# Display image
cv2.namedWindow('Contours', cv2.WINDOW_NORMAL)
cv2.resizeWindow('Contours', 1200,900)
cv2.imshow('Contours', crop)
# Wait for windows closing
cv2.waitKey() & 0xFF
cv2.destroyAllWindows
Image is here:
I used openCV's minEnclosingCircle to "fit" a circle to the points (it's not actually a fit, but it's good enough for finding a point inside the curvature of the markers). Marking each contour with the angle from its centroid to the circle's center gave me a set of angles that I could sort with.
import cv2
import numpy as np
import math
# 2d distance
def dist2D(one, two):
dx = one[0] - two[0];
dy = one[1] - two[1];
return math.sqrt(dx*dx + dy*dy);
# angle between three points (the last point is the middle)
def angle3P(p1, p2, p3):
# get distances
a = dist2D(p3, p1);
b = dist2D(p3, p2);
c = dist2D(p1, p2);
# calculate angle // assume a and b are nonzero
# (law of cosines)
numer = c**2 - a**2 - b**2;
denom = -2 * a * b;
if denom == 0:
denom = 0.000001;
rads = math.acos(numer / denom);
degs = math.degrees(rads);
# check if past 180 degrees
if p1[1] > p3[1]:
degs = 360 - degs;
return degs;
# load image
img = cv2.imread("slinky.jpg");
# rescale
scale = 0.5;
h, w = img.shape[:2];
h = int(h * scale);
w = int(w * scale);
img = cv2.resize(img, (w,h));
# change color space
lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB);
l,a,b = cv2.split(lab);
# threshold
thresh = cv2.inRange(a, 140, 255);
# get rid of little dots
kernel = np.ones((3,3),np.uint8)
thresh = cv2.erode(thresh,kernel,iterations = 1);
thresh = cv2.dilate(thresh,kernel, iterations = 1);
# contours
_, contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE);
# get centroids
centroids = [];
centers = [];
for con in contours:
m = cv2.moments(con);
cx = int(m['m10'] / m['m00']);
cy = int(m['m01'] / m['m00']);
centers.append([cx, cy]);
centroids.append([[cx, cy], con]);
img = cv2.circle(img, (cx, cy), 10, (0,0,255), -1);
# find circle around points
# NOTE: this doesn't "fit" a circle to the points
# I'm just using this to find a "good enough" center
# that's in the direction of the curve
numped = np.array(centers);
(x, y), radius = cv2.minEnclosingCircle(numped);
img = cv2.circle(img, (int(x), int(y)), int(radius), (255,0,0), 2);
middle = [x,y];
offshoot = [x + 100, y];
# get angles
angles = [];
for cen in centroids:
center, contour = cen;
angle = angle3P(center, offshoot, middle);
angles.append([angle, center, contour]);
# sort by angle
final = sorted(angles, key = lambda a: a[0], reverse = True);
# pull out just the contours
contours = [clump[2] for clump in final];
# draw contours in order
marked = img.copy();
counter = 0;
for con in contours:
cv2.drawContours(marked, [con], -1, (0, 255, 0), 2);
cv2.imshow("marked", marked);
cv2.imwrite("marking_seq/" + str(counter) + ".png", marked);
counter += 1;
cv2.waitKey(0);
# show
cv2.imshow("orig", img);
cv2.imshow("a", a);
cv2.imshow("thresh", thresh);
cv2.waitKey(0);
I am trying to filter the background of images presenting electric cables. I tried to do the following:
Transform from color to gray
Apply cv2.Laplacian or 2 times of cv2.Sobel for finding edges in both directions.
Apply thresholding cv2.THRESH_BINARY(_INV), cv2.THRESH_OTSU
Lastly, I tried to find edges with 'filtered' images using cv2.Canny together with cv2.HoughLinesP
Overall, the results aren't satisfying at all. I will give an example of 2 images:
And the output of my script:
I also played with the values in config, but the results weren't different much.
Here's the little script I managed to do:
import cv2
import matplotlib.pyplot as plt
import numpy as np
def img_show(images, cmap=None):
fig = plt.figure(figsize=(17, 10))
root = 3 # len(images) ** 0.5
for i, img in enumerate(images):
ax = fig.add_subplot(root, root, i + 1)
ax.imshow(img, cmap=cmap[i])
plt.show()
class Config:
scale = 0.4
min_threshold = 120
max_threshold = 200
canny_min_threshold = 100
canny_max_threshold = 200
config = Config()
def find_lines(img, rgb_img):
dst = cv2.Canny(img, config.canny_min_threshold, config.canny_max_threshold)
cdstP = np.copy(rgb_img)
lines = cv2.HoughLinesP(dst, 1, np.pi / 180, 150, None, 0, 0)
lines1 = lines[:, 0, :]
for x1, y1, x2, y2 in lines1[:]:
cv2.line(cdstP, (x1, y1), (x2, y2), (255, 0, 0), 5)
return cdstP
if __name__ == "__main__":
bgr_img = cv2.imread('DJI_0009.JPG')
bgr_img = cv2.resize(bgr_img, (0, 0), bgr_img, config.scale, config.scale)
rgb_img = cv2.cvtColor(bgr_img, cv2.COLOR_BGR2RGB)
gray_img = cv2.cvtColor(bgr_img, cv2.COLOR_BGR2GRAY)
# _, threshold = cv2.threshold(gray_img, config.min_threshold, config.max_threshold, cv2.THRESH_BINARY)
# laplacian = cv2.Laplacian(rgb_img, cv2.CV_8UC1)
sobelx = cv2.Sobel(gray_img, cv2.CV_8UC1, 1, 0)
sobely = cv2.Sobel(gray_img, cv2.CV_8UC1, 0, 1)
blended = cv2.addWeighted(src1=sobelx, alpha=0.5, src2=sobely, beta=0.5, gamma=0)
_, threshold = cv2.threshold(blended, config.min_threshold, config.max_threshold,
cv2.THRESH_BINARY | cv2.THRESH_OTSU)
p1 = find_lines(threshold, rgb_img)
p2 = find_lines(blended, rgb_img)
p3 = find_lines(gray_img, rgb_img)
plots = [rgb_img, p1, p2, p3]
cmaps = [None] + ['gray'] * (len(plots) - 1)
img_show(plots, cmaps)
I am assuming I need to do much better filtring. However, I also tried image segmentation, but the results weren't promising at all.
Any ideas on how to improve this?
Thanks
Here is one way to do that in Python/OpenCV. I threshold, then optionally clean with morphology. Then get the contours and for each contour compute its rotated rectangle. Then get the dimensions of the rotated rectangle and compute the aspect ratio (largest dimension / smallest dimension) and optionally the area. Then I threshold on the aspect ratio (and optionally the area) and keep only those contours that pass)
Input:
import cv2
import numpy as np
image = cv2.imread("DCIM-100-MEDIA-DJI-0009-JPG.jpg")
hh, ww = image.shape[:2]
# convert to grayscale
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# create a binary thresholded image
thresh = cv2.threshold(gray, 64, 255, cv2.THRESH_BINARY)[1]
# invert so line is white on black background
thresh = 255 - thresh
# apply morphology
kernel = np.ones((11,11), np.uint8)
clean = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
# get external contours
contours = cv2.findContours(clean, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
area_thresh = ww / 2
aspect_thresh = ww / 30
print(area_thresh,aspect_thresh)
print('')
result = image.copy()
for c in contours:
# get rotated rectangle from contour
# get its dimensions
rotrect = cv2.minAreaRect(c)
(center), (dim1,dim2), angle = rotrect
maxdim = max(dim1,dim2)
mindim = min(dim1,dim2)
area = dim1 * dim2
if mindim != 0:
aspect = maxdim / mindim
#print(area, aspect)
#if area > area_thresh and aspect > aspect_thresh:
if aspect > aspect_thresh:
# draw contour on input
cv2.drawContours(result,[c],0,(0,0,255),3)
print(area, aspect)
# save result
cv2.imwrite("DCIM-100-MEDIA-DJI-0009-JPG_thresh.jpg",thresh)
cv2.imwrite("DCIM-100-MEDIA-DJI-0009-JPG_clean.jpg",clean)
cv2.imwrite("DCIM-100-MEDIA-DJI-0009-JPG_result.jpg",result)
# display result
cv2.imshow("thresh", thresh)
cv2.imshow("clean", clean)
cv2.imshow("result", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Thresholded image:
Morphology cleaned image:
Result image:
![enter image description here][1]
Able to identify and get blue colour but culdnt identify the red colour range unable to fix colour range for red/purple stripes.I have used contour and created range for red,green,blue colour .That stripes colour range is not correct I tried settig maximum range for red/purple
import numpy as np
import cv2
img = cv2.imread(r'/home/pavithra/Downloads/pic.jpeg')
hsvFrame = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# Set range for red color and
red_lower = np.array([136, 86, 86], np.uint8)
red_upper = np.array([239, 12, 50], np.uint8)
red_mask = cv2.inRange(hsvFrame, red_lower, red_upper)
green_lower = np.array([25, 52, 72], np.uint8)
green_upper = np.array([102, 255, 255], np.uint8)
green_mask = cv2.inRange(hsvFrame, green_lower, green_upper)
blue_lower = np.array([94, 80, 2], np.uint8)
blue_upper = np.array([120, 255, 255], np.uint8)
blue_mask = cv2.inRange(hsvFrame, blue_lower, blue_upper)
kernal = np.ones((5, 5), "uint8")
red_mask = cv2.dilate(red_mask, kernal)
res_red = cv2.bitwise_and(img, img,
mask = red_mask)
# For green color
green_mask = cv2.dilate(green_mask, kernal)
res_green = cv2.bitwise_and(img, img,
mask = green_mask)
# For blue color
blue_mask = cv2.dilate(blue_mask, kernal)
res_blue = cv2.bitwise_and(img, img,
mask = blue_mask)
# Creating contour to track red color
contours, hierarchy = cv2.findContours(red_mask,
cv2.RETR_TREE,
cv2.CHAIN_APPROX_SIMPLE)
for pic, contour in enumerate(contours):
area = cv2.contourArea(contour)
if(area > 300):
x, y, w, h = cv2.boundingRect(contour)
img = cv2.rectangle(img, (x, y),
(x + w, y + h),
(0, 255, 0), 2)
cv2.putText(img, "Red", (x, y),
cv2.FONT_HERSHEY_SIMPLEX, 1.0,
(0, 255, 0),2)
contours, hierarchy = cv2.findContours(green_mask,
cv2.RETR_TREE,
cv2.CHAIN_APPROX_SIMPLE)
for pic, contour in enumerate(contours):
area = cv2.contourArea(contour)
if(area > 300):
x, y, w, h = cv2.boundingRect(contour)
img = cv2.rectangle(img, (x, y),
(x + w, y + h),
(0, 255, 0), 2)
cv2.putText(img, "Green", (x, y),
cv2.FONT_HERSHEY_SIMPLEX,
1.0, (0, 255, 0),2)
contours, hierarchy = cv2.findContours(blue_mask,
cv2.RETR_TREE,
cv2.CHAIN_APPROX_SIMPLE)
for pic, contour in enumerate(contours):
area = cv2.contourArea(contour)
if(area > 300):
x, y, w, h = cv2.boundingRect(contour)
img = cv2.rectangle(img, (x, y),
(x + w, y + h),
(255, 0, 0), 2)
cv2.putText(img, "Blue", (x, y),
cv2.FONT_HERSHEY_SIMPLEX,
1.0, (255, 0, 0),2)
resize = cv2.resize(img, (800, 480))
cv2.imshow("All clg", resize)
cv2.waitKey(0)
I split the image into half just to avoid unnecessary noise reductions. Then rotated the image to make the text horizontal. Binarized the image and obtained the three big blue-colored covid19 labels. Sorted them according to their y-coordinate (top to bottom). Performed some operations to obtain the region of interest (ROI, the two stripes which I named labels). Binarized each label to obtain the two contours of the colored stripes. Adjusted the gamma of the labels so that the colors become a little darker. Obtained the red-component of each stripe. Sorted them according to their x-coordinate (left to right). Assigned the stripe with the highest red value the red color, the other as purple.
Code:
def adjust_gamma(image, gamma=1.0):
# build a lookup table mapping the pixel values [0, 255] to
# their adjusted gamma values
invGamma = 1.0 / gamma
table = np.array([((i / 255.0) ** invGamma) * 255
for i in np.arange(0, 256)]).astype("uint8")
# apply gamma correction using the lookup table
return cv2.LUT(image, table)
def rotate(image: np.ndarray,angle, background_color):
old_width, old_height = image.shape[:2]
angle_radian = math.radians(angle)
width = abs(np.sin(angle_radian) * old_height) + abs(np.cos(angle_radian) * old_width)
height = abs(np.sin(angle_radian) * old_width) + abs(np.cos(angle_radian) * old_height)
image_center = tuple(np.array(image.shape[1::-1]) / 2)
rot_mat = cv2.getRotationMatrix2D(image_center, angle, 1.0)
rot_mat[1, 2] += (width - old_width) / 2
rot_mat[0, 2] += (height - old_height) / 2
return cv2.warpAffine(image, rot_mat, (int(round(height)), int(round(width))), borderValue=background_color)
img = cv2.imread("covid.jpg")
rows,cols = img.shape[:2]
img = img[:,:cols//2]
rot = rotate(img,90,(255,255,255))
gray = cv2.cvtColor(rot,cv2.COLOR_BGR2GRAY)
otsu = cv2.threshold(gray,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)[1]
opening = cv2.morphologyEx(otsu,cv2.MORPH_OPEN,np.ones((3,3),np.uint8),iterations=3)
dilate = cv2.dilate(opening,np.ones((1,5),np.uint8),iterations=5)
dilate2 = cv2.dilate(dilate,np.ones((3,3),np.uint8),iterations=3)
contours,_ = cv2.findContours(dilate2,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
lables = [] # the three covid19
y_coords = [] # to sort the lables from top to bottom (the one with single color comes 1st)
for cnt in contours:
x,y,w,h = cv2.boundingRect(cnt)
x = x + 10
w = w - 20
y_coords.append(y)
lables.append(rot[y+h//3:y+h-h//3,x+w//2:x+int(w*0.70)])
y_coords,lables = zip(*sorted(zip(y_coords,lables))) # sorting the lables from top to bottom
y_coords = list(y_coords)
lables = list(lables)
otsus = []
grays = []
for image in lables:
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
grays.append(gray)
mean = np.mean(gray)
median = np.median(gray)
otsus.append(cv2.threshold(gray,median-10,255,cv2.THRESH_BINARY)[1])
s = [] # just a list to store all the red and purple stripes
for i in range(len(otsus)):
otsus[i] = ~otsus[i]
rows,cols = otsus[i].shape[:2]
M = np.float32([[1,0,5],[0,1,5]])
lables[i] = cv2.warpAffine(lables[i],M,(cols+5,rows+10)) # giving some padding
# adjusting the gamma to make the colors more dark
lables[i] = adjust_gamma(lables[i],gamma=0.40)
otsus[i] = cv2.warpAffine(otsus[i],M,(cols+5,rows+10)) # same padding
strips,_ = cv2.findContours(otsus[i],cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
x_coords = [] # to sort the red and purple stripes from left to right
color = ["red","red"] # setting inital colors both to red
red_components = []
for j in range(len(strips)):
x,y,w,h = cv2.boundingRect(strips[j])
x_coords.append(x)
# strip = lables[i][y:y+h,x:x+w,:]
# strip = lables[i][y+h//4:y+h-h//4,x+w//4:x+w-w//4,:]
strip = lables[i][y:y+h,x+w//5:x+w-w//5,:]
s.append(strip)
try:
strip_color_b = int(np.mean(strip[0]))
strip_color_g = int(np.mean(strip[1]))
strip_color_r = int(np.mean(strip[2]))
# strip_color = (strip_color_b,strip_color_g,strip_color_r)
red = strip_color_r
red_components.append(red)
except IndexError:
print("Lable number ",i,"has only a single color.")
x_coords,red_components = zip(*sorted(zip(x_coords,red_components))) # simultaneously sorting the red and purple stripes from left to right
try:
if red_components[0] < red_components[1]:
color[0] = "purple"
else:
color[1] = "purple"
# print("red components = ",red_components)
print("Lable = ",i,"strip = ",0,"color = ",color[0])
print("Lable = ",i,"strip = ",1,"color = ",color[1]) # LEFT to RIGHT
except IndexError:
print("Lable number ",i,"is excluded. Continuing with further labels.")
print("TOTAL NUMBER OF LABLES = ",len(lables))
# reading the text
custom_oem_psm_config = r'--oem 3 --psm 3'
print(pytesseract.image_to_string(otsu,config=custom_oem_psm_config))
cv2.imshow("Lable 0",lables[0])
cv2.imshow("Lable 1",lables[1])
cv2.imshow("Lable 2",lables[2])
cv2.imshow("rotated",rot)
cv2.waitKey(0)
OUTPUT:
Lable number 0 has only a single color.
Lable number 0 is excluded. Continuing with further labels.
Lable = 1 strip = 0 color = purple
Lable = 1 strip = 1 color = red
Lable = 2 strip = 0 color = purple
Lable = 2 strip = 1 color = red
TOTAL NUMBER OF LABLES = 3
COVID-19Aq
COVID-19Ag
Please correct me if I am wrong.