detecting tile squares in 2048 with cv2
I am learning cv2 and trying to detect the on-board number tiles (dynamically) from the 2048 game, and outlining them in green.
Firstly I am having trouble detecting the ones in the more orange to red range (8, 16, 32, 64) and if I lower the threshold the whole board seems to be included. Sometimes, smaller parts (such as the round part of a 6 are included) or an entire tile is ignored. How would I go about detecting the tiles on a board like this?
Here is the code I have so far:
import cv2
import mss
import time
import numpy as np
# Static screenshot for board
monitor = {"top": 135, "left": 425, "width": 500, "height": 500}
sct = mss.mss()
# Run for a maximum of 150s or until 'q' is pressed
last_time = time.time()
while time.time() - last_time < 150:
img = np.asarray(sct.grab(monitor))
resized_img = cv2.resize(img, (100, 100))
gray_img = cv2.cvtColor(resized_img, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray_img, 200, 255, 0)[1]
contours = cv2.findContours(thresh, 1, 2)[1]
for cnt in contours:
if len(cnt) == 4:
cv2.drawContours(resized_img, [cnt], 0, (0, 255, 0), 2)
cv2.imshow("2048", resized_img)
if cv2.waitKey(25) & 0xFF == ord("q"):
break
cv2.destroyAllWindows()
sample detection:
EDIT: added sample input as requested
sample input:
sample output:
Thankyou for any responses, even if they are a point in the right direction
python opencv cv2
|
show 1 more comment
I am learning cv2 and trying to detect the on-board number tiles (dynamically) from the 2048 game, and outlining them in green.
Firstly I am having trouble detecting the ones in the more orange to red range (8, 16, 32, 64) and if I lower the threshold the whole board seems to be included. Sometimes, smaller parts (such as the round part of a 6 are included) or an entire tile is ignored. How would I go about detecting the tiles on a board like this?
Here is the code I have so far:
import cv2
import mss
import time
import numpy as np
# Static screenshot for board
monitor = {"top": 135, "left": 425, "width": 500, "height": 500}
sct = mss.mss()
# Run for a maximum of 150s or until 'q' is pressed
last_time = time.time()
while time.time() - last_time < 150:
img = np.asarray(sct.grab(monitor))
resized_img = cv2.resize(img, (100, 100))
gray_img = cv2.cvtColor(resized_img, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray_img, 200, 255, 0)[1]
contours = cv2.findContours(thresh, 1, 2)[1]
for cnt in contours:
if len(cnt) == 4:
cv2.drawContours(resized_img, [cnt], 0, (0, 255, 0), 2)
cv2.imshow("2048", resized_img)
if cv2.waitKey(25) & 0xFF == ord("q"):
break
cv2.destroyAllWindows()
sample detection:
EDIT: added sample input as requested
sample input:
sample output:
Thankyou for any responses, even if they are a point in the right direction
python opencv cv2
It looks like you are having boxes with defined colors at fixed positions. So, there is not much sense in detecting their position and they should be rather easy to identify if you filter for the right color(s).
– Klaus D.
Nov 23 '18 at 2:33
@KlausD. this was more of an exercise to determine if I could dynamically work out the position of the boxes as practice using cv2 - so if the boxes were layed out randomly, what the method would be to detect them. How would I go about filtering the colours if I were to do it using that method?
– NightShade
Nov 23 '18 at 3:14
Can you please attach full size sample input image? Do you want to detect the boxes only or do you also want to segment the boxes on the basis of color?
– ZdaR
Nov 23 '18 at 4:13
You need to filter the image more precisely... Try using some edge detection open CV algo with dilating and eroding. To prevent whole box to be selected or only smaller parts ( round of 6 ) to be selected as contour just check area of every contour.... Opencv has facility to do this. Once you have area of each contour then drop the contour if it is very large or very small. This will filter all unnecessary contours and you will get the desired contours....
– Devashish Prasad
Nov 23 '18 at 4:14
@ZdaR I have edited the post and provided a large sample input image and sample output made in paint. I only want to detect the individual game tiles, with no concern for the colour or value within them
– NightShade
Nov 23 '18 at 4:38
|
show 1 more comment
I am learning cv2 and trying to detect the on-board number tiles (dynamically) from the 2048 game, and outlining them in green.
Firstly I am having trouble detecting the ones in the more orange to red range (8, 16, 32, 64) and if I lower the threshold the whole board seems to be included. Sometimes, smaller parts (such as the round part of a 6 are included) or an entire tile is ignored. How would I go about detecting the tiles on a board like this?
Here is the code I have so far:
import cv2
import mss
import time
import numpy as np
# Static screenshot for board
monitor = {"top": 135, "left": 425, "width": 500, "height": 500}
sct = mss.mss()
# Run for a maximum of 150s or until 'q' is pressed
last_time = time.time()
while time.time() - last_time < 150:
img = np.asarray(sct.grab(monitor))
resized_img = cv2.resize(img, (100, 100))
gray_img = cv2.cvtColor(resized_img, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray_img, 200, 255, 0)[1]
contours = cv2.findContours(thresh, 1, 2)[1]
for cnt in contours:
if len(cnt) == 4:
cv2.drawContours(resized_img, [cnt], 0, (0, 255, 0), 2)
cv2.imshow("2048", resized_img)
if cv2.waitKey(25) & 0xFF == ord("q"):
break
cv2.destroyAllWindows()
sample detection:
EDIT: added sample input as requested
sample input:
sample output:
Thankyou for any responses, even if they are a point in the right direction
python opencv cv2
I am learning cv2 and trying to detect the on-board number tiles (dynamically) from the 2048 game, and outlining them in green.
Firstly I am having trouble detecting the ones in the more orange to red range (8, 16, 32, 64) and if I lower the threshold the whole board seems to be included. Sometimes, smaller parts (such as the round part of a 6 are included) or an entire tile is ignored. How would I go about detecting the tiles on a board like this?
Here is the code I have so far:
import cv2
import mss
import time
import numpy as np
# Static screenshot for board
monitor = {"top": 135, "left": 425, "width": 500, "height": 500}
sct = mss.mss()
# Run for a maximum of 150s or until 'q' is pressed
last_time = time.time()
while time.time() - last_time < 150:
img = np.asarray(sct.grab(monitor))
resized_img = cv2.resize(img, (100, 100))
gray_img = cv2.cvtColor(resized_img, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray_img, 200, 255, 0)[1]
contours = cv2.findContours(thresh, 1, 2)[1]
for cnt in contours:
if len(cnt) == 4:
cv2.drawContours(resized_img, [cnt], 0, (0, 255, 0), 2)
cv2.imshow("2048", resized_img)
if cv2.waitKey(25) & 0xFF == ord("q"):
break
cv2.destroyAllWindows()
sample detection:
EDIT: added sample input as requested
sample input:
sample output:
Thankyou for any responses, even if they are a point in the right direction
python opencv cv2
python opencv cv2
edited Nov 23 '18 at 4:36
NightShade
asked Nov 23 '18 at 2:17
NightShadeNightShade
11410
11410
It looks like you are having boxes with defined colors at fixed positions. So, there is not much sense in detecting their position and they should be rather easy to identify if you filter for the right color(s).
– Klaus D.
Nov 23 '18 at 2:33
@KlausD. this was more of an exercise to determine if I could dynamically work out the position of the boxes as practice using cv2 - so if the boxes were layed out randomly, what the method would be to detect them. How would I go about filtering the colours if I were to do it using that method?
– NightShade
Nov 23 '18 at 3:14
Can you please attach full size sample input image? Do you want to detect the boxes only or do you also want to segment the boxes on the basis of color?
– ZdaR
Nov 23 '18 at 4:13
You need to filter the image more precisely... Try using some edge detection open CV algo with dilating and eroding. To prevent whole box to be selected or only smaller parts ( round of 6 ) to be selected as contour just check area of every contour.... Opencv has facility to do this. Once you have area of each contour then drop the contour if it is very large or very small. This will filter all unnecessary contours and you will get the desired contours....
– Devashish Prasad
Nov 23 '18 at 4:14
@ZdaR I have edited the post and provided a large sample input image and sample output made in paint. I only want to detect the individual game tiles, with no concern for the colour or value within them
– NightShade
Nov 23 '18 at 4:38
|
show 1 more comment
It looks like you are having boxes with defined colors at fixed positions. So, there is not much sense in detecting their position and they should be rather easy to identify if you filter for the right color(s).
– Klaus D.
Nov 23 '18 at 2:33
@KlausD. this was more of an exercise to determine if I could dynamically work out the position of the boxes as practice using cv2 - so if the boxes were layed out randomly, what the method would be to detect them. How would I go about filtering the colours if I were to do it using that method?
– NightShade
Nov 23 '18 at 3:14
Can you please attach full size sample input image? Do you want to detect the boxes only or do you also want to segment the boxes on the basis of color?
– ZdaR
Nov 23 '18 at 4:13
You need to filter the image more precisely... Try using some edge detection open CV algo with dilating and eroding. To prevent whole box to be selected or only smaller parts ( round of 6 ) to be selected as contour just check area of every contour.... Opencv has facility to do this. Once you have area of each contour then drop the contour if it is very large or very small. This will filter all unnecessary contours and you will get the desired contours....
– Devashish Prasad
Nov 23 '18 at 4:14
@ZdaR I have edited the post and provided a large sample input image and sample output made in paint. I only want to detect the individual game tiles, with no concern for the colour or value within them
– NightShade
Nov 23 '18 at 4:38
It looks like you are having boxes with defined colors at fixed positions. So, there is not much sense in detecting their position and they should be rather easy to identify if you filter for the right color(s).
– Klaus D.
Nov 23 '18 at 2:33
It looks like you are having boxes with defined colors at fixed positions. So, there is not much sense in detecting their position and they should be rather easy to identify if you filter for the right color(s).
– Klaus D.
Nov 23 '18 at 2:33
@KlausD. this was more of an exercise to determine if I could dynamically work out the position of the boxes as practice using cv2 - so if the boxes were layed out randomly, what the method would be to detect them. How would I go about filtering the colours if I were to do it using that method?
– NightShade
Nov 23 '18 at 3:14
@KlausD. this was more of an exercise to determine if I could dynamically work out the position of the boxes as practice using cv2 - so if the boxes were layed out randomly, what the method would be to detect them. How would I go about filtering the colours if I were to do it using that method?
– NightShade
Nov 23 '18 at 3:14
Can you please attach full size sample input image? Do you want to detect the boxes only or do you also want to segment the boxes on the basis of color?
– ZdaR
Nov 23 '18 at 4:13
Can you please attach full size sample input image? Do you want to detect the boxes only or do you also want to segment the boxes on the basis of color?
– ZdaR
Nov 23 '18 at 4:13
You need to filter the image more precisely... Try using some edge detection open CV algo with dilating and eroding. To prevent whole box to be selected or only smaller parts ( round of 6 ) to be selected as contour just check area of every contour.... Opencv has facility to do this. Once you have area of each contour then drop the contour if it is very large or very small. This will filter all unnecessary contours and you will get the desired contours....
– Devashish Prasad
Nov 23 '18 at 4:14
You need to filter the image more precisely... Try using some edge detection open CV algo with dilating and eroding. To prevent whole box to be selected or only smaller parts ( round of 6 ) to be selected as contour just check area of every contour.... Opencv has facility to do this. Once you have area of each contour then drop the contour if it is very large or very small. This will filter all unnecessary contours and you will get the desired contours....
– Devashish Prasad
Nov 23 '18 at 4:14
@ZdaR I have edited the post and provided a large sample input image and sample output made in paint. I only want to detect the individual game tiles, with no concern for the colour or value within them
– NightShade
Nov 23 '18 at 4:38
@ZdaR I have edited the post and provided a large sample input image and sample output made in paint. I only want to detect the individual game tiles, with no concern for the colour or value within them
– NightShade
Nov 23 '18 at 4:38
|
show 1 more comment
2 Answers
2
active
oldest
votes
Rather than doing thresholding with the greyscale image, you can do thresholding on the color image with cv2.inRange
. You can set the upper and lower bounds of allowed colors to include the numbered tiles but exclude the empty tiles and edges.
Also, I assume the step where you are checking if len(cnt) == 4:
is to return only the square contours. However, resizing can result in contours that aren't exactly square for the tiles, and won't pass this check. Instead, you can get the outer contours of the tiles by changing the second input of findContours
to 0 (contours = cv2.findContours(thresh, 0, 2)[1]
) which sets the retrieval mode to cv2.RETR_EXTERNAL
.
Here is the code with changes made, and appropriate upper and lower color bounds for the example image you gave.
import cv2
import mss
import time
import numpy as np
# Static screenshot for board
monitor = {"top": 135, "left": 425, "width": 500, "height": 500}
sct = mss.mss()
# inRange bounds
lower_bound = (0, 0, 210)
upper_bound = (230, 240, 250)
# Run for a maximum of 150s or until 'q' is pressed
last_time = time.time()
while time.time() - last_time < 150:
img = np.asarray(sct.grab(monitor))[:,:,:3]
resized_img = cv2.resize(img, (100, 100))
mask = cv2.inRange(resized_img, lower_bound, upper_bound)
contours = cv2.findContours(mask, 0, 2)[1]
for cnt in contours:
cv2.drawContours(resized_img, [cnt], 0, (0, 255, 0), 2)
cv2.imshow("2048", resized_img)
if cv2.waitKey(25) & 0xFF == ord("q"):
break
cv2.destroyAllWindows()
Here is the output image created:
Edit: Here is the code for using the example image directly:
import cv2
import numpy as np
img = cv2.imread('2048.jpg')
resized_img = cv2.resize(img, (100, 100))
lower_bound = (0,0,210)
upper_bound = (230,240,250)
mask = cv2.inRange(resized_img, lower_bound, upper_bound)
contours = cv2.findContours(mask, 0, 2)[1]
for cnt in contours:
cv2.drawContours(resized_img, [cnt], 0, (0, 255, 0), 2)
cv2.imshow('2048', resized_img)
cv2.waitKey(0)
This does seem like the short most simple solution and I love it :) however when I try to run it myself I get an error: mask = cv2.inRange(resized_img, lower_bound, upper_bound) cv2.error: OpenCV(3.4.3) C:projectsopencv-pythonopencvmodulescoresrcarithm.cpp:1778: error: (-209:Sizes of input arguments do not match) The lower boundary is neither an array of the same size and same type as src, nor a scalar in function 'cv::inRange'
– NightShade
Nov 23 '18 at 5:40
My first guess is theresized_img
is a greyscale image, not the color image. Can you check if that's the case? It needs to be a 3-channel image.
– A Kruger
Nov 23 '18 at 5:45
it does not appear to be, I have tried using the code as you have submitted it above
– NightShade
Nov 23 '18 at 5:47
maybe it has something to do with the format of the screenshot taken by mss, the edited code for changing the image directly works perfectly fine
– NightShade
Nov 23 '18 at 5:57
1
Thankyou that was it, works perfectly now :D been a massive help
– NightShade
Nov 23 '18 at 6:04
|
show 1 more comment
One approach that you can take is to take the diff of this frame, from a snap-shot where all tiles are empty. This would get you the required mask with minimal computations.
Since you have not mentioned in the question that you have access to all empty tile snapshot, I would also present another technique known as color segmentation
. Since the background color is consistent, but the numbered color tiles changes, So we will segment out the background first and then invert the mask to find contours as :
import cv2
import numpy as np
def threshold_tiles(board_img):
board_bgd_color_low = np.array([155, 170, 140])
board_bgd_color_high = np.array([200, 185, 195])
board_empty_low = np.array([175, 180, 200])
board_empty_high = np.array([185, 195, 210])
mask_bgd = cv2.inRange(board_img, board_bgd_color_low, board_bgd_color_high)
mask_tile = cv2.inRange(board_img, board_empty_low, board_empty_high)
mask = cv2.max(mask_bgd, mask_tile)
kernel = np.ones((7, 7), np.uint8)
mask = cv2.dilate(mask, kernel)
return ~mask
def get_box_contours(mask):
_, cnt, hierarchy = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
cnt = filter(lambda x:cv2.contourArea(x) > 100, cnt)
return cnt
def main():
game_snapshot = cv2.imread("/path/to/img.jpg")
# Crop the white borders
game_snapshot = game_snapshot[5:-5, 5:-5]
mask = threshold_tiles(game_snapshot)
contours = get_box_contours(mask)
for i in xrange(len(contours)):
cv2.drawContours(game_snapshot, contours, i, (0, 255, 0), 3)
cv2.imwrite("output.png", game_snapshot)
if __name__ == "__main__":
main()
Intermediate masks:
+ =
Final mask for contours:
Output:
Is there a reason the edges like in 128 are not as straight for this? does this have something to do with the dilation
– NightShade
Nov 23 '18 at 5:56
No, it is due to the.jpg
format of the input image, which is a loosy format and interpolates the pixel values, it would have been better if you would have used.png
format for image serialization.
– ZdaR
Nov 23 '18 at 6:26
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53439913%2fdetecting-tile-squares-in-2048-with-cv2%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
Rather than doing thresholding with the greyscale image, you can do thresholding on the color image with cv2.inRange
. You can set the upper and lower bounds of allowed colors to include the numbered tiles but exclude the empty tiles and edges.
Also, I assume the step where you are checking if len(cnt) == 4:
is to return only the square contours. However, resizing can result in contours that aren't exactly square for the tiles, and won't pass this check. Instead, you can get the outer contours of the tiles by changing the second input of findContours
to 0 (contours = cv2.findContours(thresh, 0, 2)[1]
) which sets the retrieval mode to cv2.RETR_EXTERNAL
.
Here is the code with changes made, and appropriate upper and lower color bounds for the example image you gave.
import cv2
import mss
import time
import numpy as np
# Static screenshot for board
monitor = {"top": 135, "left": 425, "width": 500, "height": 500}
sct = mss.mss()
# inRange bounds
lower_bound = (0, 0, 210)
upper_bound = (230, 240, 250)
# Run for a maximum of 150s or until 'q' is pressed
last_time = time.time()
while time.time() - last_time < 150:
img = np.asarray(sct.grab(monitor))[:,:,:3]
resized_img = cv2.resize(img, (100, 100))
mask = cv2.inRange(resized_img, lower_bound, upper_bound)
contours = cv2.findContours(mask, 0, 2)[1]
for cnt in contours:
cv2.drawContours(resized_img, [cnt], 0, (0, 255, 0), 2)
cv2.imshow("2048", resized_img)
if cv2.waitKey(25) & 0xFF == ord("q"):
break
cv2.destroyAllWindows()
Here is the output image created:
Edit: Here is the code for using the example image directly:
import cv2
import numpy as np
img = cv2.imread('2048.jpg')
resized_img = cv2.resize(img, (100, 100))
lower_bound = (0,0,210)
upper_bound = (230,240,250)
mask = cv2.inRange(resized_img, lower_bound, upper_bound)
contours = cv2.findContours(mask, 0, 2)[1]
for cnt in contours:
cv2.drawContours(resized_img, [cnt], 0, (0, 255, 0), 2)
cv2.imshow('2048', resized_img)
cv2.waitKey(0)
This does seem like the short most simple solution and I love it :) however when I try to run it myself I get an error: mask = cv2.inRange(resized_img, lower_bound, upper_bound) cv2.error: OpenCV(3.4.3) C:projectsopencv-pythonopencvmodulescoresrcarithm.cpp:1778: error: (-209:Sizes of input arguments do not match) The lower boundary is neither an array of the same size and same type as src, nor a scalar in function 'cv::inRange'
– NightShade
Nov 23 '18 at 5:40
My first guess is theresized_img
is a greyscale image, not the color image. Can you check if that's the case? It needs to be a 3-channel image.
– A Kruger
Nov 23 '18 at 5:45
it does not appear to be, I have tried using the code as you have submitted it above
– NightShade
Nov 23 '18 at 5:47
maybe it has something to do with the format of the screenshot taken by mss, the edited code for changing the image directly works perfectly fine
– NightShade
Nov 23 '18 at 5:57
1
Thankyou that was it, works perfectly now :D been a massive help
– NightShade
Nov 23 '18 at 6:04
|
show 1 more comment
Rather than doing thresholding with the greyscale image, you can do thresholding on the color image with cv2.inRange
. You can set the upper and lower bounds of allowed colors to include the numbered tiles but exclude the empty tiles and edges.
Also, I assume the step where you are checking if len(cnt) == 4:
is to return only the square contours. However, resizing can result in contours that aren't exactly square for the tiles, and won't pass this check. Instead, you can get the outer contours of the tiles by changing the second input of findContours
to 0 (contours = cv2.findContours(thresh, 0, 2)[1]
) which sets the retrieval mode to cv2.RETR_EXTERNAL
.
Here is the code with changes made, and appropriate upper and lower color bounds for the example image you gave.
import cv2
import mss
import time
import numpy as np
# Static screenshot for board
monitor = {"top": 135, "left": 425, "width": 500, "height": 500}
sct = mss.mss()
# inRange bounds
lower_bound = (0, 0, 210)
upper_bound = (230, 240, 250)
# Run for a maximum of 150s or until 'q' is pressed
last_time = time.time()
while time.time() - last_time < 150:
img = np.asarray(sct.grab(monitor))[:,:,:3]
resized_img = cv2.resize(img, (100, 100))
mask = cv2.inRange(resized_img, lower_bound, upper_bound)
contours = cv2.findContours(mask, 0, 2)[1]
for cnt in contours:
cv2.drawContours(resized_img, [cnt], 0, (0, 255, 0), 2)
cv2.imshow("2048", resized_img)
if cv2.waitKey(25) & 0xFF == ord("q"):
break
cv2.destroyAllWindows()
Here is the output image created:
Edit: Here is the code for using the example image directly:
import cv2
import numpy as np
img = cv2.imread('2048.jpg')
resized_img = cv2.resize(img, (100, 100))
lower_bound = (0,0,210)
upper_bound = (230,240,250)
mask = cv2.inRange(resized_img, lower_bound, upper_bound)
contours = cv2.findContours(mask, 0, 2)[1]
for cnt in contours:
cv2.drawContours(resized_img, [cnt], 0, (0, 255, 0), 2)
cv2.imshow('2048', resized_img)
cv2.waitKey(0)
This does seem like the short most simple solution and I love it :) however when I try to run it myself I get an error: mask = cv2.inRange(resized_img, lower_bound, upper_bound) cv2.error: OpenCV(3.4.3) C:projectsopencv-pythonopencvmodulescoresrcarithm.cpp:1778: error: (-209:Sizes of input arguments do not match) The lower boundary is neither an array of the same size and same type as src, nor a scalar in function 'cv::inRange'
– NightShade
Nov 23 '18 at 5:40
My first guess is theresized_img
is a greyscale image, not the color image. Can you check if that's the case? It needs to be a 3-channel image.
– A Kruger
Nov 23 '18 at 5:45
it does not appear to be, I have tried using the code as you have submitted it above
– NightShade
Nov 23 '18 at 5:47
maybe it has something to do with the format of the screenshot taken by mss, the edited code for changing the image directly works perfectly fine
– NightShade
Nov 23 '18 at 5:57
1
Thankyou that was it, works perfectly now :D been a massive help
– NightShade
Nov 23 '18 at 6:04
|
show 1 more comment
Rather than doing thresholding with the greyscale image, you can do thresholding on the color image with cv2.inRange
. You can set the upper and lower bounds of allowed colors to include the numbered tiles but exclude the empty tiles and edges.
Also, I assume the step where you are checking if len(cnt) == 4:
is to return only the square contours. However, resizing can result in contours that aren't exactly square for the tiles, and won't pass this check. Instead, you can get the outer contours of the tiles by changing the second input of findContours
to 0 (contours = cv2.findContours(thresh, 0, 2)[1]
) which sets the retrieval mode to cv2.RETR_EXTERNAL
.
Here is the code with changes made, and appropriate upper and lower color bounds for the example image you gave.
import cv2
import mss
import time
import numpy as np
# Static screenshot for board
monitor = {"top": 135, "left": 425, "width": 500, "height": 500}
sct = mss.mss()
# inRange bounds
lower_bound = (0, 0, 210)
upper_bound = (230, 240, 250)
# Run for a maximum of 150s or until 'q' is pressed
last_time = time.time()
while time.time() - last_time < 150:
img = np.asarray(sct.grab(monitor))[:,:,:3]
resized_img = cv2.resize(img, (100, 100))
mask = cv2.inRange(resized_img, lower_bound, upper_bound)
contours = cv2.findContours(mask, 0, 2)[1]
for cnt in contours:
cv2.drawContours(resized_img, [cnt], 0, (0, 255, 0), 2)
cv2.imshow("2048", resized_img)
if cv2.waitKey(25) & 0xFF == ord("q"):
break
cv2.destroyAllWindows()
Here is the output image created:
Edit: Here is the code for using the example image directly:
import cv2
import numpy as np
img = cv2.imread('2048.jpg')
resized_img = cv2.resize(img, (100, 100))
lower_bound = (0,0,210)
upper_bound = (230,240,250)
mask = cv2.inRange(resized_img, lower_bound, upper_bound)
contours = cv2.findContours(mask, 0, 2)[1]
for cnt in contours:
cv2.drawContours(resized_img, [cnt], 0, (0, 255, 0), 2)
cv2.imshow('2048', resized_img)
cv2.waitKey(0)
Rather than doing thresholding with the greyscale image, you can do thresholding on the color image with cv2.inRange
. You can set the upper and lower bounds of allowed colors to include the numbered tiles but exclude the empty tiles and edges.
Also, I assume the step where you are checking if len(cnt) == 4:
is to return only the square contours. However, resizing can result in contours that aren't exactly square for the tiles, and won't pass this check. Instead, you can get the outer contours of the tiles by changing the second input of findContours
to 0 (contours = cv2.findContours(thresh, 0, 2)[1]
) which sets the retrieval mode to cv2.RETR_EXTERNAL
.
Here is the code with changes made, and appropriate upper and lower color bounds for the example image you gave.
import cv2
import mss
import time
import numpy as np
# Static screenshot for board
monitor = {"top": 135, "left": 425, "width": 500, "height": 500}
sct = mss.mss()
# inRange bounds
lower_bound = (0, 0, 210)
upper_bound = (230, 240, 250)
# Run for a maximum of 150s or until 'q' is pressed
last_time = time.time()
while time.time() - last_time < 150:
img = np.asarray(sct.grab(monitor))[:,:,:3]
resized_img = cv2.resize(img, (100, 100))
mask = cv2.inRange(resized_img, lower_bound, upper_bound)
contours = cv2.findContours(mask, 0, 2)[1]
for cnt in contours:
cv2.drawContours(resized_img, [cnt], 0, (0, 255, 0), 2)
cv2.imshow("2048", resized_img)
if cv2.waitKey(25) & 0xFF == ord("q"):
break
cv2.destroyAllWindows()
Here is the output image created:
Edit: Here is the code for using the example image directly:
import cv2
import numpy as np
img = cv2.imread('2048.jpg')
resized_img = cv2.resize(img, (100, 100))
lower_bound = (0,0,210)
upper_bound = (230,240,250)
mask = cv2.inRange(resized_img, lower_bound, upper_bound)
contours = cv2.findContours(mask, 0, 2)[1]
for cnt in contours:
cv2.drawContours(resized_img, [cnt], 0, (0, 255, 0), 2)
cv2.imshow('2048', resized_img)
cv2.waitKey(0)
edited Nov 23 '18 at 5:58
answered Nov 23 '18 at 5:19
A KrugerA Kruger
1,37838
1,37838
This does seem like the short most simple solution and I love it :) however when I try to run it myself I get an error: mask = cv2.inRange(resized_img, lower_bound, upper_bound) cv2.error: OpenCV(3.4.3) C:projectsopencv-pythonopencvmodulescoresrcarithm.cpp:1778: error: (-209:Sizes of input arguments do not match) The lower boundary is neither an array of the same size and same type as src, nor a scalar in function 'cv::inRange'
– NightShade
Nov 23 '18 at 5:40
My first guess is theresized_img
is a greyscale image, not the color image. Can you check if that's the case? It needs to be a 3-channel image.
– A Kruger
Nov 23 '18 at 5:45
it does not appear to be, I have tried using the code as you have submitted it above
– NightShade
Nov 23 '18 at 5:47
maybe it has something to do with the format of the screenshot taken by mss, the edited code for changing the image directly works perfectly fine
– NightShade
Nov 23 '18 at 5:57
1
Thankyou that was it, works perfectly now :D been a massive help
– NightShade
Nov 23 '18 at 6:04
|
show 1 more comment
This does seem like the short most simple solution and I love it :) however when I try to run it myself I get an error: mask = cv2.inRange(resized_img, lower_bound, upper_bound) cv2.error: OpenCV(3.4.3) C:projectsopencv-pythonopencvmodulescoresrcarithm.cpp:1778: error: (-209:Sizes of input arguments do not match) The lower boundary is neither an array of the same size and same type as src, nor a scalar in function 'cv::inRange'
– NightShade
Nov 23 '18 at 5:40
My first guess is theresized_img
is a greyscale image, not the color image. Can you check if that's the case? It needs to be a 3-channel image.
– A Kruger
Nov 23 '18 at 5:45
it does not appear to be, I have tried using the code as you have submitted it above
– NightShade
Nov 23 '18 at 5:47
maybe it has something to do with the format of the screenshot taken by mss, the edited code for changing the image directly works perfectly fine
– NightShade
Nov 23 '18 at 5:57
1
Thankyou that was it, works perfectly now :D been a massive help
– NightShade
Nov 23 '18 at 6:04
This does seem like the short most simple solution and I love it :) however when I try to run it myself I get an error: mask = cv2.inRange(resized_img, lower_bound, upper_bound) cv2.error: OpenCV(3.4.3) C:projectsopencv-pythonopencvmodulescoresrcarithm.cpp:1778: error: (-209:Sizes of input arguments do not match) The lower boundary is neither an array of the same size and same type as src, nor a scalar in function 'cv::inRange'
– NightShade
Nov 23 '18 at 5:40
This does seem like the short most simple solution and I love it :) however when I try to run it myself I get an error: mask = cv2.inRange(resized_img, lower_bound, upper_bound) cv2.error: OpenCV(3.4.3) C:projectsopencv-pythonopencvmodulescoresrcarithm.cpp:1778: error: (-209:Sizes of input arguments do not match) The lower boundary is neither an array of the same size and same type as src, nor a scalar in function 'cv::inRange'
– NightShade
Nov 23 '18 at 5:40
My first guess is the
resized_img
is a greyscale image, not the color image. Can you check if that's the case? It needs to be a 3-channel image.– A Kruger
Nov 23 '18 at 5:45
My first guess is the
resized_img
is a greyscale image, not the color image. Can you check if that's the case? It needs to be a 3-channel image.– A Kruger
Nov 23 '18 at 5:45
it does not appear to be, I have tried using the code as you have submitted it above
– NightShade
Nov 23 '18 at 5:47
it does not appear to be, I have tried using the code as you have submitted it above
– NightShade
Nov 23 '18 at 5:47
maybe it has something to do with the format of the screenshot taken by mss, the edited code for changing the image directly works perfectly fine
– NightShade
Nov 23 '18 at 5:57
maybe it has something to do with the format of the screenshot taken by mss, the edited code for changing the image directly works perfectly fine
– NightShade
Nov 23 '18 at 5:57
1
1
Thankyou that was it, works perfectly now :D been a massive help
– NightShade
Nov 23 '18 at 6:04
Thankyou that was it, works perfectly now :D been a massive help
– NightShade
Nov 23 '18 at 6:04
|
show 1 more comment
One approach that you can take is to take the diff of this frame, from a snap-shot where all tiles are empty. This would get you the required mask with minimal computations.
Since you have not mentioned in the question that you have access to all empty tile snapshot, I would also present another technique known as color segmentation
. Since the background color is consistent, but the numbered color tiles changes, So we will segment out the background first and then invert the mask to find contours as :
import cv2
import numpy as np
def threshold_tiles(board_img):
board_bgd_color_low = np.array([155, 170, 140])
board_bgd_color_high = np.array([200, 185, 195])
board_empty_low = np.array([175, 180, 200])
board_empty_high = np.array([185, 195, 210])
mask_bgd = cv2.inRange(board_img, board_bgd_color_low, board_bgd_color_high)
mask_tile = cv2.inRange(board_img, board_empty_low, board_empty_high)
mask = cv2.max(mask_bgd, mask_tile)
kernel = np.ones((7, 7), np.uint8)
mask = cv2.dilate(mask, kernel)
return ~mask
def get_box_contours(mask):
_, cnt, hierarchy = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
cnt = filter(lambda x:cv2.contourArea(x) > 100, cnt)
return cnt
def main():
game_snapshot = cv2.imread("/path/to/img.jpg")
# Crop the white borders
game_snapshot = game_snapshot[5:-5, 5:-5]
mask = threshold_tiles(game_snapshot)
contours = get_box_contours(mask)
for i in xrange(len(contours)):
cv2.drawContours(game_snapshot, contours, i, (0, 255, 0), 3)
cv2.imwrite("output.png", game_snapshot)
if __name__ == "__main__":
main()
Intermediate masks:
+ =
Final mask for contours:
Output:
Is there a reason the edges like in 128 are not as straight for this? does this have something to do with the dilation
– NightShade
Nov 23 '18 at 5:56
No, it is due to the.jpg
format of the input image, which is a loosy format and interpolates the pixel values, it would have been better if you would have used.png
format for image serialization.
– ZdaR
Nov 23 '18 at 6:26
add a comment |
One approach that you can take is to take the diff of this frame, from a snap-shot where all tiles are empty. This would get you the required mask with minimal computations.
Since you have not mentioned in the question that you have access to all empty tile snapshot, I would also present another technique known as color segmentation
. Since the background color is consistent, but the numbered color tiles changes, So we will segment out the background first and then invert the mask to find contours as :
import cv2
import numpy as np
def threshold_tiles(board_img):
board_bgd_color_low = np.array([155, 170, 140])
board_bgd_color_high = np.array([200, 185, 195])
board_empty_low = np.array([175, 180, 200])
board_empty_high = np.array([185, 195, 210])
mask_bgd = cv2.inRange(board_img, board_bgd_color_low, board_bgd_color_high)
mask_tile = cv2.inRange(board_img, board_empty_low, board_empty_high)
mask = cv2.max(mask_bgd, mask_tile)
kernel = np.ones((7, 7), np.uint8)
mask = cv2.dilate(mask, kernel)
return ~mask
def get_box_contours(mask):
_, cnt, hierarchy = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
cnt = filter(lambda x:cv2.contourArea(x) > 100, cnt)
return cnt
def main():
game_snapshot = cv2.imread("/path/to/img.jpg")
# Crop the white borders
game_snapshot = game_snapshot[5:-5, 5:-5]
mask = threshold_tiles(game_snapshot)
contours = get_box_contours(mask)
for i in xrange(len(contours)):
cv2.drawContours(game_snapshot, contours, i, (0, 255, 0), 3)
cv2.imwrite("output.png", game_snapshot)
if __name__ == "__main__":
main()
Intermediate masks:
+ =
Final mask for contours:
Output:
Is there a reason the edges like in 128 are not as straight for this? does this have something to do with the dilation
– NightShade
Nov 23 '18 at 5:56
No, it is due to the.jpg
format of the input image, which is a loosy format and interpolates the pixel values, it would have been better if you would have used.png
format for image serialization.
– ZdaR
Nov 23 '18 at 6:26
add a comment |
One approach that you can take is to take the diff of this frame, from a snap-shot where all tiles are empty. This would get you the required mask with minimal computations.
Since you have not mentioned in the question that you have access to all empty tile snapshot, I would also present another technique known as color segmentation
. Since the background color is consistent, but the numbered color tiles changes, So we will segment out the background first and then invert the mask to find contours as :
import cv2
import numpy as np
def threshold_tiles(board_img):
board_bgd_color_low = np.array([155, 170, 140])
board_bgd_color_high = np.array([200, 185, 195])
board_empty_low = np.array([175, 180, 200])
board_empty_high = np.array([185, 195, 210])
mask_bgd = cv2.inRange(board_img, board_bgd_color_low, board_bgd_color_high)
mask_tile = cv2.inRange(board_img, board_empty_low, board_empty_high)
mask = cv2.max(mask_bgd, mask_tile)
kernel = np.ones((7, 7), np.uint8)
mask = cv2.dilate(mask, kernel)
return ~mask
def get_box_contours(mask):
_, cnt, hierarchy = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
cnt = filter(lambda x:cv2.contourArea(x) > 100, cnt)
return cnt
def main():
game_snapshot = cv2.imread("/path/to/img.jpg")
# Crop the white borders
game_snapshot = game_snapshot[5:-5, 5:-5]
mask = threshold_tiles(game_snapshot)
contours = get_box_contours(mask)
for i in xrange(len(contours)):
cv2.drawContours(game_snapshot, contours, i, (0, 255, 0), 3)
cv2.imwrite("output.png", game_snapshot)
if __name__ == "__main__":
main()
Intermediate masks:
+ =
Final mask for contours:
Output:
One approach that you can take is to take the diff of this frame, from a snap-shot where all tiles are empty. This would get you the required mask with minimal computations.
Since you have not mentioned in the question that you have access to all empty tile snapshot, I would also present another technique known as color segmentation
. Since the background color is consistent, but the numbered color tiles changes, So we will segment out the background first and then invert the mask to find contours as :
import cv2
import numpy as np
def threshold_tiles(board_img):
board_bgd_color_low = np.array([155, 170, 140])
board_bgd_color_high = np.array([200, 185, 195])
board_empty_low = np.array([175, 180, 200])
board_empty_high = np.array([185, 195, 210])
mask_bgd = cv2.inRange(board_img, board_bgd_color_low, board_bgd_color_high)
mask_tile = cv2.inRange(board_img, board_empty_low, board_empty_high)
mask = cv2.max(mask_bgd, mask_tile)
kernel = np.ones((7, 7), np.uint8)
mask = cv2.dilate(mask, kernel)
return ~mask
def get_box_contours(mask):
_, cnt, hierarchy = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
cnt = filter(lambda x:cv2.contourArea(x) > 100, cnt)
return cnt
def main():
game_snapshot = cv2.imread("/path/to/img.jpg")
# Crop the white borders
game_snapshot = game_snapshot[5:-5, 5:-5]
mask = threshold_tiles(game_snapshot)
contours = get_box_contours(mask)
for i in xrange(len(contours)):
cv2.drawContours(game_snapshot, contours, i, (0, 255, 0), 3)
cv2.imwrite("output.png", game_snapshot)
if __name__ == "__main__":
main()
Intermediate masks:
+ =
Final mask for contours:
Output:
edited Nov 23 '18 at 5:31
answered Nov 23 '18 at 5:25
ZdaRZdaR
13.4k33554
13.4k33554
Is there a reason the edges like in 128 are not as straight for this? does this have something to do with the dilation
– NightShade
Nov 23 '18 at 5:56
No, it is due to the.jpg
format of the input image, which is a loosy format and interpolates the pixel values, it would have been better if you would have used.png
format for image serialization.
– ZdaR
Nov 23 '18 at 6:26
add a comment |
Is there a reason the edges like in 128 are not as straight for this? does this have something to do with the dilation
– NightShade
Nov 23 '18 at 5:56
No, it is due to the.jpg
format of the input image, which is a loosy format and interpolates the pixel values, it would have been better if you would have used.png
format for image serialization.
– ZdaR
Nov 23 '18 at 6:26
Is there a reason the edges like in 128 are not as straight for this? does this have something to do with the dilation
– NightShade
Nov 23 '18 at 5:56
Is there a reason the edges like in 128 are not as straight for this? does this have something to do with the dilation
– NightShade
Nov 23 '18 at 5:56
No, it is due to the
.jpg
format of the input image, which is a loosy format and interpolates the pixel values, it would have been better if you would have used .png
format for image serialization.– ZdaR
Nov 23 '18 at 6:26
No, it is due to the
.jpg
format of the input image, which is a loosy format and interpolates the pixel values, it would have been better if you would have used .png
format for image serialization.– ZdaR
Nov 23 '18 at 6:26
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53439913%2fdetecting-tile-squares-in-2048-with-cv2%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
It looks like you are having boxes with defined colors at fixed positions. So, there is not much sense in detecting their position and they should be rather easy to identify if you filter for the right color(s).
– Klaus D.
Nov 23 '18 at 2:33
@KlausD. this was more of an exercise to determine if I could dynamically work out the position of the boxes as practice using cv2 - so if the boxes were layed out randomly, what the method would be to detect them. How would I go about filtering the colours if I were to do it using that method?
– NightShade
Nov 23 '18 at 3:14
Can you please attach full size sample input image? Do you want to detect the boxes only or do you also want to segment the boxes on the basis of color?
– ZdaR
Nov 23 '18 at 4:13
You need to filter the image more precisely... Try using some edge detection open CV algo with dilating and eroding. To prevent whole box to be selected or only smaller parts ( round of 6 ) to be selected as contour just check area of every contour.... Opencv has facility to do this. Once you have area of each contour then drop the contour if it is very large or very small. This will filter all unnecessary contours and you will get the desired contours....
– Devashish Prasad
Nov 23 '18 at 4:14
@ZdaR I have edited the post and provided a large sample input image and sample output made in paint. I only want to detect the individual game tiles, with no concern for the colour or value within them
– NightShade
Nov 23 '18 at 4:38