I need to get the information in the below barcode with the Python pyzbar library, but it does not recognize it. Should I make any improvement before using pyzbar?
this is the code:
from pyzbar.pyzbar import decode
import cv2
def barcodeReader(image):
gray_img = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
barcodes = decode(gray_img)
barcode = barcodeReader("My_image")
print (barcode)
Result: []
You could try to reconstruct the barcode by:
Inverse binarizing the image with cv2.threshold, such that you get white lines on black background.
Counting all non-zero pixels along the rows using np.count_nonzero.
Getting all indices, where the count exceeds a pre-defined threshold, let's say 100 here.
On a new, all white image, drawing black lines at the found indices.
Here's some code:
import cv2
import numpy as np
from skimage import io # Only needed for web grabbing images, use cv2.imread for local images
# Read image from web, convert to grayscale, and inverse binary threshold
image = cv2.cvtColor(io.imread('https://i.stack.imgur.com/D8Jk7.jpg'), cv2.COLOR_RGB2GRAY)
_, image_thr = cv2.threshold(image, 128, 255, cv2.THRESH_BINARY_INV)
# Count non-zero pixels along the rows; get indices, where count exceeds certain threshold (here: 100)
row_nz = np.count_nonzero(image_thr, axis=0)
idx = np.argwhere(row_nz > 100)
# Generate new image, draw lines at found indices
image_new = np.ones_like(image_thr) * 255
image_new[35:175, idx] = 0
cv2.imshow('image_thr', image_thr)
cv2.imshow('image_new', image_new)
cv2.waitKey(0)
cv2.destroyAllWindows()
The inverse binarized image:
The reconstructed image:
I'm not sure, if the result is a valid barcode. To improve the solution you could get rid of the numbers beforehand. Also, play around with the threshold.
Hope that helps!
You can follow below approach:
Using morphological operation detect vertical lines and stored the xmin ymin, xmax and ymax of vertical image.
sort all xmin values and grouped them based distance.
do same excercise ymin and ymax and grouped them.
consider smallest pixels values from larger group of xmin and ymin larger group respectively.
consider largest values from larger group of xmax and ymax larger grop respectively.
you will get exact xmin,ymin,xmax,ymax of barcode.
Related
I need to add space between two lines by using OpenCV or PIL.
If the lines vary "sufficiently" in their length, then the following approach might be useful:
Inverse binarize the image using cv2.threshold.
Dilate the image with a horizontal line kernel using cv2.dilate to emphasize the lines.
Sum all pixels row-wise using np.sum, and calculate the absolute differences between the rows using np.diff.
There will be "steps" in the differences between the rows, which resemble the step between the lines. Set up a threshold and find the proper indices using np.where.
Insert white lines in the original image before the found indices using np.insert. In the below example, the index was chosen manually. Work has to be done to properly automatize this: Exclude "steps" to "background", find "steps" between multiple lines.
Here comes a code snippet:
import cv2
from matplotlib import pyplot as plt
import numpy as np
from skimage import io # Only needed for web grabbing images, use cv2.imread for local images
# Read and binarize image
image = cv2.cvtColor(io.imread('https://i.stack.imgur.com/56g7s.jpg'), cv2.COLOR_RGB2GRAY)
_, image_bin = cv2.threshold(image, 128, 255, cv2.THRESH_BINARY_INV)
# Dilate rows by using horizontal line as kernel
kernel = np.ones((1, 51), np.uint8)
image_dil = cv2.dilate(image_bin, kernel)
# Sum pixels row-wise, and calculate absolute differences between the rows
row_sum = np.sum(image_dil / 255, 1, dtype=np.int32)
row_sum_diff = np.abs(np.diff(row_sum))
# Just for visualization: Summed row-wise pixels
plt.plot(row_sum)
plt.show()
# Find "steps" in the differences between the rows
step_thr = 100
step_idx = np.where(row_sum_diff > step_thr)[0]
# Insert n lines before desired index; simple hard-coding here, more work needs to be done for multiple lines
n_lines = 5
image_mod = np.insert(image, step_idx[1] + 1, 255 * np.ones((n_lines, image.shape[1]), np.uint8), axis=0)
# Result visualization
cv2.imshow('image', image)
cv2.imshow('image_dil', image_dil)
cv2.imshow('image_mod', image_mod)
cv2.waitKey(0)
cv2.destroyAllWindows()
The dilated, inverse binarized image:
The visualization of the "steps":
The final output with n = 5 inserted white lines:
As you can see, the result isn't perfect, but that's due to the original image. In the corresponding row, you have parts of the first and second line. So, a proper distinction between these two isn't possible. One might add a very small morphological closing to the output to get rid of these artifacts.
Hope that helps!
I want to analyse a specific part of an image, as an example I'd like to focus on the bottom right 200x200 section and count all the black pixels, so far I have:
im1 = Image.open(path)
rgb_im1 = im1.convert('RGB')
for pixel in rgb_im1.getdata():
Whilst you could do this with cropping and a pair of for loops, that is really slow and not ideal.
I would suggest you use Numpy as it is very commonly available, very powerful and very fast.
Here's a 400x300 black rectangle with a 1-pixel red border:
#!/usr/bin/env python3
import numpy as np
from PIL import Image
# Open the image and make into Numpy array
im = Image.open('image.png')
ni = np.array(im)
# Declare an ROI - Region of Interest as the bottom-right 200x200 pixels
# This is called "Numpy slicing" and is near-instantaneous https://www.tutorialspoint.com/numpy/numpy_indexing_and_slicing.htm
ROI = ni[-200:,-200:]
# Calculate total area of ROI and subtract non-zero pixels to get number of zero pixels
# Numpy.count_nonzero() is highly optimised and extremely fast
black = 200*200 - np.count_nonzero(ROI)
print(f'Black pixel total: {black}')
Sample Output
Black pixel total: 39601
Yes, you can make it shorter, for example:
h, w = 200,200
im = np.array(Image.open('image.png'))
black = h*w - np.count_nonzero(ni[-h:,-w:])
If you want to debug it, you can take the ROI and make it into a PIL Image which you can then display. So just use this line anywhere after you make the ROI:
# Display image to check
Image.fromarray(ROI).show()
You can try cropping the Image to the specific part that you want:-
img = Image.open(r"Image_location")
x,y = img.size
img = img.crop((x-200, y-200, x, y))
The above code takes an input image, and crops it to its bottom right 200x200 pixels. (make sure the image dimensions are more then 200x200, otherwise an error will occur)
Original Image:-
Image after Cropping:-
You can then use this cropped image, to count the number of black pixels, where it depends on your use case what you consider as a BLACK pixel (a discrete value like (0, 0, 0) or a range/threshold (0-15, 0-15, 0-15)).
P.S.:- The final Image will always have a dimension of 200x200 pixels.
from PIL import Image
img = Image.open("ImageName.jpg")
crop_area = (a,b,c,d)
cropped_img = img.crop(crop_area)
I can only ever find examples in C/C++ and they never seem to map well to the OpenCV API. I'm loading video frames (both from files and from a webcam) and want to reduce them to 16 color, but mapped to a 24-bit RGB color-space (this is what my output requires - a giant LED display).
I read the data like this:
ret, frame = self._vid.read()
image = cv2.cvtColor(frame, cv2.COLOR_RGB2BGRA)
I did find the below python example, but cannot figure out how to map that to the type of output data I need:
import numpy as np
import cv2
img = cv2.imread('home.jpg')
Z = img.reshape((-1,3))
# convert to np.float32
Z = np.float32(Z)
# define criteria, number of clusters(K) and apply kmeans()
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
K = 8
ret,label,center=cv2.kmeans(Z,K,None,criteria,10,cv2.KMEANS_RANDOM_CENTERS)
# Now convert back into uint8, and make original image
center = np.uint8(center)
res = center[label.flatten()]
res2 = res.reshape((img.shape))
cv2.imshow('res2',res2)
cv2.waitKey(0)
cv2.destroyAllWindows()
That obviously works for the OpenCV image viewer but trying to do the same errors on my output code since I need an RGB or RGBA format. My output works like this:
for y in range(self.height):
for x in range(self.width):
self._led.set(x,y,tuple(image[y,x][0:3]))
Each color is represented as an (r,g,b) tuple.
Any thoughts on how to make this work?
I think the following could be faster than kmeans, specially with a k = 16.
Convert the color image to gray
Contrast stretch this gray image to so that resulting image gray levels are between 0 and 255 (use normalize with NORM_MINMAX)
Calculate the histogram of this stretched gray image using 16 as the number of bins (calcHist)
Now you can modify these 16 values of the histogram. For example you can sort and assign ranks (say 0 to 15), or assign 16 uniformly distributed values between 0 and 255 (I think these could give you a consistent output for a video)
Backproject this histogram onto the stretched gray image (calcBackProject)
Apply a color-map to this backprojected image (you might want to scale the backprojected image befor applying a colormap using applyColorMap)
Tip for kmeans:
If you are using kmeans for video, you can use the cluster centers from the previous frame as the initial positions in kmeans for the current frame. That way, it'll take less time to converge, so kmeans in the subsequent frames will most probably run faster.
You can speed up your processing by applying the k-means on a downscaled version of your image. This will give you the cluster centroids. You can then quantify each pixel of the original image by picking the closest centroid.
I have the following test code in Python to read, threshold and display an image:
import cv2
import numpy as np
from matplotlib import pyplot as plt
# read image
img = cv2.imread('slice-309.png',0)
ret,thresh = cv2.threshold(img,0,230, cv2.THRESH_BINARY)
height, width = img.shape
print "height and width : ",height, width
size = img.size
print "size of the image in number of pixels", size
# plot the binary image
imgplot = plt.imshow(img, 'gray')
plt.show()
I would like to count the number of pixels within the image with a certain label, for instance black.
How can I do that ? I looked at tutorials of OpenCV but did not find any help :-(
Thanks!
For black images you get the total number of pixels (rows*cols) and then subtract it from the result you get from cv2.countNonZero(mat).
For other values, you can create a mask using cv2.inRange() to return a binary mask showing all the locations of the color/label/value you want and then use cv2.countNonZero to count how many of them there are.
UPDATE (Per Miki's comment):
When trying to find the count of elements with a particular value, Python allows you to skip the cv2.inRange() call and just do:
cv2.countNonZero(img == scalar_value)
import cv2
image = cv2.imread("pathtoimg", 0)
count = cv2.countNonZero(image)
print(count)
I am trying to determine the centroid of one specific object using OpenCV and Python.
I am using the following code, but it is taking too much time to calculate the centroid.
I need a faster approach for this -- should I change the resolution of the cameras in order to increase the computing speed?
This is my code:
meanI=[0]
meanJ=[0]
#taking infinite frames continuously to make a video
while(True):
ret, frame = capture.read()
rgb_image = cv2.cvtColor(frame , 0)
content_red = rgb_image[:,:,2] #red channel of image
content_green = rgb_image[:,:,1] #green channel of image
content_blue = rgb_image[:,:,0] #blue channel of image
r = rgb_image.shape[0] #gives the rows of the image matrix
c = rgb_image.shape[1] # gives the columns of the image matrix
d = rgb_image.shape[2] #gives the depth order of the image matrux
binary_image = np.zeros((r,c),np.float32)
for i in range (1,r): #thresholding the object as per requirements
for j in range (1,c):
if((content_red[i][j]>186) and (content_red[i][j]<230) and \
(content_green[i][j]>155) and (content_green[i][j]<165) and \
(content_blue[i][j]> 175) and (content_blue[i][j]< 195)):
binary_image[i][j] = 1
meanI.append(i)
meanJ.append(j)
cv2.imshow('frame1',binary_image)
cv2.waitKey()
cox = np.mean(meanI) #x-coordinate of centroid
coy = np.mean(meanJ) #y-coordinate of centroid
As you have discovered, nested loops in Python are very slow. It is best to avoid iterating over every pixel using nested loops. Fortunately, OpenCV has some built-in functions that do exactly what you are trying to achieve: inRange(), which creates a binary image of pixels which fall in between the specified bounds, and moments(), which you can use to calculate the centroid of a binary image. I strongly suggest reading over OpenCV's documentation to get a feel for what the library offers.
Combining these two functions gives the following code:
import numpy as np
import cv2
lower = np.array([175, 155, 186], np.uint8) # Note these ranges are BGR ordered
upper = np.array([195, 165, 230], np.uint8)
binary = cv2.inRange(im, lower, upper) # im is your BGR image
moments = cv2.moments(binary, True)
cx = moments['m10'] / moments['m00']
cy = moments['m01'] / moments['m00']
cx and cy are the x- and y-coordinates of the image centroid. This version is a whopping 3000 times faster than using nested loops.