opencv threshold problem for finding diff images - python

I have 2 images like below. I want to have differences two of them.
I tried some codes with threshold. But no threshold different. two images threshold image is all black. How can I make differences are with color white on thresh?
(the difference is just on top-left like a small dark)
before = cv2.imread('1.png')
after = cv2.imread('2.png')
threshb = cv2.threshold(before, 0, 255, cv2.THRESH_BINARY_INV)[1]
thresha = cv2.threshold(after, 0, 255, cv2.THRESH_BINARY_INV)[1]
cv.imshow("before",threshb)
cv.imshow("after",thresha)
Not: I used "structural_similarity" link here for finding differences but it founds a lot of differences :(
I don't need small pixel differences. I need differences like seen with human eyes.

The way to handle that is to do absdiff followed by some gain in Python/OpenCV. When you do the absdiff with these images, the difference is small so will not be much above black. So you have to increase the gain to see the difference. (Or you could set an appropriate threshold)
Input 1:
Input 2:
import cv2
import numpy as np
from skimage import exposure as exposure
# read image 1
img1 = cv2.imread('gray1.png', cv2.IMREAD_GRAYSCALE)
# read image 2
img2 = cv2.imread('gray2.png', cv2.IMREAD_GRAYSCALE)
# do absdiff
diff = cv2.absdiff(img1,img2)
# apply gain
result1 = cv2.multiply(diff, 5)
# or do threshold
result2 = cv2.threshold(diff, 10, 255, cv2.THRESH_BINARY)[1]
# save result
cv2.imwrite('gray1_gray2_diff1.png', result1)
cv2.imwrite('gray1_gray2_diff2.png', result2)
# display result
cv2.imshow('diff', diff)
cv2.imshow('result1', result1)
cv2.imshow('result2', result2)
cv2.waitKey(0)
cv2.destroyAllWindows()
Result 1 (gain):
Result 2 (threshold):

Related

Performing OCR of Seven Segment Display images

I'm working on performing OCR of energy meter displays: example 1 example 2 example 3
I tried to use tesseract-ocr with the letsgodigital trained data. But the performance is very poor.
I'm fairly new to the topic and this is what I've done:
import numpy as np
import cv2
import imutils
from skimage import exposure
from pytesseract import image_to_string
import PIL
def process_image(orig_image_arr):
gry_disp_arr = cv2.cvtColor(orig_image_arr, cv2.COLOR_BGR2GRAY)
gry_disp_arr = exposure.rescale_intensity(gry_disp_arr, out_range= (0,255))
#thresholding
ret, thresh = cv2.threshold(gry_disp_arr,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
return thresh
def ocr_image(orig_image_arr):
otsu_thresh_image = process_image(orig_image_arr)
cv2_imshow(otsu_thresh_image)
return image_to_string(otsu_thresh_image, lang="letsgodigital", config="--psm 8 -c tessedit_char_whitelist=.0123456789")
img1 = cv2.imread('test2.jpg')
cnv = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
text = ocr_image(cnv)
This gives very poor results with the example images. I have a couple of questions:
How can I identify the four corners of the display? (Edge detection doesn’t seem to work very well)
Is there any futher preprocessing that I can do to improve the performance?
Thanks for any help.
Notice how your power meters either use blue or green LEDs to light up the display; I suggest you use this color display to your advantage. What I'd do is select only one RGB channel based on the LED color. Then I can threshold it based on some algorithm or assumption. After that, you can do the downstream steps of cropping / resizing / transformation / OCR etc.
For example, using your example image 1, look at its histogram here.
Notice how there is a small peak of green to the right of the 150 mark.
I take advantage of this, and set anything below 150 to zero. My assumption being that the green peak is the bright green LED in the image.
img = cv2.imread('example_1.jpg', 1)
# Get only green channel
img_g = img[:,:,1]
# Set threshold for green value, anything less than 150 becomes zero
img_g[img_g < 150] = 0
This is what I get.
This should be much easier for downstream OCR now.
# You should also set anything >= 150 to max value as well, but I didn't in this example
img_g[img_g >= 150] = 255
The above steps should replace this step
_ret, thresh = cv2.threshold(img_g, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
Here's the output of this step.

Opencv, Python - How to remove the gray pixels around the date text

I am trying to remove the grayish “noise” surrounding the dates using Python/OpenCV to help the OCR (Optical Character Recognition) to recognize the dates.
The original image looks like this: https://static.mothership.sg/1/2017/03/10-Feb-MC-1.jpg
The python script I tried looked as below. However, I have other similar images in which the contrast or lighting coditions varies.
import cv2
import numpy as np
img = cv2.imread("mc.jpeg")
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
alpha = 3.5
beta = -2
new = alpha * img + beta
new = np.clip(new, 0, 255).astype(np.uint8)
cv2.imwrite("cleaned.png", new)
I also tried Thresholding and/or adaptiveThresholding and some time, I was able to separate the dates from the grayish background. Sometimes it was very challenging. I wonder is there an automatic way to determine the threshold value ?
Below are example of what I hope to achieve.
Blurry Image:
Otsu's Binarization automatically calculates a threshold value from an image histogram.
# Otsu's thresholding after Gaussian filtering
blur = cv2.GaussianBlur(img,(5,5),0)
ret,Otsu = cv2.threshold(blur,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
cv2.imwrite("Otsu's_thresholding", Otsu)
see this link
You can try to build a model of the background and then weight each input pixel by that model. The output gain should be relatively constant during most of the image. These are the steps for this method:
Apply a soft median blur filter to get rid of small noise
Get the model of the background via local maximum. Apply a very strong close operation, with a big structuring element (I’m using a rectangular kernel of size 15)
Perform gain adjustment by dividing 255 between each local maximum pixel. Weight this value with each input image pixel.
You should get a nice image where the background illumination is pretty much normalized, threshold this image to get a binary mask of the text
This is the code:
import numpy as np
import cv2
# image path
path = "C:/opencvImages/sheet01.jpg"
# Read an image in default mode:
inputImage = cv2.imread(path)
# Remove small noise via median:
filterSize = 5
imageMedian = cv2.medianBlur(inputImage, filterSize)
# Get local maximum:
kernelSize = 15
maxKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (kernelSize, kernelSize))
localMax = cv2.morphologyEx(imageMedian, cv2.MORPH_CLOSE, maxKernel, None, None, 1, cv2.BORDER_REFLECT101)
# Adjust image gain:
height, width, depth = localMax.shape
# Create output Mat:
outputImage = np.zeros(shape=[height, width, depth], dtype=np.uint8)
for i in range(0, height):
for j in range(0, width):
# Get current BGR pixels:
v1 = inputImage[i, j]
v2 = localMax[i, j]
# Gain adjust:
tempArray = []
for c in range(0, 3):
currentPixel = v2[c]
if currentPixel != 0:
gain = 255 / v2[c]
gain = v1[c] * gain
else:
gain = 0
# Gain set and clamp:
tempArray.append(np.clip(gain, 0, 255))
# Set pixel vec to out image:
outputImage[i, j] = tempArray
# Convert RGB to grayscale:
grayscaleImage = cv2.cvtColor(outputImage, cv2.COLOR_BGR2GRAY)
# Threshold:
threshValue = 110
_, binaryImage = cv2.threshold(grayscaleImage, threshValue, 255, cv2.THRESH_BINARY)
# Write image:
imageFilename = "C:/opencvImages/binaryMask2.png"
cv2.imwrite(imageFilename, binaryImage)
I get the following results testing the complete image:
And the cropped text:
Please note that the gain adjustment operations are not vectorized. The script is slow, mainly because I'm starting with Python and don’t know the proper Numpy syntax to speed-up this operation. I've been using C++ for a long time, so feel free to further improve the code.
Edit:
Please, be aware that your result can only be as good as the quality of your input. See your input and ask yourself "Is this a good input for an automated process?" (Automated processes are usually not very smart). The second picture you posted is very low quality. Not only is blurry but also is low res and has compression artifacts. All these factors will hinder automated processing.
With that said, here's an improvement you can include in the original:
Try to normalize brightness-contrast on the grayscale output:
grayscaleImage = np.uint8(cv2.normalize(grayscaleImage, grayscaleImage, 0, 255, cv2.NORM_MINMAX))
Your grayscale image goes from this:
to this:
A little bit darker and improved on contrast. Let's try to compute the optimal threshold value automatically via Otsu thresholding:
threshValue, binaryImage = cv2.threshold(grayscaleImage, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
It gets you this:
However, we can adjust the result if we add bias to Otsu's threshold, like this:
threshValue, binaryImage = cv2.threshold(grayscaleImage, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
bias = 0.9
threshValue = bias * threshValue
_, binaryImage = cv2.threshold(grayscaleImage, threshValue, 255, cv2.THRESH_BINARY)
That's the best quality you can get with these images using this method.
If you find these suggestions and tips useful, please, at least up-vote my answer.

Image Processing Cut an area that is different from a pattern

Hi,
I want to get difference between the first image and the second,
I want to cut the numbers from the image.
I am getting the difference between the pixels but the result is:
But what I want is:
Is it possible cut the image like this?
Here is what I did:
import cv2
import numpy as np
from PIL import Image
import pytesseract
import os
import sys
img = Image.open("recherche.png").convert("RGBA")
pattern = Image.open("pattern.png").convert("RGBA")
pixels = img.load()
pixelsPattern = pattern.load()
new = Image.open("new.png").convert("RGBA")
pixelNew = new.load()
for i in range(img.size[0]):
for j in range(img.size[1]):
if(pixels[i,j] != pixelsPattern[i,j]):
pixelNew[i,j] = pixels[i,j]
I am directly getting the bit difference, but It is not giving me what I want, I tried medianBlur and similar things to do it like 4th image but I am not able to make it sharp like in the 4th image. (I created 4th image manually with paint.)
It's a challenging problem, because the pattern was deliberately designed to make it difficult to solve by software.
I suggest the following steps:
Convert img and pattern to binary images (gray levels are not part of the number).
Compute absolute difference of img and pattern.
Apply closing morphological operation for closing small gaps.
Here is the code:
import cv2
import numpy as np
# Read image and pattern as Grayscale images (output of cv2.imread is numpty array).
img = cv2.imread("recherche.png", cv2.IMREAD_GRAYSCALE)
pattern = cv2.imread("pattern.png", cv2.IMREAD_GRAYSCALE)
# Convert img and pattern to binary images (all values above 1 goes to 255)
_, img = cv2.threshold(img, 1, 255, cv2.THRESH_BINARY)
_, pattern = cv2.threshold(pattern, 1, 255, cv2.THRESH_BINARY)
# Compute absolute difference of img and pattern (result is 0 where equal and 255 when not equal)
dif = cv2.absdiff(img, pattern)
# Apply closing morphological operation
dif = cv2.morphologyEx(dif, cv2.MORPH_CLOSE, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5)));
dif = 255 - dif # Inverse polarity
# Display result
cv2.imshow('dif', dif)
cv2.waitKey(0)
cv2.destroyAllWindows()
Result:
As you can see, solution is not perfect, but getting a perfect result is very challenging...

Convert Gray background to white background without disturbing the original image

I have cropped few images which have gray back ground and need to convert them to white back ground to compare with Reference images.
The following code I implemented to convert:
import cv2
im_gray = cv2.imread('gray_bg.png', cv2.IMREAD_GRAYSCALE)
(thresh, im_bw) = cv2.threshold(im_gray, 255, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
cv2.imwrite('white_bg.png', im_bw)
input:
output:
expected output:
If you observe, my output image has some noise in edges of the original image (I hope I am not wrong in saying). Because of this, while comparing my output with Reference images, I am not getting the desired output. Can someone suggest me how to do it?
Here is the program We wrote to compare two images:
SourceImagePath = r'white_bg.png'
TemplateImagePath = r'ex_white_bg.png'
#def IconValidation(self,SourceImagePath,TemplateImagePath):
sourceImg=cv.imread(SourceImagePath)
templateImg=cv.imread(TemplateImagePath)
_,tempwidth,tempheight=templateImg.shape[::-1]
srcheight = np.size(sourceImg, 0)
srcwidth = np.size(sourceImg, 1)
if(srcwidth < tempwidth) and (srcheight < tempheight):
print("comparison")
resultImg = cv.matchTemplate(sourceImg,templateImg,cv.TM_CCOEFF_NORMED)
matchVal = resultImg[0][0]
threshold=0.95
if(matchVal>threshold):
print("passed")
else:
print("failed")
What you see is aliasing, not noise. It appears because of hard thresholding.
You input image does have a little noise, which you see by enlarging (possibly due to lossy compression at some stage), but it is not aliased.
You can turn the gray background to white while keeping the black by applying a gain of 1.4 (the gray level is around 180). This will avoid the introduction of aliasing.
Avoiding the change of color encoding achive the best results. Here is the code:
im_gray = cv2.imread('gray_bg.png', cv2.IMREAD_UNCHANGED)
b, g, r = cv2.split(im_gray)
t = [None] * 3
u = [None] * 3
for i, im in enumerate([b, g, r]):
t[i], u[i] = cv2.threshold(im, 255, 255, cv2.THRESH_BINARY + cv2.THRESH_TRIANGLE)
dst = cv2.merge((*u,))
cv2.imwrite('white_bg.png', dst)
By comparing it with the original it gives 99.99% equality.
Than if you really need it you can convert the image to grayscale encoding with cv2.cvtColor(src, cv2.COLOR_BGR2GRAY).
Result vs Wanted:

I want to increase brightness and contrast of images in dynamic way so that the program is applicable for any new images

I have few images where I need to increase or decrease the contrast and brightness of the image in a dynamic way so that it is visible clearly. And the program needs to be dynamic so that it even works for new images also. I also want character should be dark.
I was able to increase brightness and contrast but it is not working properly for each image.
import cv2
import numpy as np
img = cv2.imread('D:\Bright.png')
image = cv2.GaussianBlur(img, (5, 5), 0)
#image = cv2.threshold(image, 0, 255, cv2.THRESH_BINARY)[1]
#kernel = np.ones((2,1),np.uint8)
#dilation = cv2.dilate(img,kernel)
cv2.imshow('test', image)
cv2.waitKey(0)
cv2.destroyAllWindows()
imghsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
imghsv[:,:,2] = [[max(pixel - 25, 0) if pixel < 190 else min(pixel + 25, 255) for pixel in row] for row in imghsv[:,:,2]]
cv2.imshow('contrast', cv2.cvtColor(imghsv, cv2.COLOR_HSV2BGR))
#cv2.imwrite('D:\\112.png',cv2.cvtColor(imghsv, cv2.COLOR_HSV2BGR))
cv2.waitKey(0)
cv2.destroyAllWindows()
#raw_input()
I want a program which works fine for every image and words are a little darker so that they are easily visible.
As Tilarion suggested, you could try "Auto Brightness And Contrast" to see if it works well. The theory behind this is explained well here in the solution section. The solution is in C++. I've written a version of it in python which you can directly use, works only on 1 channel at a time for colour images:
def auto_brightandcontrast(input_img, channel, clip_percent=1):
histSize=180
alpha=0
beta=0
minGray=0
maxGray=0
accumulator=[]
if(clip_percent==0):
#min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(hist)
return input_img
else:
hist = cv2.calcHist([input_img],[channel],None,[256],[0, 256])
accumulator.insert(0,hist[0])
for i in range(1,histSize):
accumulator.insert(i,accumulator[i-1]+hist[i])
maxx=accumulator[histSize-1]
minGray=0
clip_percent=clip_percent*(maxx/100.0)
clip_percent=clip_percent/2.0
while(accumulator[minGray]<clip_percent[0]):
minGray=minGray+1
maxGray=histSize-1
while(accumulator[maxGray]>=(maxx-clip_percent[0])):
maxGray=maxGray-1
inputRange=maxGray-minGray
alpha=(histSize-1)/inputRange
beta=-minGray*alpha
out_img=input_img.copy()
cv2.convertScaleAbs(input_img,out_img,alpha,beta)
return out_img
It is a very few lines of code to do it in Python Wand (which is based upon ImageMagick). Here is a script.
#!/bin/python3.7
from wand.image import Image
with Image(filename='task4.jpg') as img:
img.contrast_stretch(black_point=0.02, white_point=0.99)
img.save(filename='task4_stretch2_99.jpg')
Input:
Result:
Increase the black point value to make the text darker and/or decrease the white point value to make the lighter parts brighter.
Thanks to Eric McConville (the Wand developer) for correcting my arguments to make the code work.

Categories