Image processing detection - python

I want to detect the circlip in the fixture. if circlip is not present it should give a message "circlip not present".

Binarization applied to the saturation component gives interesting results.
vs.
But the circlip needs to remain tinted.

The solution provided by #YvesDaoust gives good insights into solving the problem.
Thresholding on Saturation channel as suggested by #YvesDaoust, followed by Morphological closing, and largest connected component extraction solves this specific problem.
Note that this solution is not general for all illumination conditions, resolutions, rotation, color, etc...
But it might work for similar conditions.
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import cv2
import numpy as np
img = cv2.imread("input2.jpg")
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
s = hsv[...,1]
th = 100
s[s<th]=0
op = cv2.MORPH_CLOSE
morph_elem = cv2.MORPH_ELLIPSE
morph_size = 5
element = cv2.getStructuringElement(morph_elem, (2*morph_size + 1, 2*morph_size+1), (morph_size, morph_size))
mph = cv2.morphologyEx(s, op, element)
# Reference: https://stackoverflow.com/a/47057324
def lcc (image):
image = image.astype('uint8')
nb_components, output, stats, centroids = cv2.connectedComponentsWithStats(image, connectivity=4)
sizes = stats[:, -1]
max_label = 1
max_size = sizes[1]
for i in range(2, nb_components):
if sizes[i] > max_size:
max_label = i
max_size = sizes[i]
img2 = np.zeros(output.shape)
img2[output == max_label] = 255
img2 = img2.astype(np.uint8)
return img2
mask = lcc(mph)
thresh = 20000000
if np.sum(mask) < thresh:
print("circlip not present")
res = img
else:
res = cv2.bitwise_and(img,img,mask = mask)
cv2.namedWindow("img", cv2.WINDOW_NORMAL)
cv2.imshow("img", res)
cv2.waitKey(0)

Related

Python - Remove black outline & overlay PNG image on JPEG image

I have two images:
Fragments from painting
Whole painting
I need to solve two issues:
1st. On the first image, I need to remove the black outline from each fragment. I've tried threshold and erosion, but neither of them worked. How can I do that?
2nd. I can't overlap the first image on the second, and I really don't know why. It always result on the first image overlapping it totally and putting black pixels where it should be possible to see the second image.
I'm using Python3 and OpenCV 3.2, on Ubuntu 18.04.
My program:
from PIL import Image
from matplotlib import pyplot as plt
import numpy as np
import cv2
import sys
plano_f = cv2.imread("Domenichino_Virgin-and-unicorn.jpg")
sobrepor = cv2.imread("Domenichino_Virgin-and-unicorn_img.png")
plano_f = cv2.cvtColor(plano_f, cv2.COLOR_BGR2GRAY, -1)
#sobrepor_BGRA = cv2.cvtColor(sobrepor, cv2.COLOR_BGR2BGRA)
sobrepor_BGRA = cv2.imread("nova_png.png", -1)
plt.imshow(sobrepor_BGRA),plt.show()
rows, cols, han = sobrepor_BGRA.shape
total = rows*cols
#printProgressBar(0, total, prefix="Executando...", suffix="completo", length=50)
'''for i in range(rows):
for j in range(cols):
if(sobrepor_BGRA[i, j][0] <= 5 and sobrepor_BGRA[i, j][1] <= 5 and sobrepor_BGRA[i, j][2] <= 5 and sobrepor_BGRA[i, j][3] != 0):
sobrepor_BGRA[i, j] = (0, 0, 0, 0)
#printProgressBar(i*j, total, prefix='Executando...', suffix='completo', length=50)
sys.stdout.write("\rExecutando linha " + str(i) + " de " + str(rows) + "...")
sys.stdout.flush()
cv2.imwrite("nova_png.png", sobrepor_BGRA)'''
kernel = cv2.getStructuringElement(cv2.MORPH_CROSS, (3,3))
#sobrepor_BGRA = cv2.cvtColor(sobrepor_BGRA, cv2.COLOR_BGRA2GRAY, -1)
sobrepor_BGRA = cv2.erode(sobrepor_BGRA, kernel, iterations=3)
#sobrepor_BGRA = cv2.cvtColor(sobrepor_BGRA, cv2.COLOR_GRAY2BGRA)
cv2.imwrite("nova_png2.png", sobrepor_BGRA)
#sobrepor_RGBA = cv2.cvtColor(sobrepor_BGRA, cv2.COLOR_BGRA2RGBA)
#plt.imshow(sobrepor_RGBA),plt.show()
sys.stdout.write("\nPronto!")
nova_img = cv2.addWeighted(sobrepor_BGRA, 1, plano_f, 0, 0)
cv2.imwrite("combined.png", nova_img)
plt.imshow(nova_img),plt.show()
You can use bitwise operations to do this. The idea is to obtain a mask of the missing sections of the fragments then bitwise-or the two sections together. Here's two halfs of the image, one is the fragments you already have and the other is the missing sections.
We combine both halves to get the whole painting
import cv2
import numpy as np
fragment = cv2.imread('1.jpg')
whole = cv2.imread('2.jpg')
fragment[np.where((fragment <= [250,250,250]).all(axis=2))] = [0]
result1 = cv2.bitwise_and(whole, fragment)
result2 = cv2.bitwise_and(whole, 255 - fragment)
final = result1 + result2
cv2.imshow('result1', result1)
cv2.imshow('result2', result2)
cv2.imshow('final', final)
cv2.waitKey()
1st - your image is a jpeg image which means that the black lines around the pieces are going to be imperfect due to compression artifacts, a simple threshold or dilation isn't going to perfectly remove these. You can try saving in a lossless format and modifying by hand in paint or something to clean up, you may even want to perform this step after doing an erosion and cleaning up most of it.
2nd - why don't you just copy with a mask using the copyTo function, here is an example:
import cv2
img1 = cv2.imread('x2djw.jpg')
img2 = cv2.imread('5RnNh.jpg')
img2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
thr, img1_mask = cv2.threshold(img1, 250, 255, cv2.THRESH_BINARY_INV)
img1_mask = img1_mask[:, :, 0] & img1_mask[:, :, 1] & img1_mask[:, :, 2]
el = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
img1_mask = cv2.erode(img1_mask, el)
img2 = cv2.merge((img2, img2, img2))
img2 = cv2.copyTo(img1, img1_mask, img2)
cv2.imwrite('test_result.png', img2)

Writing robust (size invariant) circle detection (Watershed)

Edit: Quick Summary so far: I use the watershed algorithm but I have probably a problem with threshold. It didn't detect the brighter circles.
New: Fast radial symmetry transform approach which didn't quite work eiter (Edit 6).
I want to detect circles with different sizes. The use case is to detect coins on an image and to extract them solely. -> Get the single coins as single image files.
For this I used the Hough Circle Transform of open-cv:
(https://docs.opencv.org/2.4/doc/tutorials/imgproc/imgtrans/hough_circle/hough_circle.html)
import sys
import cv2 as cv
import numpy as np
def main(argv):
## [load]
default_file = "data/newcommon_1euro.jpg"
filename = argv[0] if len(argv) > 0 else default_file
# Loads an image
src = cv.imread(filename, cv.IMREAD_COLOR)
# Check if image is loaded fine
if src is None:
print ('Error opening image!')
print ('Usage: hough_circle.py [image_name -- default ' + default_file + '] \n')
return -1
## [load]
## [convert_to_gray]
# Convert it to gray
gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
## [convert_to_gray]
## [reduce_noise]
# Reduce the noise to avoid false circle detection
gray = cv.medianBlur(gray, 5)
## [reduce_noise]
## [houghcircles]
rows = gray.shape[0]
circles = cv.HoughCircles(gray, cv.HOUGH_GRADIENT, 1, rows / 8,
param1=100, param2=30,
minRadius=0, maxRadius=120)
## [houghcircles]
## [draw]
if circles is not None:
circles = np.uint16(np.around(circles))
for i in circles[0, :]:
center = (i[0], i[1])
# circle center
cv.circle(src, center, 1, (0, 100, 100), 3)
# circle outline
radius = i[2]
cv.circle(src, center, radius, (255, 0, 255), 3)
## [draw]
## [display]
cv.imshow("detected circles", src)
cv.waitKey(0)
## [display]
return 0
if __name__ == "__main__":
main(sys.argv[1:])
I tried all parameters (rows, param1, param2, minRadius, and maxRadius) to optimize the results. This worked very well for one specific image but other images with different sized coins didn't work.
Examples:
Parameters
circles = cv.HoughCircles(gray, cv.HOUGH_GRADIENT, 1, rows / 16,
param1=100, param2=30,
minRadius=0, maxRadius=120)
With the same parameters:
Changed to rows/8
I also tried two other approaches of this thread: writing robust (color and size invariant) circle detection with opencv (based on Hough transform or other features)
The approach of fireant leads to this result:
The approach of fraxel didn't work either.
For the first approach: This happens with all different sizes and also the min and max radius.
How could I change the code, so that the coin size is not important or that it finds the parameters itself?
Thank you in advance for any help!
Edit:
I tried the watershed algorithm of Open-cv, as suggested by Alexander Reynolds: https://docs.opencv.org/3.4/d3/db4/tutorial_py_watershed.html
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('data/P1190263.jpg')
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(gray,0,255,cv.THRESH_BINARY_INV+cv.THRESH_OTSU)
# noise removal
kernel = np.ones((3,3),np.uint8)
opening = cv.morphologyEx(thresh,cv.MORPH_OPEN,kernel, iterations = 2)
# sure background area
sure_bg = cv.dilate(opening,kernel,iterations=3)
# Finding sure foreground area
dist_transform = cv.distanceTransform(opening,cv.DIST_L2,5)
ret, sure_fg = cv.threshold(dist_transform,0.7*dist_transform.max(),255,0)
# Finding unknown region
sure_fg = np.uint8(sure_fg)
unknown = cv.subtract(sure_bg,sure_fg)
# Marker labelling
ret, markers = cv.connectedComponents(sure_fg)
# Add one to all labels so that sure background is not 0, but 1
markers = markers+1
# Now, mark the region of unknown with zero
markers[unknown==255] = 0
markers = cv.watershed(img,markers)
img[markers == -1] = [255,0,0]
#Display:
cv.imshow("detected circles", img)
cv.waitKey(0)
It works very well on the test image of the open-cv website:
But it performs very bad on my own images:
I can't really think of a good reason why it's not working on my images?
Edit 2:
As suggested I looked at the intermediate images. The thresh looks not good in my opinion. Next, there is no difference between opening and dist_transform. The corresponding sure_fg shows the detected images.
thresh:
opening:
dist_transform:
sure_bg:
sure_fg:
Edit 3:
I tried all distanceTypes and maskSizes I could find, but the results were quite the same (https://www.tutorialspoint.com/opencv/opencv_distance_transformation.htm)
Edit 4:
Furthermore, I tried to change the (first) threshold function. I used different threshold values instead of the OTSU Function. The best one was with 160, but it was far from good:
In the tutorial it looks like this:
It seems like the coins are somehow too bright to be detected by this algorithm, but I don't know how to improve it?
Edit 5:
Changing the overall contrast and brightness of the image (with cv.convertScaleAbs) didn't improve the results. Increasing the contrast however should increase the "difference" between foreground and background, at least on the normal image. But it even got worse. The corresponding threshold image didn't improved (didn't get more white pixel).
Edit 6: I tried another approach, the fast radial symmetry transform (from here https://github.com/ceilab/frst_python)
import cv2
import numpy as np
def gradx(img):
img = img.astype('int')
rows, cols = img.shape
# Use hstack to add back in the columns that were dropped as zeros
return np.hstack((np.zeros((rows, 1)), (img[:, 2:] - img[:, :-2]) / 2.0, np.zeros((rows, 1))))
def grady(img):
img = img.astype('int')
rows, cols = img.shape
# Use vstack to add back the rows that were dropped as zeros
return np.vstack((np.zeros((1, cols)), (img[2:, :] - img[:-2, :]) / 2.0, np.zeros((1, cols))))
# Performs fast radial symmetry transform
# img: input image, grayscale
# radii: integer value for radius size in pixels (n in the original paper); also used to size gaussian kernel
# alpha: Strictness of symmetry transform (higher=more strict; 2 is good place to start)
# beta: gradient threshold parameter, float in [0,1]
# stdFactor: Standard deviation factor for gaussian kernel
# mode: BRIGHT, DARK, or BOTH
def frst(img, radii, alpha, beta, stdFactor, mode='BOTH'):
mode = mode.upper()
assert mode in ['BRIGHT', 'DARK', 'BOTH']
dark = (mode == 'DARK' or mode == 'BOTH')
bright = (mode == 'BRIGHT' or mode == 'BOTH')
workingDims = tuple((e + 2 * radii) for e in img.shape)
# Set up output and M and O working matrices
output = np.zeros(img.shape, np.uint8)
O_n = np.zeros(workingDims, np.int16)
M_n = np.zeros(workingDims, np.int16)
# Calculate gradients
gx = gradx(img)
gy = grady(img)
# Find gradient vector magnitude
gnorms = np.sqrt(np.add(np.multiply(gx, gx), np.multiply(gy, gy)))
# Use beta to set threshold - speeds up transform significantly
gthresh = np.amax(gnorms) * beta
# Find x/y distance to affected pixels
gpx = np.multiply(np.divide(gx, gnorms, out=np.zeros(gx.shape), where=gnorms != 0),
radii).round().astype(int);
gpy = np.multiply(np.divide(gy, gnorms, out=np.zeros(gy.shape), where=gnorms != 0),
radii).round().astype(int);
# Iterate over all pixels (w/ gradient above threshold)
for coords, gnorm in np.ndenumerate(gnorms):
if gnorm > gthresh:
i, j = coords
# Positively affected pixel
if bright:
ppve = (i + gpx[i, j], j + gpy[i, j])
O_n[ppve] += 1
M_n[ppve] += gnorm
# Negatively affected pixel
if dark:
pnve = (i - gpx[i, j], j - gpy[i, j])
O_n[pnve] -= 1
M_n[pnve] -= gnorm
# Abs and normalize O matrix
O_n = np.abs(O_n)
O_n = O_n / float(np.amax(O_n))
# Normalize M matrix
M_max = float(np.amax(np.abs(M_n)))
M_n = M_n / M_max
# Elementwise multiplication
F_n = np.multiply(np.power(O_n, alpha), M_n)
# Gaussian blur
kSize = int(np.ceil(radii / 2))
kSize = kSize + 1 if kSize % 2 == 0 else kSize
S = cv2.GaussianBlur(F_n, (kSize, kSize), int(radii * stdFactor))
return S
img = cv2.imread('data/P1190263.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
result = frst(gray, 60, 2, 0, 1, mode='BOTH')
cv2.imshow("detected circles", result)
cv2.waitKey(0)
I only get this nearly black output (it has some very dark grey shadows). I don't know what to change and would be thankful for help!

Detection of leaf on unpredictable background

A project I have been working about for some time is a unsupervised leaf segmentation. The leaves are captured on a white or colored paper, and some of them has shadows.
I want to be able to threshold the leaf and also remove the shadow (while reserving the leaf's details); however I cannot use fixed threshold values due to diseases changing the color of the leaf.
Then, I begin to research and find out a proposal by Horprasert et. al. (1999) in "A Statistical Approach for Real-time Robust Background Subtraction and Shadow Detection", which compare areas in the image with colour of the now-known background using the chromacity distortion measure. This measure takes account of the fact that for desaturated colours, hue is not a relevant measure.
Based on it, I was able to achieve the following results:
However, the leaves that are captured on a white paper need to change the Mask V cv2.bitwise_not() giving me the below result:
I'm thinking that I'm forgetting some step to get a complete mask that will work for all or most of my leaves. Samples can be found here.
My Code:
import numpy as np
import cv2
import matplotlib.pyplot as plot
import scipy.ndimage as ndimage
def brightness_distortion(I, mu, sigma):
return np.sum(I*mu/sigma**2, axis=-1) / np.sum((mu/sigma)**2, axis=-1)
def chromacity_distortion(I, mu, sigma):
alpha = brightness_distortion(I, mu, sigma)[...,None]
return np.sqrt(np.sum(((I - alpha * mu)/sigma)**2, axis=-1))
def bwareafilt ( image ):
image = image.astype(np.uint8)
nb_components, output, stats, centroids = cv2.connectedComponentsWithStats(image, connectivity=4)
sizes = stats[:, -1]
max_label = 1
max_size = sizes[1]
for i in range(2, nb_components):
if sizes[i] > max_size:
max_label = i
max_size = sizes[i]
img2 = np.zeros(output.shape)
img2[output == max_label] = 255
return img2
img = cv2.imread("Amostra03.jpeg")
sat = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)[:,:,1]
val = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)[:,:,2]
sat = cv2.medianBlur(sat, 11)
val = cv2.medianBlur(val, 11)
thresh_S = cv2.adaptiveThreshold(sat , 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 401, 10);
thresh_V = cv2.adaptiveThreshold(val , 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 401, 10);
mean_S, stdev_S = cv2.meanStdDev(img, mask = 255 - thresh_S)
mean_S = mean_S.ravel().flatten()
stdev_S = stdev_S.ravel()
chrom_S = chromacity_distortion(img, mean_S, stdev_S)
chrom255_S = cv2.normalize(chrom_S, chrom_S, alpha=0, beta=255, norm_type=cv2.NORM_MINMAX).astype(np.uint8)[:,:,None]
mean_V, stdev_V = cv2.meanStdDev(img, mask = 255 - thresh_V)
mean_V = mean_V.ravel().flatten()
stdev_V = stdev_V.ravel()
chrom_V = chromacity_distortion(img, mean_V, stdev_V)
chrom255_V = cv2.normalize(chrom_V, chrom_V, alpha=0, beta=255, norm_type=cv2.NORM_MINMAX).astype(np.uint8)[:,:,None]
thresh2_S = cv2.adaptiveThreshold(chrom255_S , 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 401, 10);
thresh2_V = cv2.adaptiveThreshold(chrom255_V , 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 401, 10);
images = [img, thresh_S, thresh_V, cv2.bitwise_and(thresh2_S, cv2.bitwise_not(thresh2_V))]
titles = ['Original Image', 'Mask S', 'Mask V', 'S + V']
for i in range(4):
plot.subplot(2,2,i+1),
if i == 0 :
plot.imshow(images[i])
else :
plot.imshow(images[i], cmap='gray')
plot.title(titles[i])
plot.xticks([]),plot.yticks([])
plot.show()
Any idea to solve this issue?
Try this on...I'm using "grabCut" from the openCV lib. It's not perfect, but it might be a good start.
import cv2
import numpy as np
from matplotlib import pyplot as plt
import matplotlib
#%matplotlib inline #uncomment if in notebook
def mask_leaf(im_name, external_mask=None):
im = cv2.imread(im_name)
im = cv2.blur(im, (5,5))
height, width = im.shape[:2]
mask = np.ones(im.shape[:2], dtype=np.uint8) * 2 #start all possible background
'''
#from docs:
0 GC_BGD defines an obvious background pixels.
1 GC_FGD defines an obvious foreground (object) pixel.
2 GC_PR_BGD defines a possible background pixel.
3 GC_PR_FGD defines a possible foreground pixel.
'''
#2 circles are "drawn" on mask. a smaller centered one I assume all pixels are definite foreground. a bigger circle, probably foreground.
r = 100
cv2.circle(mask, (int(width/2.), int(height/2.)), 2*r, 3, -3) #possible fg
#next 2 are greens...dark and bright to increase the number of fg pixels.
mask[(im[:,:,0] < 45) & (im[:,:,1] > 55) & (im[:,:,2] < 55)] = 1 #dark green
mask[(im[:,:,0] < 190) & (im[:,:,1] > 190) & (im[:,:,2] < 200)] = 1 #bright green
mask[(im[:,:,0] > 200) & (im[:,:,1] > 200) & (im[:,:,2] > 200) & (mask != 1)] = 0 #pretty white
cv2.circle(mask, (int(width/2.), int(height/2.)), r, 1, -3) #fg
#if you pass in an external mask derived from some other operation it is factored in here.
if external_mask is not None:
mask[external_mask == 1] = 1
bgdmodel = np.zeros((1,65), np.float64)
fgdmodel = np.zeros((1,65), np.float64)
cv2.grabCut(im, mask, None, bgdmodel, fgdmodel, 1, cv2.GC_INIT_WITH_MASK)
#show mask
plt.figure(figsize=(10,10))
plt.imshow(mask)
plt.show()
#mask image
mask2 = np.where((mask==1) + (mask==3), 255, 0).astype('uint8')
output = cv2.bitwise_and(im, im, mask=mask2)
plt.figure(figsize=(10,10))
plt.imshow(output)
plt.show()
mask_leaf('leaf1.jpg', external_mask=None)
mask_leaf('leaf2.jpg', external_mask=None)
Addressing the external mask. Here's an example of HDBSCAN clustering...I'm not going to go into the details...you can look up the docs and change it or use as-is.
import hdbscan
from collections import Counter
def hdbscan_mask(im_name):
im = cv2.imread(im_name)
im = cv2.blur(im, (5,5))
indices = np.dstack(np.indices(im.shape[:2]))
data = np.concatenate((indices, im), axis=-1)
data = data[:,2:]
data = imb.reshape(im.shape[0]*im.shape[1], 3)
clusterer = hdbscan.HDBSCAN(min_cluster_size=1000, min_samples=20)
clusterer.fit(data)
plt.figure(figsize=(10,10))
plt.imshow(clusterer.labels_.reshape(im.shape[0:2]))
plt.show()
height, width = im.shape[:2]
mask = np.ones(im.shape[:2], dtype=np.uint8) * 2 #start all possible background
cv2.circle(mask, (int(width/2.), int(height/2.)), 100, 1, -3) #possible fg
#grab cluster number for circle
vals_im = clusterer.labels_.reshape(im.shape[0:2])
vals = vals_im[mask == 1]
commonvals = []
cnts = Counter(vals)
for v, count in cnts.most_common(20):
#print '%i: %7d' % (v, count)
if v == -1:
continue
commonvals.append(v)
tst = np.in1d(vals_im, np.array(commonvals))
tst = tst.reshape(vals_im.shape)
hmask = tst.astype(np.uint8)
plt.figure(figsize=(10,10))
plt.imshow(hmask)
plt.show()
return hmask
hmask = hdbscan_mask('leaf1.jpg')
then to use the initial function with the new mask (output suppressed):
mask_leaf('leaf1.jpg', external_mask=hmask)
This was all made in a notebook from scratch so hopefully there's no errant variables that choke it up when running it somewhere else. (note: I did NOT swap BGR to RGB for plt display, sorry)

OpenCV Python best thresholding for document photos with varying light

Thank you for your time dear reader,
i´m trying to implement a document scanner in Python/OpenCV but im struggling with varying lights in the image.
Im hoping that maybe some kind soul can at least point me in the right direction because I have no concrete clue how I could improve it - if that is possible or known at all.
Im using otsus binarization for thresholding:
https://docs.opencv.org/3.4.0/d7/d4d/tutorial_py_thresholding.html
My results so far are pretty good:
But for difficult lighting with either two bright/dark areas or one bright - one dark area for instance (this is a crass example) it fails:
Playing around with Gimp curves sometimes gets me clear edges - maybe there is a best practice how to tackle this problem that I dont know of?
I played around with the code a lot but got no real progress by combining hierarchy / chain approx / approxpolydp epsilon methods etc.
My current code:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import cv2 as cv
import numpy as np
import sys
# zero at the end reads black white
img = cv.imread(sys.argv[1],0)
blur = cv.GaussianBlur(img,(5,5),0)
# find normalized_histogram, and its cumulative distribution function
hist = cv.calcHist([blur],[0],None,[256],[0,256])
hist_norm = hist.ravel()/hist.max()
Q = hist_norm.cumsum()
bins = np.arange(256)
fn_min = np.inf
thresh = -1
for i in range(1,256):
p1,p2 = np.hsplit(hist_norm,[i]) # probabilities
q1,q2 = Q[i],Q[255]-Q[i] # cum sum of classes
b1,b2 = np.hsplit(bins,[i]) # weights
# finding means and variances
m1,m2 = np.sum(p1*b1)/q1, np.sum(p2*b2)/q2
v1,v2 = np.sum(((b1-m1)**2)*p1)/q1,np.sum(((b2-m2)**2)*p2)/q2
# calculates the minimization function
fn = v1*q1 + v2*q2
if fn < fn_min:
fn_min = fn
thresh = i
# find otsu's threshold value with OpenCV function
ret, otsu = cv.threshold(blur,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
cv.imwrite('otsu.jpg',otsu)
_, contours, hierarchy = cv.findContours(otsu, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
def biggestRectangle(contours):
biggest = None
max_area = 0
indexReturn = -1
for index in range(len(contours)):
i = contours[index]
area = cv.contourArea(i)
if area > 100:
peri = cv.arcLength(i,True)
approx = cv.approxPolyDP(i,0.1*peri,True)
if area > max_area: #and len(approx)==4:
biggest = approx
max_area = area
indexReturn = index
return indexReturn
indexReturn = biggestRectangle(contours)
hull = cv.convexHull(contours[indexReturn])
orig = cv.imread(sys.argv[1])
cv.imwrite('hola.jpg',cv.drawContours(orig, [hull], 0, (0,255,0),3))
The biggest rectangle code I copied from here:
How to detect document from a picture in opencv?
All credits to monic! (accepted answer)
I think the best way to achieve this would be to use color Masks but in HSV so that it stays focused on the color and not on the brightness/contrast.
Keep in mind that OpenCV encodes HSV with those ranges:
H: 0 - 180
S: 0 - 255
V: 0 - 255
Here is how I would do it:
# Convert your image to HSV
imgHsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV);
# Define lower/upper color
lower = np.array([0, 0, 180])
upper = np.array([180, 20, 255])
# Check the region of the image actually with a color in the range defined below
# inRange returns a matrix in black and white
bw = cv2.inRange(imgHsv, lower, upper)

Doing the same as ImageMagick's "-level" in python / PIL?

I want to adjust the colour levels of an image in python. I can use any python library that can easily be installed on my Ubuntu desktop. I want to do the same as ImageMagick's -level ( http://www.imagemagick.org/www/command-line-options.html#level ). PIL (Python Image Library) doesn't seem to have it. I have been calling convert on the image and then reading in the file back again, but that seems wasteful. Is there a better / faster way?
If I understood correctly the -level option of ImageMagick, then the level_image function I provide should do what you want.
Two things to note:
the speed definitely can be improved
it currently only works with RGB images
the algorithm goes through the HSV colorspace, and affects only the V (brightness) component
The code:
import colorsys
class Level(object):
def __init__(self, minv, maxv, gamma):
self.minv= minv/255.0
self.maxv= maxv/255.0
self._interval= self.maxv - self.minv
self._invgamma= 1.0/gamma
def new_level(self, value):
if value <= self.minv: return 0.0
if value >= self.maxv: return 1.0
return ((value - self.minv)/self._interval)**self._invgamma
def convert_and_level(self, band_values):
h, s, v= colorsys.rgb_to_hsv(*(i/255.0 for i in band_values))
new_v= self.new_level(v)
return tuple(int(255*i)
for i
in colorsys.hsv_to_rgb(h, s, new_v))
def level_image(image, minv=0, maxv=255, gamma=1.0):
"""Level the brightness of image (a PIL.Image instance)
All values ≤ minv will become 0
All values ≥ maxv will become 255
gamma controls the curve for all values between minv and maxv"""
if image.mode != "RGB":
raise ValueError("this works with RGB images only")
new_image= image.copy()
leveller= Level(minv, maxv, gamma)
levelled_data= [
leveller.convert_and_level(data)
for data in image.getdata()]
new_image.putdata(levelled_data)
return new_image
If there is some way to do the RGB→HSV conversion (and vice versa) using PIL, then one can split into the H, S, V bands, use the .point method of the V band and convert back to RGB, speeding up the process by a lot; however, I haven't found such a way.
Why not use PythonMagick? It's a Python interface to Image Magick.
This is the code that I use. Levels are done, 1) on the brightness channel of the HSV image and, 2) according to the desired amount of blacks and whites pixels in the result.
The code can be modified to avoid to use pillow since openCV use numpy arrays as internal data. If doing so, be aware that openCV native colorspace is BGR. You will have to change the calls to cv.cvtColor() accordingly.
from PIL import Image
import numpy as np
import cv2 as cv
fileName = 'foo.JPG'
fileOut = 'bar.JPG'
imgPil = Image.open(fileName)
imgCV = np.asarray(imgPil, np.uint8)
hsv = cv.cvtColor(imgCV, cv.COLOR_RGB2HSV)
h,s,v = cv.split(hsv)
ceil = np.percentile(v,95) # 5% of pixels will be white
floor = np.percentile(v,5) # 5% of pixels will be black
a = 255/(ceil-floor)
b = floor*255/(floor-ceil)
v = np.maximum(0,np.minimum(255,v*a+b)).astype(np.uint8)
hsv = cv.merge((h,s,v))
rgb = cv.cvtColor(hsv, cv.COLOR_HSV2RGB)
imgPil = Image.fromarray(rgb)
imgPil.save(fileOut)
using code from this link here
# Auto leveling for image
def levels(data, all_same = 0, clip = 0):
if data.mode not in ['RGB', 'CMYK']:
return data
## get redistriputed histogram scalled smoothly
lut = _makelut(data, all_same, clip)
## update image points using histogram
data = data.point(lut)
return data
def _find_hi_lo(lut, clip):
min = None
max = None
for i in range(len(lut)):
if lut[i] > clip:
min = i
break
lut.reverse()
for i in range(len(lut)):
if lut[i] > clip:
max = 255 - i
break
return min, max
def _scale(channels, min, max):
lut = []
# hefny fix
ratio = float(max-min)
if ratio == 0:
ratio = 1
for i in range (channels):
for i in range(256):
value = int((i - min)*(255.0/ratio))
if value < 0:
value = 0
if value > 255:
value = 255
lut.append(value)
return lut
def _makelut(data, all_same, clip):
histogram = data.histogram()
lut = []
r, g, b, k = [], [], [], []
channels = len(histogram)/256
for i in range(256):
r.append(histogram[i])
g.append(histogram[256+i])
b.append(histogram[512+i])
if channels == 4:
for i in range(256):
k.append(histogram[768+i])
rmin, rmax = _find_hi_lo(r, clip)
gmin, gmax = _find_hi_lo(g, clip)
bmin, bmax = _find_hi_lo(b, clip)
if channels == 4:
kmin, kmax = _find_hi_lo(k)
else:
kmin, kmax = 128, 128
if all_same == 1:
min_max = [rmin, gmin, bmin, kmin, rmax, gmax, bmax, kmax]
min_max.sort()
lut = _scale(channels, min_max[0], min_max[-1])
else:
r_lut = _scale(1, rmin, rmax)
g_lut = _scale(1, gmin, gmax)
b_lut = _scale(1, bmin, bmax)
if channels == 4:
k_lut = _scale(1, kmin, kmax)
lut = []
for i in range (256):
lut.append(r_lut[i])
for i in range (256):
lut.append(g_lut[i])
for i in range (256):
lut.append(b_lut[i])
if channels == 4:
for i in range (256):
lut.append(k_lut[i])
return lut
from PIL import ImageEnhance , ImageDraw , Image
img = Image.open(file_path)
img2 = levels(img)

Categories

Resources