2017-02-15 6 views
1

Я хочу использовать изображение с помощью. Для этого я написал (правда, с большим количеством помощи) программы, которая:OpenCV/Python: cv2.minAreaRect не вернет вращающийся прямоугольник

  1. трансформирует изображение, чтобы быть легче вычислить (молотите, расширение и т.д.)
  2. рисуют контуры вокруг всех объектов
  3. вычисляющих четыре крайние точки вокруг текста контуров (без учета ничего с запасом)
  4. рисует прямоугольник вокруг этой области, используя cv2.minAreaRect

идея заключалась в том, что cv2.minAreaRect возвращает угол а, который я мог бы используйте для выравнивания изображения. Однако в моем случае это -90 °.

Вы можете увидеть образец входного изображения here. Вы можете увидеть результат, который я получаю here.

Я проверил программу на «чистом» изображении (MS Word Screenshot rotaten ≈ 30 ° в Gimp), и он дал идентичный результат.

Мой код:

import numpy as np 
import cv2 
import itertools 

img = cv2.imread('zuo.png') 
imgray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) 

ret,thresh = cv2.threshold(imgray,64,255,0) 
############ 
kernel = np.ones((2,2),np.uint8) 
img_e = cv2.dilate(thresh,kernel,iterations = 1) 
# cv2.imwrite("out_eroded.png", img_e) 
# http://docs.opencv.org/3.0-beta/doc/py_tutorials/py_imgproc/py_morphological_ops/py_morphological_ops.html 
# img_e = thresh 
############ 
imgbw, contours, hierarchy = cv2.findContours(img_e,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE) 
# imgbw, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE) 

margin_distance = 25 

def flatten(arr, n = 1): 
    # print(arr) 
    ret = list(itertools.chain.from_iterable(arr)) 
    # print(ret) 
    if n != 1: 
     return flatten(ret, n - 1) 
    else: 
     return ret 

# print(list(flatten([[1,2,3],[4,5,6], [7], [8,9]]))) 

def get_min_max_values(cs, im_y, im_x): 
    # print(flatten(cs), 1) 
    # print(im_y, im_x) 
    min_y = im_y - margin_distance 
    min_x = im_x - margin_distance 
    max_y = margin_distance 
    max_x = margin_distance 
    for lvl1 in cs: 
     for lvl2 in lvl1: 
      x, y = lvl2[0] 
      # x = im_x - x 
      # y = im_y - y 
      max_y = max(y, max_y) if y + margin_distance < im_y else max_y 
      max_x = max(x, max_x) if x + margin_distance < im_x else max_x 
      min_y = min(y, min_y) if y > margin_distance else min_y 
      min_x = min(x, min_x) if x > margin_distance else min_x 

    return ((min_y, min_x), (min_y, max_x), (max_y, min_x), (max_y, max_x)) 

new_rect = get_min_max_values(contours, len(img), len(img[0])) 
new_rect = list(map(lambda x: list(x)[::-1], list(new_rect))) 
print(new_rect) 
rect = cv2.minAreaRect(np.int0(new_rect)) 
# print(rect) 
print(rect) 
box = cv2.boxPoints(rect) 
box = np.int0(box) 

img_out = cv2.drawContours(img, [box], -1, (0,0,255), 5) # -1 = wszystkie kontury 
img_out = cv2.drawContours(img, contours, -1, (0,255,0), 3) 

cv2.imwrite("out.png", img_out) 

Почему не прямоугольник перекос в соответствии с текстом? Я не вижу никаких артефактов, которые бы оправдывали это.

EDIT: Добавлены чистые, родственные цифровые файлы: input и output.

+1

Можете ли вы построить (с красным кругом или как угодно) точки, содержащиеся в 'new_rect'? – Miki

+0

Вы можете добавить изображение GIMP и результат? – Micka

+1

в get_min_max_values ​​вы возвращаете выровненный по оси прямоугольник (угловые точки), так что оптимизируется minAreaRect. – Micka

ответ

2

TLDR: Используйте только выпуклый корпус вместо четырех точек!

Часть 1: Ошибка (ы) в вашем текущем подходе.

Ваша функция get_min_max_values ​​вычисляет угловые точки axis-aligned bounding box of all contours. Но то, что вы на самом деле хотите вычислить здесь, - это координаты самой левой, самой верхней, самой правой и точки ботомоста всех контуров.

Вместо того, чтобы только «запомнить» минимальный y, вы должны сохранить обе координаты точки, где y было минимальным (самая верхняя точка). То же самое относится ко всем остальным пунктам.

В приведенном ниже коде показано, как правильно вычислить эти точки. Я решил оставить фрагмент кода коротким и читаемым, поэтому я только показываю, как вычислить левую и верхнюю точку здесь. Все четыре точки вычисляются одинаково так или иначе ...

Как вы заметили, я не сравниваю (clamp) точки с пометкой с петлей; вместо этого я делаю это только один раз в конце цикла, так как это дает те же результаты, но код проще.

def get_min_max_values(cs, im_height, im_width): 

    min_y = im_height - margin_distance 
    min_x = im_width - margin_distance 

    left_point = (min_y, min_x) 
    top_point = (min_y, min_x) 

    for lvl1 in cs: 
    for lvl2 in lvl1: 
     x, y = lvl2[0] 

     left_point = left_point if x > left_point[1] else (y, x) 
     top_point = top_point if y > top_point[0] else (y, x) 

    left_point[0] = left_point[0] if left_point[0] > margin_distance else margin_distance + 1 
    left_point[1] = left_point[1] if left_point[1] > margin_distance else margin_distance + 1 

    top_point[0] = top_point[0] if top_point[0] > margin_distance else margin_distance + 1 
    top_point[1] = top_point[1] if top_point[1] > margin_distance else margin_distance + 1 


    return (top_point, left_point) 

Теперь давайте посмотрим на результаты:

enter image description here

Вы можете видеть, что все четыре «экстремальные» точки действительно внутри повернутого прямоугольника, но много других пунктов остаются вне из-за ограничение «минимальной площади».Вы должны учитывать все «пограничные» точки, когда вы вычисляете поворотный ограничивающий прямоугольник с минимальным значением, чтобы сделать эту работу правильной.

Часть 2: Решение, которое работает и требует минимальных изменений в коде

После расчета контуров с findContours, вы должны скопировать все эти точки контура в одном массиве, а затем, наконец, вы должны пройти этот массив до convexHull function. Эта функция вычисляет convex hull точек. Затем вы можете использовать эти пункты в качестве входных данных для функции minAreaRect и это то, что вы получите:

enter image description here

Дальнейшее улучшение вашего решения

Я совершенно уверен, что ваш алгоритм может работать намного быстрее, если вы делаете не вычислять контуры вообще. Вместо этого просто используйте пороговые позиции пикселей в качестве входов для функции выпуклого корпуса.

+0

Спасибо за предложение и образец кода. Я попытался экстраполировать и адаптировать оставшуюся часть кода, который вы предлагали, но он продолжает возвращать действительно странные координаты. У меня есть идея о том, как применить свое окончательное предложение, поэтому позвольте мне принять ваш ответ, несмотря на проблемы с запуском этого кода. – MrVocabulary

+0

Я только сейчас заметил, что вы отредактировали сообщение, прежде чем я ответил (не обновлялся). Спасибо за вашу помощь - результат, который вы получили, именно то, что я искал! Однако у меня проблемы с воспроизведением результата - он просто не рисует прямоугольник. Не могли бы вы взглянуть на мою попытку и рассказать мне, что мне не хватает? http://pastebin.com/VEVHgtii – MrVocabulary

+1

Здравствуйте, вам кажется, вам нужно только убедиться, что вы положили * все * точки из * всех * найденных контуров в один массив, который затем передается в качестве аргумента в convexHull. Кстати, не забывайте о моем совете, чтобы избежать использования контуров и использовать вместо них пороговые точки; это упростит работу, и результаты будут одинаковыми. :) – Nejc

Смежные вопросы