Related
I am working on a OCR project and the document image needs unwarping operation. But I find that the color of the background is very similar as the paper.
To deal with this issue, my solution is to find the text part and filled as a connected polygon. The code is as follows.
import cv2
import imutils
import numpy as np
#function to order points to proper rectangle
def order_points(pts):
# initialzie a list of coordinates that will be ordered
# such that the first entry in the list is the top-left,
# the second entry is the top-right, the third is the
# bottom-right, and the fourth is the bottom-left
rect = np.zeros((4, 2), dtype="float32")
# the top-left point will have the smallest sum, whereas
# the bottom-right point will have the largest sum
s = pts.sum(axis=1)
rect[0] = pts[np.argmin(s)]
rect[2] = pts[np.argmax(s)]
# now, compute the difference between the points, the
# top-right point will have the smallest difference,
# whereas the bottom-left will have the largest difference
diff = np.diff(pts, axis=1)
rect[1] = pts[np.argmin(diff)]
rect[3] = pts[np.argmax(diff)]
# return the ordered coordinates
return rect
#function to transform image to four points
def four_point_transform(image, pts):
# obtain a consistent order of the points and unpack them
# individually
rect = order_points(pts)
# # multiply the rectangle by the original ratio
# rect *= ratio
(tl, tr, br, bl) = rect
# compute the width of the new image, which will be the
# maximum distance between bottom-right and bottom-left
# x-coordiates or the top-right and top-left x-coordinates
widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))
widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))
maxWidth = max(int(widthA), int(widthB))
# compute the height of the new image, which will be the
# maximum distance between the top-right and bottom-right
# y-coordinates or the top-left and bottom-left y-coordinates
heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))
heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))
maxHeight = max(int(heightA), int(heightB))
# now that we have the dimensions of the new image, construct
# the set of destination points to obtain a "birds eye view",
# (i.e. top-down view) of the image, again specifying points
# in the top-left, top-right, bottom-right, and bottom-left
# order
dst = np.array([
[0, 0],
[maxWidth - 1, 0],
[maxWidth - 1, maxHeight - 1],
[0, maxHeight - 1]], dtype="float32")
# compute the perspective transform matrix and then apply it
M = cv2.getPerspectiveTransform(rect, dst)
warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))
# return the warped image
return warped
#function to find two largest countours which ones are may be
# full image and our rectangle edged object
def findLargestCountours(cntList, cntWidths):
newCntList = []
newCntWidths = []
#finding 1st largest rectangle
first_largest_cnt_pos = cntWidths.index(max(cntWidths))
# adding it in new
newCntList.append(cntList[first_largest_cnt_pos])
newCntWidths.append(cntWidths[first_largest_cnt_pos])
#removing it from old
cntList.pop(first_largest_cnt_pos)
cntWidths.pop(first_largest_cnt_pos)
#finding second largest rectangle
seccond_largest_cnt_pos = cntWidths.index(max(cntWidths))
# adding it in new
newCntList.append(cntList[seccond_largest_cnt_pos])
newCntWidths.append(cntWidths[seccond_largest_cnt_pos])
#removing it from old
cntList.pop(seccond_largest_cnt_pos)
cntWidths.pop(seccond_largest_cnt_pos)
print('Old Screen Dimentions filtered', cntWidths)
print('Screen Dimentions filtered', newCntWidths)
return newCntList, newCntWidths
#driver function which identifieng 4 corners and doing four point transformation
def convert_object(image, screen_size = None, isDebug = False):
image = imutils.resize(image,height=600)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = cv2.bilateralFilter(gray, 11, 17, 17) # 11 //TODO 11 FRO OFFLINE MAY NEED TO TUNE TO 5 FOR ONLINE
gray = cv2.medianBlur(gray, 5)
kernel = np.ones((5, 5), np.uint8)
grad = cv2.morphologyEx(gray, cv2.MORPH_GRADIENT, kernel)
if isDebug :
cv2.imshow('grad', grad)
_, bw = cv2.threshold(grad, 0.0, 255.0, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
if isDebug :
cv2.imshow('bw', bw)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (30, 50))
connected = cv2.morphologyEx(bw, cv2.MORPH_CLOSE, kernel)
kernel=np.ones((5,5),np.uint8)
connected=cv2.dilate(connected,kernel,iterations=3)
# using RETR_EXTERNAL instead of RETR_CCOMP
countours, hierarchy = cv2.findContours(connected.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
if isDebug : print('length of countours ', len(countours))
imageCopy = image.copy()
if isDebug : cv2.imshow('drawn countours', cv2.drawContours(imageCopy, countours, -1, (0, 255, 0), 1))
cnts = sorted(countours, key=cv2.contourArea, reverse=True)
hull = np.zeros(image.shape, dtype=np.uint8)
for cnt in cnts:
approx = cv2.convexHull(cnt)
cv2.drawContours(hull, [approx], -1, (255, 0, 0), thickness=cv2.FILLED)
if isDebug : cv2.imshow("hull", hull)
grayhull = cv2.cvtColor(hull, cv2.COLOR_BGR2GRAY)
countours, hierarchy = cv2.findContours(grayhull.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
if isDebug : print('length of countours ', len(countours))
imageCopy = image.copy()
if isDebug : cv2.imshow('drawn countours', cv2.drawContours(imageCopy, countours, -1, (0, 255, 0), 3))
mask = np.zeros(bw.shape, dtype=np.uint8)
rgb = image.copy()
for idx in range(len(countours)):
x, y, w, h = cv2.boundingRect(countours[idx])
mask[y:y+h, x:x+w] = 0
cv2.drawContours(mask, countours, idx, (255, 255, 255), -1)
r = float(cv2.countNonZero(mask[y:y+h, x:x+w])) / (w * h)
if r > 0.45 and w > 8 and h > 8:
cv2.rectangle(rgb, (x, y), (x+w-1, y+h-1), (0, 255, 0), 2)
if isDebug : cv2.imshow("rgb", rgb)
# approximate the contour
cnts = sorted(countours, key=cv2.contourArea, reverse=True)
screenCntList = []
scrWidths = []
for cnt in cnts:
peri = cv2.arcLength(cnt, True) # cnts[1] always rectangle O.o
approx = cv2.approxPolyDP(cnt, 0.05 * peri, True)
screenCnt = approx
print(len(approx))
if (len(screenCnt) == 4):
(X, Y, W, H) = cv2.boundingRect(cnt)
# print('X Y W H', (X, Y, W, H))
screenCntList.append(screenCnt)
scrWidths.append(W)
print('Screens found :', len(screenCntList))
print('Screen Dimentions', scrWidths)
if len(screenCntList) > 1:
screenCntList, scrWidths = findLargestCountours(screenCntList, scrWidths)
print(screenCntList)
if isDebug : cv2.imshow(" Screen", cv2.drawContours(image.copy(), [screenCntList[0]], -1, (0, 255, 0), 3))
pts = screenCntList[0].reshape(4, 2)
print('Found bill rectagle at ', pts)
rect = order_points(pts)
print(rect)
# apply the four point tranform to obtain a "birds eye view" of
# the image
warp = four_point_transform(image, pts)
# show the original and warped images
if(isDebug):
cv2.imshow("Original", image)
cv2.imshow("warp", warp)
cv2.waitKey(0)
cv2.imwrite("result.png",warp.astype(np.uint8))
convert_object(cv2.imread('abc.jpg'), isDebug=True)
But because of the paragram indentation, the approxPolyDP can not find the boundary well.
Is there any effective solution for this case? thanks in advance.
I am currently working on a project to detect and score bullet holes on a simple ringed target.
The code I have now assigns the holes a score based on the centre of the contour. This is the Target1 I am using
If a contour overlaps the boundary of a higher zone I want it to be assigned the higher score, regardless of where the centre is. An example is shown below:
]2
The code and output of my program are shown below, where the scores are assigned respective of the centre rather than overlapping boundaries:
from cv2 import cv2
import numpy as np
import imutils
def centroid(contour):
M = cv2.moments(contour)
cx = int(round(M['m10']/M['m00']))
cy = int(round(M['m01']/M['m00']))
centre = (cx, cy)
return centre
def getScore(scoreboundaries, HoleDist): #function to assign a score to each hole
score = 0
if scoreboundaries[0]>HoleDist:
score = 10
for i in range(1, len(scoreboundaries)):
if scoreboundaries[i-1]<=HoleDist<scoreboundaries[i]:
score = len(scoreboundaries) - i
return score
default = cv2.imread("3.jpg")
img = cv2.resize(default,(640,640))
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
h,s,v = cv2.split(hsv)
v_mask = cv2.inRange(v, 0, 155)
cnts = cv2.findContours(v_mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
for c in cnts:
if cv2.contourArea(c) > 10000:
cv2.drawContours(img, [c], -1, (0, 255, 0), 2)
area_max = cv2.contourArea(c)
radius_max = np.sqrt(area_max / np.pi)
section_size = radius_max / 9
centre_v_mask = cv2.inRange(v, 215, 255)
cnts = cv2.findContours(centre_v_mask.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
for c in cnts:
if cv2.contourArea(c) > 10:
centre_coords = centroid(c)
h_mask = cv2.inRange(h, 0, 30)
h_mask = cv2.medianBlur(h_mask, 11)
cnts = cv2.findContours(h_mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
holes = []
HoleDists = []
scoreboundaries = []
for i in range(1,10): #calculate other rings
cv2.circle(img, centre_coords, int(i*(section_size)), (255, 0, 0), 1)
scoreboundaries.append(int(i*(section_size)))
for c in cnts: #plot bullet holes
if cv2.contourArea(c) > 1:
x,y,w,h = cv2.boundingRect(c)
pts =[(x, y), (x+w, y), (x, y+h), (x+w, y+h)]
centre_holes = centroid(c)
pts.append(centre_holes)
pointscore = 0
for pt in pts:
X = pt[0]
Y = pt[1]
HoleDist = np.sqrt((X-centre_coords[0])**2 + (Y - centre_coords[1])**2)
HoleDists.append(HoleDist)
score = getScore(scoreboundaries, HoleDist)
if score>pointscore:
pointScore = score
cv2.circle(img, (centre_holes), 1, (0, 0, 255), -1)
cv2.rectangle(img, (x,y),(x+w,y+h),(0,0,255),2)
cv2.drawContours(img, [c], -1, (0, 255, 0), 1)
cv2.putText(img, "Score: " + str(pointScore), (centre_holes[0] - 20, centre_holes[1] + 20),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)
cv2.imshow('frame', img)
cv2.waitKey(0)
I was wondering where I am going wrong. All help is appreciated, thank you.
It's a classic Python bug. You capitalize pointscore at the lines:
pointScore = score
cv2.putText( img, "Score: " + str(pointScore) ....
So you're really just using the last score you saw since pointscore is always 0 and pointScore = score.
Here's the picture with those two typos fixed. Also bear in mind that OpenCV contours are just a set of points that connect together. You can use the same scoring code to iterate through the contour points if you want.
If you want to swap to using contour points it's a small change.
Change this:
for pt in pts:
X = pt[0]
Y = pt[1]
To this:
for pt in c:
pt = pt[0] # contour points have an extra pair of brackets [[x,y]]
X = pt[0]
Y = pt[1]
I need to calculate major axis length,minor axis length and eccentricity.I am using fitEllipse() for calculation.But some object's major axis length gives higher value then height value.I couldn't find to how fix the problem.
Here is the code:
import cv2
import numpy as np
img = cv2.imread('Resources/son1.png')
#input and output image
def getContours(img,imgContour):
#Find contours and set contour retrivial mode
contours, hierarchy = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
#cnt is the one of the contours for every object in image
for cnt in contours:
# Contour for shape and I find area and length using this contour
cv2.drawContours(imgContour, cnt,-1, (0, 255, 0), 1)
BoundingBox(Rectangle):gives the bounding box parameters => (x,y,width,height)
x, y, w, h = cv2.boundingRect(cnt)
img = cv2.rectangle(imgContour,(x, y),(x+w, y+h),(0,255,0),2)
print("bounding_box(x,y,w,h):", x, y, w, h)
#W: and H: texts (Length)
cv2.putText(imgContour, "w={},h={}".format(w,h), (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255,0,0),1,16)
# give us specific contour (cnt) area
area = cv2.contourArea(cnt)
# Area text
cv2.putText(imgContour, "Area: " + str(int(area)), (x + w + 20, y + 45), cv2.FONT_HERSHEY_SIMPLEX, 0.5,
(255, 0, 0), 1, 16)
# # #Center of objects in picture
#
# M = cv2.moments(cnt)
# print(M)
# centx = int(M['m10'] / M['m00'])
# centy = int(M['m01'] / M['m00'])
#
# print(centx,centy)
MajorAxislength,MinorAxisLength,Eccentricity
(x,y), (minorAxisLength, majorAxisLength), angle = cv2.fitEllipse(cnt)
ellipse = cv2.fitEllipse(cnt)
cv2.ellipse(img,ellipse,(0, 0, 255), 2)
#semi-major and semi-minor
a = majorAxisLength / 2
b = minorAxisLength / 2
#Formula of eccentricity is :
Eccentricity = round(np.sqrt(pow(a, 2) - pow(b, 2))/a, 2)
x = int(x + w / 2) + 1
y = int(y + h / 2) + 1
cv2.putText(imgContour, 'Minor ='+str(round(minorAxisLength, 2)), (x+10, y), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 1,16)
cv2.putText(imgContour, 'Major ='+str(round(majorAxisLength, 2)), (x+10, y+20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 1, 16)
cv2.putText(imgContour, 'Eccentricity ='+str(round(Eccentricity, 3)), (x+10, y+40), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 1, 16)
imgContour = img.copy()
imgBlur = cv2.GaussianBlur(img, (7, 7),1)
imgGray = cv2.cvtColor(imgBlur, cv2.COLOR_BGR2GRAY)
ImgThresh = cv2.threshold(imgGray, 100 , 255, cv2.THRESH_BINARY)[1]
imgCanny = cv2.Canny(imgGray,50,50)
getContours(imgCanny,imgContour)
cv2.imshow("Original Image", img)
cv2.imshow("Canny", imgCanny)
cv2.imshow("Contour", imgContour)
cv2.waitKey(0)
cv2.destroyAllWindows()
I added original image:
Here is the result image:
This is my input sudoku image : 0000.png and this is my desired output image : output.
I am trying to find the contours of the sudoku board so I can extract the board separately. This is the code I've tried so far.
import cv2
import operator
import numpy as np
import matplotlib.pyplot as plt
image = cv2.imread("0000.png")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (5,5), 0)
thresh = cv2.adaptiveThreshold(blur, 255, 1, 1, 11, 2)
contours,_ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
max_area = 0
c = 0
for i in contours:
area = cv2.contourArea(cv2.UMat(i))
if area > 1000:
if area > max_area:
max_area = area
best_cnt = i
image = cv2.drawContours(image, contours, c, (0, 255, 0), 3)
c+=1
mask = np.zeros((gray.shape),np.uint8)
cv2.drawContours(mask,[best_cnt],0,255,-1)
cv2.drawContours(mask,[best_cnt],0,0,2)
out = np.zeros_like(gray)
out[mask == 255] = gray[mask == 255]
blur = cv2.GaussianBlur(out, (11,11), 0)
thresh = cv2.adaptiveThreshold(blur, 255, 1, 1, 11, 2)
c = 0
for i in contours:
area = cv2.contourArea(i)
if area > 1000/2:
cv2.drawContours(image, contours, c, (0, 255, 0), 3)
c+=1
These are the images I'm able to extract after finding adaptive thresholds and using contours to draw masks.
plt.imshow(out, cmap='gray')
plt.imshow(thresh, cmap='gray')
plt.imshow(image)
How can I modify my code to generate the output image, where the sudoku board is cropped as in output ?
EDIT :
I tried extracting the coordinates of the biggest blog and cropped the largest rectangle, but it's still not accurate as the output image I desire.
contours, h = cv2.findContours(out.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = sorted(contours, key=cv2.contourArea, reverse=True)
polygon = contours[0]
bottom_right, _ = max(enumerate([pt[0][0] + pt[0][1] for pt in polygon]), key=operator.itemgetter(1))
top_left, _ = min(enumerate([pt[0][0] + pt[0][1] for pt in polygon]), key=operator.itemgetter(1))
bottom_left, _ = min(enumerate([pt[0][0] - pt[0][1] for pt in polygon]), key=operator.itemgetter(1))
top_right, _ = max(enumerate([pt[0][0] - pt[0][1] for pt in polygon]), key=operator.itemgetter(1))
box = [polygon[top_left][0], polygon[top_right][0], polygon[bottom_right][0], polygon[bottom_left][0]]
def distance_between(p1, p2):
"""Returns the scalar distance between two points"""
a = p2[0] - p1[0]
b = p2[1] - p1[1]
return np.sqrt((a ** 2) + (b ** 2))
def crop_and_warp(img, crop_rect):
"""Crops and warps a rectangular section from an image into a square of similar size."""
top_left, top_right, bottom_right, bottom_left = crop_rect[0], crop_rect[1], crop_rect[2], crop_rect[3]
src = np.array([top_left, top_right, bottom_right, bottom_left], dtype='float32')
side = max([
distance_between(bottom_right, top_right),
distance_between(top_left, bottom_left),
distance_between(bottom_right, bottom_left),
distance_between(top_left, top_right)
])
dst = np.array([[0, 0], [side - 1, 0], [side - 1, side - 1], [0, side - 1]], dtype='float32')
m = cv2.getPerspectiveTransform(src, dst)
return cv2.warpPerspective(img, m, (int(side), int(side)))
cropped = crop_and_warp(out, box)
plt.imshow(cropped, cmap='gray')
The border is not neatly extracted unlike my desired output image. The reason I want it in this format is because then then the image can be split into 81 images with the help of a 9x9 grid and I could detect all the numbers in each of the image. The black trace above the image is causing troubles there.
I am trying to find the dominant color inside a contour (black or white).
I am using OpenCV to read an image and extract white on black images. This is what I got so far:
The green outline is the contour, the blue lines the bounding box. So I this instance I am trying to extract the numbers 87575220 but as you can see it also recognizes some random artifacts and for instance the letter G. I think the solution would be to find the dominant colour inside of the contours and that colour should be close to white. I don't have any idea how to do this though.
This the code I have at the moment:
import argparse
import cv2
import imutils
import numpy as np
parser = argparse.ArgumentParser()
parser.add_argument("--image", "-i", required=True, help="Image to detect blobs from")
args = vars(parser.parse_args())
image = cv2.imread(args["image"])
image = imutils.resize(image, width=1200)
grey = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
(minVal, maxVal, minLoc, maxLoc) = cv2.minMaxLoc(grey)
maxval_10 = maxVal * 0.5
ret, threshold = cv2.threshold(grey, maxval_10, 255, cv2.THRESH_BINARY)
canny = cv2.Canny(grey, 200, 250)
lines = cv2.HoughLines(canny, 1, np.pi / 180, 140)
print(maxVal)
theta_min = 60 * np.pi / 180.
theta_max = 120 * np.pi / 180.0
theta_avr = 0
theta_deg = 0
filteredLines = []
for rho, theta in lines[0]:
a = np.cos(theta)
b = np.sin(theta)
x0 = a * rho
y0 = b * rho
x1 = int(x0 + 1000 * (-b))
y1 = int(y0 + 1000 * (a))
x2 = int(x0 - 1000 * (-b))
y2 = int(y0 - 1000 * (a))
cv2.line(image, (x1, y1), (x2, y2), (0, 0, 255), 2)
if theta_min <= theta <= theta_max:
filteredLines.append(theta)
theta_avr += theta
if len(filteredLines) > 0:
theta_avr /= len(filteredLines)
theta_deg = (theta_avr / np.pi * 180) - 90
else:
print("Failed to detect skew")
image = imutils.rotate(image, theta_deg)
canny = imutils.rotate(canny, theta_deg)
im2, contours, hierarchy = cv2.findContours(canny, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
# cv2.drawContours(image, contours, -1, (0, 255, 0), 1)
cv2.imshow('Contours', im2)
boundingBoxes = []
filteredContours = []
for cnt in contours:
(x, y, w, h) = cv2.boundingRect(cnt)
if (h > 20 and h < 90 and w > 5 and w < h):
if cv2.contourArea(cnt, True) <= 0:
boundingBoxes.append((x, y, w, h))
filteredContours.append(cnt)
for x, y, w, h in boundingBoxes:
cv2.rectangle(image, (x, y), (x + w, y + h), (255, 0, 0), 2)
cv2.drawContours(image, filteredContours, -1, (0, 255, 0), 1)
cv2.imshow('Image', image)
cv2.imshow('Edges', canny)
cv2.imshow('Threshold', threshold)
cv2.waitKey(0)
cv2.destroyAllWindows()
This is the original picture:
I would try to make a ROI before I start searching for numbers. You have not give the original image so this example is made with the image you posted (with boxes and contours allready drawn). Should aslo work with the original though. Steps are written in the example code. Hope it helps. Cheers!
Example code:
import cv2
import numpy as np
# Read the image and make a copy then create a blank mask
img = cv2.imread('dominant.jpg')
img2 = img.copy()
h,w = img.shape[:2]
mask = np.zeros((h,w), np.uint8)
# Transform to gray colorspace and perform histogram equalization
gray = cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY)
equ = cv2.equalizeHist(gray)
# Transform all pixels above thershold to white
black = np.where(equ>10)
img2[black[0], black[1], :] = [255, 255, 255]
# Transform to gray colorspace and make a thershold then dilate the thershold
gray = cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
kernel = np.ones((15,15),np.uint8)
dilation = cv2.dilate(thresh,kernel,iterations = 1)
# Search for contours and select the biggest one and draw it on mask
_, contours, hierarchy = cv2.findContours(dilation,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
cnt = max(contours, key=cv2.contourArea)
cv2.drawContours(mask, [cnt], 0, 255, -1)
# Perform a bitwise operation
res = cv2.bitwise_and(img, img, mask=mask)
# Display the ROI
cv2.imshow('img', res)
Result:
You could create a mask out of each contour:
mask = np.zeros(image.shape, dtype="uint8")
cv2.drawContours(mask, [cnt], -1, 255, -1)
and then calculate the mean value of all pixels inside of the mask:
mean = cv2.mean(image, mask=mask)
and then check whether mean is close enough to white
Colors and mean do not match well due to color space properties. I would create an histogram and select the most frequent one (some color down sampling could be applied too)