Python opencv sorting contours
<p>I am following this question:</p>

<p><a href="http://stackoverflow.com/questions/38654302/how-can-i-sort-contours-from-left-to-right-and-top-to-bottom">How can I sort contours from left to right and top to bottom?</a></p>

<p>to sort contours from left-to-right and top-to-bottom. However, my contours are found using this (OpenCV 3):</p>

<pre><code>im2, contours, hierarchy = cv2.findContours(threshold,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
</code></pre>

<p>and they are formatted like this:</p>

<pre><code>   array([[[ 1,  1]],

   [[ 1, 36]],

   [[63, 36]],

   [[64, 35]],

   [[88, 35]],

   [[89, 34]],

   [[94, 34]],

   [[94,  1]]], dtype=int32)]
</code></pre>

<p>When I run the code </p>

<pre><code>max_width = max(contours, key=lambda r: r[0] + r[2])[0]
max_height = max(contours, key=lambda r: r[3])[3]
nearest = max_height * 1.4
contours.sort(key=lambda r: (int(nearest * round(float(r[1])/nearest)) * max_width + r[0]))
</code></pre>

<p>I am getting the error </p>

<pre><code>ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
</code></pre>

<p>so I changed it to this:</p>

<pre><code>max_width = max(contours, key=lambda r:  np.max(r[0] + r[2]))[0]
max_height = max(contours, key=lambda r:  np.max(r[3]))[3]
nearest = max_height * 1.4
contours.sort(key=lambda r: (int(nearest * round(float(r[1])/nearest)) * max_width + r[0]))
</code></pre>

<p>but now I am getting the error:</p>

<p><code>TypeError: only length-1 arrays can be converted to Python scalars</code></p>

<p><strong>EDIT:</strong></p>

<p>After reading the answer below I modified my code:</p>

<p><strong>EDIT 2</strong></p>

<p>This is the code that I use to "dilate" the characters and find the contours</p>

<pre><code>kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(35,35))

# dilate the image to get text
# binaryContour is just the black and white image shown below
dilation = cv2.dilate(binaryContour,kernel,iterations = 2)
</code></pre>

<p><strong>END OF EDIT 2</strong></p>

<pre><code>im2, contours, hierarchy = cv2.findContours(dilation,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)

myContours = []

# Process the raw contours to get bounding rectangles
for cnt in reversed(contours):

    epsilon = 0.1*cv2.arcLength(cnt,True)
    approx = cv2.approxPolyDP(cnt,epsilon,True)

    if len(approx == 4):

        rectangle = cv2.boundingRect(cnt)
        myContours.append(rectangle)

max_width = max(myContours, key=lambda r: r[0] + r[2])[0]
max_height = max(myContours, key=lambda r: r[3])[3]
nearest = max_height * 1.4
myContours.sort(key=lambda r: (int(nearest * round(float(r[1])/nearest)) * max_width + r[0]))

i=0
for x,y,w,h in myContours:

    letter = binaryContour[y:y+h, x:x+w]
    cv2.rectangle(binaryContour,(x,y),(x+w,y+h),(255,255,255),2)
    cv2.imwrite("pictures/"+str(i)+'.png', letter) # save contour to file
    i+=1
</code></pre>

<p>Contours before sorting:</p>

<pre><code>[(1, 1, 94, 36), (460, 223, 914, 427), (888, 722, 739, 239), (35,723, 522, 228), 
(889, 1027, 242, 417), (70, 1028, 693, 423), (1138, 1028, 567, 643),     
(781, 1030, 98, 413), (497, 1527, 303, 132), (892, 1527, 168, 130),  
(37, 1719, 592, 130), (676, 1721, 413, 129), (1181, 1723, 206, 128), 
(30, 1925, 997, 236), (1038, 1929, 170, 129), (140, 2232, 1285, 436)]
</code></pre>

<p>Contours after sorting:</p>

<p>(<strong>NOTE:</strong> This is not the order I want the contours to be sorted in. Refer to image at the bottom)</p>

<pre><code>[(1, 1, 94, 36), (460, 223, 914, 427), (35, 723, 522, 228), (70,1028, 693, 423), 
(781, 1030, 98, 413), (888, 722, 739, 239), (889, 1027, 242, 417), 
(1138, 1028, 567, 643), (30, 1925, 997, 236), (37, 1719, 592, 130), 
(140, 2232, 1285, 436), (497, 1527, 303, 132), (676, 1721, 413, 129), 
(892, 1527, 168, 130), (1038, 1929, 170, 129), (1181, 1723, 206, 128)]
</code></pre>

<p>Image I am working with</p>

<p><a href="http://i.stack.imgur.com/9VayB.png" rel="nofollow"><img src="http://i.stack.imgur.com/9VayB.png" alt="enter image description here"></a></p>

<p>I want to find the contours in the following order:
<a href="http://i.stack.imgur.com/s4jKm.png" rel="nofollow"><img src="http://i.stack.imgur.com/s4jKm.png" alt="enter image description here"></a></p>

<p>Dilation image used for finding contours
<a href="http://i.stack.imgur.com/sTyvW.png" rel="nofollow"><img src="http://i.stack.imgur.com/sTyvW.png" alt="enter image description here"></a></p>

<p>What you actually need is to devise a formula to convert your contour information to a rank and use that rank to sort the contours, Since you need to sort the contours from top to Bottom and left to right so your formula must involve the <code>origin</code> of a given contour to calculate its rank. For example we can use this simple method:</p>

<pre><code>def get_contour_precedence(contour, cols):
    origin = cv2.boundingRect(contour)
    return origin[1] * cols + origin[0]
</code></pre>

<p>It gives a rank to each contour depending upon the origin of contour. It varies largely when two consecutive contours lie vertically but varies marginally when contours are stacked horizontally. So in this way, First the contours would be grouped from Top to Bottom and in case of Clash the less variant value among the horizontal laid contours would be used. </p>

<pre><code>import cv2

def get_contour_precedence(contour, cols):
    tolerance_factor = 10
    origin = cv2.boundingRect(contour)
    return ((origin[1] // tolerance_factor) * tolerance_factor) * cols + origin[0]

img = cv2.imread("/Users/anmoluppal/Downloads/9VayB.png", 0)

_, img = cv2.threshold(img, 70, 255, cv2.THRESH_BINARY)

im, contours, h = cv2.findContours(img.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

contours.sort(key=lambda x:get_contour_precedence(x, img.shape[1]))

# For debugging purposes.
for i in xrange(len(contours)):
    img = cv2.putText(img, str(i), cv2.boundingRect(contours[i])[:2], cv2.FONT_HERSHEY_COMPLEX, 1, [125])
</code></pre>

<p><a href="http://i.stack.imgur.com/ooNMj.png" rel="nofollow"><img src="http://i.stack.imgur.com/ooNMj.png" alt="enter image description here"></a></p>

<p>If you see closely, the third row where <code>3, 4, 5, 6</code> contours are placed the <code>6</code> comes between 3 and 5, The reason is that the <code>6</code>th contour is slightly below the line of <code>3, 4, 5</code> contours.</p>

<p>Tell me is you want the output in other way around we can tweak the <code>get_contour_precedence</code> to get <code>3, 4, 5, 6</code> ranks of contour corrected. </p>

python python-2.7 opencv lambda opencv3.0