Skip to content

Instantly share code, notes, and snippets.

@AVatch
Created May 9, 2016 19:11
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AVatch/843a9e5ed9cd1214676c849c7ff6736d to your computer and use it in GitHub Desktop.
Save AVatch/843a9e5ed9cd1214676c849c7ff6736d to your computer and use it in GitHub Desktop.
OpenCV SIFT
import cv2
import numpy as np
from matplotlib import pyplot as plt
# Import our game board
canvas = cv2.imread('./data/box_med.jpg')
# Import our piece (we are going to use a clump for now)
piece = cv2.imread('./data/piece_small.jpg')
# Pre-process the piece
def identify_contour(piece, threshold_low=150, threshold_high=255):
"""Identify the contour around the piece"""
piece = cv2.cvtColor(piece, cv2.COLOR_BGR2GRAY) # better in grayscale
ret, thresh = cv2.threshold(piece, threshold_low, threshold_high, 0)
image, contours, heirarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
contour_sorted = np.argsort(map(cv2.contourArea, contours))
return contours, contour_sorted[-2]
def get_bounding_rect(contour):
"""Return the bounding rectangle given a contour"""
x,y,w,h = cv2.boundingRect(contour)
return x, y, w, h
# Get the contours
contours, contour_index = identify_contour(piece.copy())
# Get a bounding box around the piece
x, y, w, h = get_bounding_rect(contours[contour_index])
cropped_piece = piece.copy()[y:y+h, x:x+w]
# Initiate SIFT detector
sift = cv2.xfeatures2d.SIFT_create()
img1 = processed_piece.copy() # queryImage
img2 = canvas.copy() # trainImage
# find the keypoints and descriptors with SIFT
kp1, des1 = sift.detectAndCompute(img1,None)
kp2, des2 = sift.detectAndCompute(img2,None)
FLANN_INDEX_KDTREE = 0
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
search_params = dict(checks = 50)
flann = cv2.FlannBasedMatcher(index_params, search_params)
matches = flann.knnMatch(des1,des2,k=2)
# store all the good matches as per Lowe's ratio test.
good = []
for m,n in matches:
# good.append(m)
if m.distance < 0.7*n.distance:
good.append(m)
MIN_MATCH_COUNT = 10
if len(good)>MIN_MATCH_COUNT:
src_pts = np.float32([ kp1[m.queryIdx].pt for m in good ]).reshape(-1,1,2)
dst_pts = np.float32([ kp2[m.trainIdx].pt for m in good ]).reshape(-1,1,2)
M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC,5.0)
matchesMask = mask.ravel().tolist()
d,h,w = img1.shape[::-1]
pts = np.float32([ [0,0],[0,h-1],[w-1,h-1],[w-1,0] ]).reshape(-1,1,2)
dst = cv2.perspectiveTransform(pts,M)
img2 = cv2.polylines(img2,[np.int32(dst)],True,255,3, cv2.LINE_AA)
else:
print "Not enough matches are found - %d/%d" % (len(good),MIN_MATCH_COUNT)
matchesMask = None
draw_params = dict(matchColor = (0,255,0), # draw matches in green color
singlePointColor = None,
matchesMask = matchesMask, # draw only inliers
flags = cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
img3 = cv2.drawMatches(img1,kp1,img2,kp2,good,None,**draw_params)
cv2.imwrite('./output/solution.jpg', img3)
@frajos100
Copy link

Hi Adrian,
Has there been any latest update on the algorithm to solve Jigsaw puzzle.
What is the best algorithm and if so it would be nice if you could provide some insights along with link.

@YamilDiaz
Copy link

YamilDiaz commented Dec 18, 2021

I'm trying to upgrade their algorithm, but I'm getting this error:

[1] 76794 illegal hardware instruction sudo python3 sift.py

This is the current code:

import cv2
import numpy as np
from matplotlib import pyplot as plt

# Import our game board
canvas = cv2.imread('./jigsaw.jpeg')
# Import our piece (we are going to use a clump for now)
piece = cv2.imread('./piece.jpg')

# Pre-process the piece
def identify_contour(piece, threshold_low=150, threshold_high=255):
    """Identify the contour around the piece"""
    piece = cv2.cvtColor(piece, cv2.COLOR_BGR2GRAY) # better in grayscale
    ret, thresh = cv2.threshold(piece, threshold_low, threshold_high, 0)
    contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    print(map(cv2.contourArea, contours))
    contour_sorted = np.argsort(map(cv2.contourArea, contours))
    return contours, contour_sorted[0]

def get_bounding_rect(contour):
    """Return the bounding rectangle given a contour"""
    x,y,w,h = cv2.boundingRect(contour)
    return x, y, w, h

# Get the contours
contours, contour_index = identify_contour(piece.copy())

# Get a bounding box around the piece
x, y, w, h = get_bounding_rect(contours[contour_index])
cropped_piece = piece.copy()[y:y+h, x:x+w]

# Initiate SIFT detector
sift =  cv2.SIFT_create()

img1 = cropped_piece.copy() # queryImage
img2 = canvas.copy() # trainImage

# find the keypoints and descriptors with SIFT
kp1, des1 = sift.detectAndCompute(img1,None)
kp2, des2 = sift.detectAndCompute(img2,None)

FLANN_INDEX_KDTREE = 0
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
search_params = dict(checks = 50)

flann = cv2.FlannBasedMatcher(index_params, search_params)

matches = flann.knnMatch(des1,des2,k=2)

# store all the good matches as per Lowe's ratio test.
good = []
for m,n in matches:
#     good.append(m)
    if m.distance < 0.7*n.distance:
        good.append(m)


MIN_MATCH_COUNT = 10

if len(good)>MIN_MATCH_COUNT:
    src_pts = np.float32([ kp1[m.queryIdx].pt for m in good ]).reshape(-1,1,2)
    dst_pts = np.float32([ kp2[m.trainIdx].pt for m in good ]).reshape(-1,1,2)

    M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC,5.0)
    matchesMask = mask.ravel().tolist()

    d,h,w = img1.shape[::-1]
    pts = np.float32([ [0,0],[0,h-1],[w-1,h-1],[w-1,0] ]).reshape(-1,1,2)
    dst = cv2.perspectiveTransform(pts,M)

    img2 = cv2.polylines(img2,[np.int32(dst)],True,255,3, cv2.LINE_AA)

else:
    # print "Not enough matches are found - %d/%d" % (len(good),MIN_MATCH_COUNT)
    matchesMask = None

draw_params = dict(matchColor = (0,255,0), # draw matches in green color
                   singlePointColor = None,
                   matchesMask = matchesMask, # draw only inliers
                   flags = cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)

img3 = cv2.drawMatches(img1,kp1,img2,kp2,good,None,**draw_params)
cv2.imwrite('./output/solution.jpg', img3)

@jumpjack
Copy link

jumpjack commented Aug 7, 2022

Any progress in this? It would be cool to be able to take a shot of one piece, pass it to the script, and get a "heat map" showing where the piece fits more probably in the big image. This should allow working also with lo-res images (in case of big puzzles).

@jumpjack
Copy link

jumpjack commented Aug 7, 2022

For high number of pieces, instead, I think the only way to go is focusing on borders and completely disregard image, but can this script be adapted for such a task?

@jumpjack
Copy link

jumpjack commented Aug 7, 2022

Found "border finder": https://github.com/ralbertazzi/jigsaw-puzzle-solver
But it just "digitize" borders, it does not do anything more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment