I have a question about python and opencv. I would like to change the part of the picture which is black to some other color (no matter what). After changing, I would like to get the pixel values, these 8 points marked with a red circle. How to do it?
https://imgur.com/2E1Wwqg
import cv2
import numpy as np
img = cv2.imread("image.jpg");
img[np.where((img == [0,0,0]).all(axis = 2))] = [50,150,166]
cv2.imwrite('output.png', img)
cv2.imshow("shapes", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
You can do that using OpenCV findContours() and minAreaRect() like this:
#!/usr/bin/env python3
import numpy as np
import cv2
# Load image
im = cv2.imread('start.png')
# Convert to grayscale
imgray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
# Theshold inverse so the black comes out white because findContours() looks for white objects
ret,thresh = cv2.threshold(imgray,16,255,cv2.THRESH_BINARY_INV)
cv2.imwrite('thresh.png',thresh)
# Remove noise specks
thresh = cv2.medianBlur(thresh,5)
cv2.imwrite('thresh-m.png',thresh)
# Find contours, draw on image and save
im2, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(im, contours, -1, (0,255,0), 3)
# Show user what we found
i=0
for cnt in contours:
rect = cv2.minAreaRect(cnt)
box = cv2.boxPoints(rect)
box = np.int0(box)
cv2.drawContours(im,[box],0,(255,0,0),2)
print('Contour:{}\n{}'.format(i,box))
i = i+1
cv2.imwrite('result.png',im)
The thresholded image looks like this:
And the result image look like this:
The program output is the 4 corner points of the 4 minimum rectangles each one containing one of your lines.
Contour:0
[[416 776]
[410 767]
[659 607]
[664 616]]
Contour:1
[[297 780]
[ 77 599]
[ 83 592]
[303 773]]
Contour:2
[[518 695]
[507 694]
[519 176]
[530 177]]
Contour:3
[[226 688]
[224 174]
[233 173]
[235 687]]
Related
I have an image that has an object which I cropped out of the image using Canny filter
import cv2
import numpy as np
from matplotlib import pyplot as plt
from PIL import Image
# load image
img = cv2.imread('dataset/example.png')
#.
#.
#.
#.
#.
# canny edge detection then find the non-zero min-max coords of canny
#.
#.
#.
#.
#.
# ROI
roi = img[y1:y2, x1:x2]
## crop ROI
cropped = np.array(img)
cropped[y1:y2, x1:x2] = (0, 0, 0)
bg = Image.fromarray(cropped)
This is the result I get:
Is there a way to select the region outside the crop area (black box)? Basically selecting the inverse of cropped[y1:y2, x1:x2] and then getting the average colour of that background?
You cannot crop non-4 vertex polygons - remember you are working with matrices. If you want to get the contours of the non-black region, you can first get a binary mask using a threshold value of 0. This will render everything above that value in white. Then get the contours of that binary mask, like this:
# importing cv2 & numpy
import numpy as np
import cv2
# image path
path = "C://opencvImages//"
fileName = "squareTest.png"
# Reading an image in default mode:
inputImage = cv2.imread(path + fileName)
# Grayscale conversion:
grayscaleImage = cv2.cvtColor(inputImage, cv2.COLOR_BGR2GRAY)
# Fixed Thresholding:
thresholdValue = 0
_, binaryImage = cv2.threshold(grayscaleImage, thresholdValue, 255, cv2.THRESH_BINARY)
This is the mask you obtain:
Now, simple get the contours:
# Find the contours on the mask image:
contours, hierarchy = cv2.findContours(binaryImage, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
# Draw the contours on the mask image:
cv2.drawContours(inputCopy, contours, -1, (255, 0, 0), 3)
This is the result:
Now, if you want the mean BGR(A) value of the non-black region, use the binary mask we got and pass it to cv2.mean as mask, like this:
means = cv2.mean(inputImage, mask=binaryImage)
You get:
(130.01283431634118, 223.66963836747732, 121.75817119126356, 0.0)
You can use cv2.mean with a mask:
# create a mask from coordinages
mask = cv2.rectangle(np.zeros(img.shape[:2],'uint8'), (y1,x1), (y2,x2), 255, -1)
# out
means = cv2.mean(img, mask=mask)
I want to use OCR (pytesseract) to recognize the text located in images like these:
I have thousands of these arrows. Until now the procedure is as follows: I first resize the image (for another process). Then I crop the image to get rid of the most part of the arrow. Next I draw a white rectangle as a frame to remove further noise but still have distance between text and image borders for better text recognition. I resize the image again to ensure a height of capital letters to ~30 px (https://groups.google.com/forum/#!msg/tesseract-ocr/Wdh_JJwnw94/24JHDYQbBQAJ). Finally I binarize the image with a threshold of 150.
Full code:
import cv2
image_file = '001.jpg'
# load the input image and grab the image dimensions
image = cv2.imread(image_file, cv2.IMREAD_GRAYSCALE)
(h_1, w_1) = image.shape[:2]
# resize the image and grab the new image dimensions
image = cv2.resize(image, (int(w_1*320/h_1), 320))
(h_1, w_1) = image.shape
# crop image
image_2 = image[70:h_1-70, 20:w_1-20]
# get image_2 height, width
(h_2, w_2) = image_2.shape
# draw white rectangle as a frame around the number -> remove noise
cv2.rectangle(image_2, (0, 0), (w_2, h_2), (255, 255, 255), 40)
# resize image, that capital letters are ~ 30 px in height
image_2 = cv2.resize(image_2, (int(w_2*50/h_2), 50))
# image binarization
ret, image_2 = cv2.threshold(image_2, 150, 255, cv2.THRESH_BINARY)
# save image to file
cv2.imwrite('processed_' + image_file, image_2)
# tesseract part can be commented out
import pytesseract
config_7 = ("-c tessedit_char_whitelist=0123456789AB --oem 1 --psm 7")
text = pytesseract.image_to_string(image_2, config=config_7)
print("OCR TEXT: " + "{}\n".format(text))
The problem is that the text located in the arrow is never centered. Sometimes I remove part of the text with the method described above (e.g. in image 50A).
Is there a method in image processing to get rid of the arrow in a more elegant way? For instance using contour detection and deletion? I am more interested in the OpenCV part than the tesseract part to recognize the text.
Any help is appreciated.
If you look at the pictures you will see that there is a white arrow in the image which is also the biggest contour (especially if you draw a black border on the image). If you make a blank mask and draw the arrow (biggest contour on the image) then erode it a little bit you can perform a per element bitwise conjunction of the actual image and eroded mask. If it is not clear look at the bottom code and comments and you will see that it is actually pretty simple.
# imports
import cv2
import numpy as np
img = cv2.imread("number.png") # read image
# you can resize the image here if you like - it should still work for both sizes
h, w = img.shape[:2] # get the actual images height and width
img = cv2.resize(img, (int(w*320/h), 320))
h, w = img.shape[:2]
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # transform to grayscale
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)[1] # perform OTSU threhold
cv2.rectangle(thresh, (0, 0), (w, h), (0, 0, 0), 2)
contours = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[0] # search for contours
max_cnt = max(contours, key=cv2.contourArea) # select biggest one
mask = np.zeros((h, w), dtype=np.uint8) # create a black mask
cv2.drawContours(mask, [max_cnt], -1, (255, 255, 255), -1) # draw biggest contour on the mask
kernel = np.ones((15, 15), dtype=np.uint8) # make a kernel with appropriate values - in both cases (resized and original) 15 is ok
erosion = cv2.erode(mask, kernel, iterations=1) # erode the mask with given kernel
reverse = cv2.bitwise_not(img.copy()) # reversed image of the actual image 0 becomes 255 and 255 becomes 0
img = cv2.bitwise_and(reverse, reverse, mask=erosion) # per-element bit-wise conjunction of the actual image and eroded mask (erosion)
img = cv2.bitwise_not(img) # revers the image again
# save image to file and display
cv2.imwrite("res.png", img)
cv2.imshow("img", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
Result:
You can try simple Python script:
import cv2
import numpy as np
img = cv2.imread('mmubS.png', cv2.IMREAD_GRAYSCALE)
thresh = cv2.threshold(img, 200, 255, cv2.THRESH_BINARY_INV )[1]
im_flood_fill = thresh.copy()
h, w = thresh.shape[:2]
im_flood_fill=cv2.rectangle(im_flood_fill, (0,0), (w-1,h-1), 255, 2)
mask = np.zeros((h + 2, w + 2), np.uint8)
cv2.floodFill(im_flood_fill, mask, (0, 0), 0)
im_flood_fill = cv2.bitwise_not(im_flood_fill)
cv2.imshow('clear text', im_flood_fill)
cv2.imwrite('text.png', im_flood_fill)
Result:
from PIL import Image
import pytesseract
from pdf2image import convert_from_path
import os
import pandas as pd
import cv2
import numpy as np
files = os.chdir("C:/Users/abhishek_kumar1/Desktop/New folder")
#print(os.getcwd())
pages = convert_from_path("d.pdf",190,single_file=True,
poppler_path='C:/Users/abhishek_kumar1/Downloads/poppler-0.68.0_x86/poppler-0.68.0/bin')
image_counter=1
for page in pages:
filename = "page_"+str(image_counter)+".jpg"
page.save(filename,'JPEG')
img = cv2.imread(filename)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv2.imwrite('grey.png',gray)
binary,thresh1 = cv2.threshold(gray, 0, 255,cv2.THRESH_OTSU|cv2.THRESH_BINARY_INV)
cv2.imwrite('Thresh1.png',thresh1)
rect_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (15, 3))
dilation = cv2.dilate(thresh1, rect_kernel, iterations = 6)
contours, hierarchy = cv2.findContours(dilation, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
im2 = img.copy()
ROI_number = 0
for cnt in contours[::-1]:
[x,y,w,h] = cv2.boundingRect(cnt)
ROI=im2[y:y+h, x:x+w]
#print(str(w),str(h))
#cv2.putText(im2, str(h), (x,y - 10 ), cv2.FONT_HERSHEY_SIMPLEX, 0.1, (255, 0, 0), 1)
#cv2.putText(im2, str(w), (x,y + 10 ), cv2.FONT_HERSHEY_SIMPLEX, 0.1, (0, 0, 255), 1)
cv2.imwrite('ROI_{}.jpg'.format(ROI_number),ROI)
cv2.rectangle(im2,(x,y),(x+w,y+h),(36,255,12),1)
ROI_number += 1
cv2.imwrite('contours1.png',im2)
How to find only this image from above code section section, is there any options to understand font type from image like bold, italic,something else
get trouble to find only the bold line part from all of images.
Please any body have a suggestion regarding this please help me out.
Alex Alex's answer did not work for me. Here is my alternative described in words.
The general idea is that we compare how many black pixels there are in comparison to the minimum possible pixels to still form characters. This provides us with a difference from the skeleton to normal text and skeleton to bold text. In this way, we can quite clearly separate normal text from the bold text.
Use OCR software to extract bounding boxes of individual words. Optional: Combine individual words into lines of words, for example by word_num in Pytesseract.
Convert the image to grayscale and invert the image colors
Perform Zhang-Suen thinning on the selected area of text on the image (opencv contribution: cv2.ximgproc.thinning)
Sum where there are white pixels in the thinned image, i.e. where values are equal to 255 (white pixels are letters)
Sum where there are white pixels in the inverted image
Finally compute the thickness (sum_inverted_pixels - sum_skeleton_pixels) / sum_skeleton_pixels (sometimes there will be zero division error, check when the sum of the skeleton is 0 and return 0 instead)
Normalize the thickness by minimum and maximum values
Apply a threshold for deciding when a word/line of text is bold, e.g. 0.6 or 0.7
See python code and result:
import cv2
import numpy as np
img = cv2.imread('C.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 160, 255, cv2.THRESH_BINARY)[1]
kernel = np.ones((5,5),np.uint8)
kernel2 = np.ones((3,3),np.uint8)
marker = cv2.dilate(thresh,kernel,iterations = 1)
mask=cv2.erode(thresh,kernel,iterations = 1)
while True:
tmp=marker.copy()
marker=cv2.erode(marker, kernel2)
marker=cv2.max(mask, marker)
difference = cv2.subtract(tmp, marker)
if cv2.countNonZero(difference) == 0:
break
marker_color = cv2.cvtColor(marker, cv2.COLOR_GRAY2BGR)
out=cv2.bitwise_or(img, marker_color)
cv2.imwrite('out.png', out)
cv2.imshow('result', out )
A white background image like below, some texts in black under (extra) red background, some texts are red. The position of texts (no matter with background or not) are not fixed.
I want to reproduce an image with only the texts.
A way I think of is to replace the red background into white, but inevitably the red texts will be gone too.
Here is what I have tried:
from PIL import Image
import numpy as np
orig_color = (255,0,0)
replacement_color = (255,255,255)
img = Image.open("C:\\TEM\\AB.png").convert('RGB')
data = np.array(img)
data[(data == orig_color).all(axis = -1)] = replacement_color
img2 = Image.fromarray(data, mode='RGB')
img2.show()
Result as below:
What's the best way to keep only all the texts of the picture? (ideal as below)
Thank you.
Here is my approach using only the red and green channels of the image (using OpenCV, see my comments in the code for the explanation):
import cv2
import imageio
import numpy as np
# extract red and green channel from the image
r, g = cv2.split(imageio.imread('https://i.stack.imgur.com/bMSzZ.png'))[:2]
imageio.imsave('r-channel.png', r)
imageio.imsave('g-channel.png', g)
# white image as canvas for drawing contours
canvas = np.ones(r.shape, np.uint8) * 255
# find contours in the inverted green channel
# change [0] to [1] when using OpenCV 3, in which contours are returned secondly
contours = cv2.findContours(255 - g, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)[0]
# filter out contours that are too large and have length 4 (rectangular)
contours = [
cnt for cnt in contours
if cv2.contourArea(cnt) <= 500 and len(cnt) == 4
]
# fill kept contours with black on the canvas
cv2.drawContours(canvas, contours, -1, 0, -1)
imageio.imsave('filtered-contours.png', canvas)
# combine kept contours with red channel using '&' to bring back the "AAA"
# use '|' with the green channel to remove contour edges around the "BBB"
result = canvas & r | g
imageio.imsave('result.png', result)
r-channel.png
g-channel.png
filtered-contours.png
result.png
Update
Here is a more generalisable solution based on another example image you provided in the chat:
import cv2
import numpy as np
img = cv2.imread('example.png')
result = np.ones(img.shape[:2], np.uint8) * 255
for channel in cv2.split(img):
canvas = np.ones(img.shape[:2], np.uint8) * 255
contours = cv2.findContours(255 - channel, cv2.RETR_LIST,
cv2.CHAIN_APPROX_SIMPLE)[0]
# size threshold may vary per image
contours = [cnt for cnt in contours if cv2.contourArea(cnt) <= 100]
cv2.drawContours(canvas, contours, -1, 0, -1)
result = result & (canvas | channel)
cv2.imwrite('result.png', result)
Here I no longer filter on contour length, as this causes problems when other characters are touching the rectangle. All channels of the image are used to make it compatible with different colours.
I would like to detect all the bright spots in this image (https://i.imgur.com/UnTWWHz.png)
The code I've tried is via thresholding, but it only detects the very bright ones. As you can see in the image below.
But some of the spots are out of focus which I need to also detect them.
Could you suggest a method? The picture below shows the blurred spots that I'd like to detect in yellow circles
I tried with the following code
import os
import cv2
import numpy as np
path="C:/Slides/Fluoroscent/E_03_G_O_subpics"
imgname="sub_2_4.png"
image = cv2.imread(os.path.join(path,imgname))
# constants
BINARY_THRESHOLD = 10
CONNECTIVITY = 4
DRAW_CIRCLE_RADIUS = 18
thr=50
# convert to gray
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# threshold the black/ non-black areas
_, thresh = cv2.threshold(gray_image, BINARY_THRESHOLD, thr, cv2.THRESH_BINARY)
# find connected components
components = cv2.connectedComponentsWithStats(thresh, CONNECTIVITY, cv2.CV_32S)
# draw circles around center of components
#see connectedComponentsWithStats function for attributes of components variable
centers = components[3]
for center in centers:
cv2.circle(image, (int(center[0]), int(center[1])), DRAW_CIRCLE_RADIUS, (0,0,255), thickness=1)
cv2.imwrite(os.path.join(path,"result_thresh_"+str(thr)+".png"), image)
cv2.imshow("result", image)
cv2.waitKey(0)
As mentioned in the comments you will get better results by changing the threshold values. I changed the values to 20 and 255 respectively and added erosion to get rid of some noise. You can play around with morphological transformations to get the exact desired result. Read more here .
Code:
import cv2
import numpy as np
kernel = np.ones((5,5),np.uint8)
CONNECTIVITY = 4
DRAW_CIRCLE_RADIUS = 18
img = cv2.imread('blobs.png')
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray_img, 20, 255, cv2.THRESH_BINARY)
erosion = cv2.erode(thresh,kernel,iterations = 1)
components = cv2.connectedComponentsWithStats(erosion, CONNECTIVITY, cv2.CV_32S)
centers = components[3]
for center in centers:
cv2.circle(img, (int(center[0]), int(center[1])), DRAW_CIRCLE_RADIUS, (0,0,255), thickness=1)
cv2.imshow('Original', img)
cv2.imshow('Thresh', thresh)
cv2.imshow('Erosion', erosion)
cv2.waitKey(0)
Results:
Threshold
Erosion
Original with circles