Як усунути дефекти опуклості в квадраті судоку?


193

Я займався цікавим проектом: вирішення судоку з вхідного зображення за допомогою OpenCV (як у захисних окулярах Google тощо). І я виконав завдання, але наприкінці знайшов маленьку проблему, заради якої я прийшов сюди.

Програмування я робив за допомогою Python API OpenCV 2.3.1.

Нижче я зробив:

  1. Прочитайте зображення
  2. Знайдіть контури
  3. Виберіть один з максимальною площею (а також дещо еквівалентний квадрату).
  4. Знайдіть кутові точки.

    наприклад, наведено нижче:

    введіть тут опис зображення

    ( Зауважте тут, що зелена лінія правильно збігається з істинною межею судоку, тому судоку можна правильно викривити . Перевірте наступне зображення)

  5. виверніть зображення до ідеального квадрата

    наприклад зображення:

    введіть тут опис зображення

  6. Виконайте OCR (для якого я використав метод, який я надав у розпізнаванні простих цифр OCR у OpenCV-Python )

І метод спрацював добре.

Проблема:

Перевірте це зображення.

Виконання кроку 4 на цьому зображенні дає результат нижче:

введіть тут опис зображення

Намальована червона лінія - це оригінальний контур, який є справжнім контуром кордону судоку.

Намальована зелена лінія - це приблизний контур, який буде контуром викривленого зображення.

Звичайно, є різниця між зеленою та червоною лінією у верхньому краї судоку. Тому під час викривлення я не отримую початкової межі судоку.

Моє запитання:

Як я можу викривити зображення на правильній межі судоку, тобто червоній лінії АБО як я можу видалити різницю між червоною та зеленою лінією? Чи є метод для цього в OpenCV?


1
Ви робите своє виявлення на основі кутових точок, про які погоджуються червона та зелена лінія. Я не знаю OpenCV, але, мабуть, ви захочете виявити лінії між цими кутовими точками та основою на основі цього.
Дугал

Можливо, змушуйте лінії, що з'єднують кутові точки, збігатися з важкими чорними пікселями на зображенні. Тобто, замість того, щоб зелені лінії просто знаходили пряму між кутовими точками, змушуйте їх переходити важкі чорні пікселі. Я думаю, це значно ускладнить вашу проблему, і я не знаю жодних вбудованих модулів OpenCV, які будуть негайно корисними для вас.
ely

@ Дугал: Я думаю, що намальована зелена лінія є приблизною прямою червоною лінією. значить, це лінія між цими кутовими точками. Коли я перетворюю зелену лінію, я отримую вигнуту червону лінію у верхній частині викривленого зображення. (Сподіваюся, ви розумієте, моє пояснення здається трохи поганим)
Абід Рахман K

@ EMS: Я думаю, що намальована червона лінія знаходиться саме на межі судоку. Але проблема полягає в тому, як викривити зображення саме на межі судоку. (я маю на увазі, проблема полягає в деформації, тобто перетворенні цих вигнутих меж у точний квадрат, як я показав на другому зображенні)
Абід Рахман K

Відповіді:


252

У мене є рішення, яке працює, але вам доведеться самостійно перевести його на OpenCV. Це написано в Mathematica.

Перший крок - регулювання яскравості зображення, розділивши кожен піксель в результаті операції закриття:

src = ColorConvert[Import["http://davemark.com/images/sudoku.jpg"], "Grayscale"];
white = Closing[src, DiskMatrix[5]];
srcAdjusted = Image[ImageData[src]/ImageData[white]]

введіть тут опис зображення

Наступним кроком є ​​пошук зони судоку, щоб я міг проігнорувати (замаскувати) фон. Для цього я використовую підключений аналіз компонентів і вибираю компонент, який має найбільшу опуклу область:

components = 
  ComponentMeasurements[
    ColorNegate@Binarize[srcAdjusted], {"ConvexArea", "Mask"}][[All, 
    2]];
largestComponent = Image[SortBy[components, First][[-1, 2]]]

введіть тут опис зображення

Заповнюючи це зображення, я отримую маску для сітки судоку:

mask = FillingTransform[largestComponent]

введіть тут опис зображення

Тепер я можу використовувати похідний фільтр другого порядку, щоб знайти вертикальні та горизонтальні лінії у двох окремих зображеннях:

lY = ImageMultiply[MorphologicalBinarize[GaussianFilter[srcAdjusted, 3, {2, 0}], {0.02, 0.05}], mask];
lX = ImageMultiply[MorphologicalBinarize[GaussianFilter[srcAdjusted, 3, {0, 2}], {0.02, 0.05}], mask];

введіть тут опис зображення

Я знову використовую підключений аналіз компонентів, щоб витягти лінії сітки з цих зображень. Лінії сітки набагато довші, ніж цифри, тому я можу використовувати довжину супорта, щоб вибрати лише компоненти, пов’язані з сіткою. Сортуючи їх за місцем розташування, я отримую 2x10 зображень масок для кожної з вертикальних / горизонтальних ліній сітки на зображенні:

verticalGridLineMasks = 
  SortBy[ComponentMeasurements[
      lX, {"CaliperLength", "Centroid", "Mask"}, # > 100 &][[All, 
      2]], #[[2, 1]] &][[All, 3]];
horizontalGridLineMasks = 
  SortBy[ComponentMeasurements[
      lY, {"CaliperLength", "Centroid", "Mask"}, # > 100 &][[All, 
      2]], #[[2, 2]] &][[All, 3]];

введіть тут опис зображення

Далі я беру кожну пару вертикальних / горизонтальних ліній сітки, розширюю їх, обчислюю перехрестя пікселів на піксель і обчислюю центр результату. Ці точки є перетинами лінії сітки:

centerOfGravity[l_] := 
 ComponentMeasurements[Image[l], "Centroid"][[1, 2]]
gridCenters = 
  Table[centerOfGravity[
    ImageData[Dilation[Image[h], DiskMatrix[2]]]*
     ImageData[Dilation[Image[v], DiskMatrix[2]]]], {h, 
    horizontalGridLineMasks}, {v, verticalGridLineMasks}];

введіть тут опис зображення

Останнім кроком є ​​визначення двох функцій інтерполяції для відображення X / Y через ці точки та перетворення зображення за допомогою цих функцій:

fnX = ListInterpolation[gridCenters[[All, All, 1]]];
fnY = ListInterpolation[gridCenters[[All, All, 2]]];
transformed = 
 ImageTransformation[
  srcAdjusted, {fnX @@ Reverse[#], fnY @@ Reverse[#]} &, {9*50, 9*50},
   PlotRange -> {{1, 10}, {1, 10}}, DataRange -> Full]

введіть тут опис зображення

Усі операції є основною функцією обробки зображень, тому це повинно бути можливим і в OpenCV. Перетворення зображення на основі сплайну може бути складніше, але я не думаю, що це вам дійсно не потрібно. Можливо, використання перспективної трансформації, яку ви зараз використовуєте для кожної окремої комірки, дасть досить хороші результати.


3
Боже мій !!!!!!!!! Це було дивовижно. Це дійсно справді чудово. Я спробую зробити це в OpenCV. Сподіваюся, ви допоможете мені з деталями щодо певних функцій та термінології ... Дякую.
Абід Рахман К

@arkiaz: Я не є експертом OpenCV, але допоможу, якщо зможу, звичайно.
Нікі

Чи можете ви пояснити, для чого використовується функція "закриття"? що я маю на увазі, що відбувається на задньому плані? У документації сказано, що закриття видаляє шум солі та перцю? Чи закривається фільтр низьких частот?
Абід Рахман К

2
Дивовижна відповідь! Звідки ви взяли ідею поділу шляхом закриття, щоб нормалізувати яскравість зображення? Я намагаюся покращити швидкість цього методу, оскільки поділ з плаваючою комою болісно повільний на мобільних телефонах. Чи є у вас якісь пропозиції? @AbidRahmanK
1 ''

1
@ 1 *: Я думаю, що це називається "коригування білого зображення". Не запитуйте мене, де я читав про це, це стандартний інструмент для обробки зображень. Модель, що стоїть за ідеєю, проста: Кількість світла, відбитого від (Ламбертіанської) поверхні, є якраз яскравістю поверхні, меншою за кількість світла, яке відображатиме біле тіло в тому ж положенні. Оцініть видиму яскравість білого тіла в тому ж положенні, поділіть фактичну яскравість на це, і ви отримаєте яскравість поверхні.
Нікі

209

Відповідь Нікі вирішила мою проблему, але його відповідь була в Mathematica. Тому я подумав, що я мушу дати їй адаптацію OpenCV. Але після впровадження я побачив, що OpenCV-код набагато більший, ніж код математики nikie. Крім того, я не зміг знайти метод інтерполяції, виконаний nikie в OpenCV (хоча це можна зробити за допомогою scipy, я скажу це, коли прийде час.)

1. Попередня обробка зображення (операція закриття)

import cv2
import numpy as np

img = cv2.imread('dave.jpg')
img = cv2.GaussianBlur(img,(5,5),0)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
mask = np.zeros((gray.shape),np.uint8)
kernel1 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(11,11))

close = cv2.morphologyEx(gray,cv2.MORPH_CLOSE,kernel1)
div = np.float32(gray)/(close)
res = np.uint8(cv2.normalize(div,div,0,255,cv2.NORM_MINMAX))
res2 = cv2.cvtColor(res,cv2.COLOR_GRAY2BGR)

Результат:

Результат закриття

2. Пошук площі Судоку та створення зображення маски

thresh = cv2.adaptiveThreshold(res,255,0,1,19,2)
contour,hier = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)

max_area = 0
best_cnt = None
for cnt in contour:
    area = cv2.contourArea(cnt)
    if area > 1000:
        if area > max_area:
            max_area = area
            best_cnt = cnt

cv2.drawContours(mask,[best_cnt],0,255,-1)
cv2.drawContours(mask,[best_cnt],0,0,2)

res = cv2.bitwise_and(res,mask)

Результат:

введіть тут опис зображення

3. Знаходження вертикальних ліній

kernelx = cv2.getStructuringElement(cv2.MORPH_RECT,(2,10))

dx = cv2.Sobel(res,cv2.CV_16S,1,0)
dx = cv2.convertScaleAbs(dx)
cv2.normalize(dx,dx,0,255,cv2.NORM_MINMAX)
ret,close = cv2.threshold(dx,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
close = cv2.morphologyEx(close,cv2.MORPH_DILATE,kernelx,iterations = 1)

contour, hier = cv2.findContours(close,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
for cnt in contour:
    x,y,w,h = cv2.boundingRect(cnt)
    if h/w > 5:
        cv2.drawContours(close,[cnt],0,255,-1)
    else:
        cv2.drawContours(close,[cnt],0,0,-1)
close = cv2.morphologyEx(close,cv2.MORPH_CLOSE,None,iterations = 2)
closex = close.copy()

Результат:

введіть тут опис зображення

4. Пошук горизонтальних ліній

kernely = cv2.getStructuringElement(cv2.MORPH_RECT,(10,2))
dy = cv2.Sobel(res,cv2.CV_16S,0,2)
dy = cv2.convertScaleAbs(dy)
cv2.normalize(dy,dy,0,255,cv2.NORM_MINMAX)
ret,close = cv2.threshold(dy,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
close = cv2.morphologyEx(close,cv2.MORPH_DILATE,kernely)

contour, hier = cv2.findContours(close,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
for cnt in contour:
    x,y,w,h = cv2.boundingRect(cnt)
    if w/h > 5:
        cv2.drawContours(close,[cnt],0,255,-1)
    else:
        cv2.drawContours(close,[cnt],0,0,-1)

close = cv2.morphologyEx(close,cv2.MORPH_DILATE,None,iterations = 2)
closey = close.copy()

Результат:

введіть тут опис зображення

Звичайно, це не так добре.

5. Пошук точок сітки

res = cv2.bitwise_and(closex,closey)

Результат:

введіть тут опис зображення

6. Виправлення дефектів

Тут nikie робить якусь інтерполяцію, про яку я не маю великих знань. І я не зміг знайти жодної відповідної функції для цього OpenCV. (може, воно там, я не знаю).

Ознайомтеся з цим SOF, де пояснюється, як це зробити за допомогою SciPy, який я не хочу використовувати: Перетворення зображення у OpenCV

Отже, тут я взяв 4 кути кожного підквадрату і застосував основи "Перспектива" до кожного.

Для цього спочатку ми знаходимо центроїди.

contour, hier = cv2.findContours(res,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
centroids = []
for cnt in contour:
    mom = cv2.moments(cnt)
    (x,y) = int(mom['m10']/mom['m00']), int(mom['m01']/mom['m00'])
    cv2.circle(img,(x,y),4,(0,255,0),-1)
    centroids.append((x,y))

Але отримані центроїди не будуть сортовані. Перегляньте зображення нижче, щоб побачити їх замовлення:

введіть тут опис зображення

Тож ми їх сортуємо зліва направо, зверху вниз.

centroids = np.array(centroids,dtype = np.float32)
c = centroids.reshape((100,2))
c2 = c[np.argsort(c[:,1])]

b = np.vstack([c2[i*10:(i+1)*10][np.argsort(c2[i*10:(i+1)*10,0])] for i in xrange(10)])
bm = b.reshape((10,10,2))

Тепер дивіться нижче їх замовлення:

введіть тут опис зображення

Нарешті ми застосовуємо трансформацію і створюємо нове зображення розміром 450x450.

output = np.zeros((450,450,3),np.uint8)
for i,j in enumerate(b):
    ri = i/10
    ci = i%10
    if ci != 9 and ri!=9:
        src = bm[ri:ri+2, ci:ci+2 , :].reshape((4,2))
        dst = np.array( [ [ci*50,ri*50],[(ci+1)*50-1,ri*50],[ci*50,(ri+1)*50-1],[(ci+1)*50-1,(ri+1)*50-1] ], np.float32)
        retval = cv2.getPerspectiveTransform(src,dst)
        warp = cv2.warpPerspective(res2,retval,(450,450))
        output[ri*50:(ri+1)*50-1 , ci*50:(ci+1)*50-1] = warp[ri*50:(ri+1)*50-1 , ci*50:(ci+1)*50-1].copy()

Результат:

введіть тут опис зображення

Результат майже такий же, як і nikie, але довжина коду велика. Можливо, кращі методи доступні там, але до цього часу це працює нормально.

З повагою ARK.


4
"Я вважаю за краще збій моєї програми, ніж отримання неправильних відповідей." <- Я також згоден на це 100%
Віктор Сер

Дякую, справжню відповідь дає Нікі. Але це було в математиці, тому я просто перетворив його на OpenCV. Тож справжня відповідь отримала достатньо відгуків, я думаю
Абід Рахман К

Ах, не бачив, що ви також розмістили запитання :)
Віктор Шер

Так. Питання також моє. Моя відповідь і відповідь відрізняються лише наприкінці. Він отримав якусь функцію інтеполяції в математиці, яка не є numpy або opencv (але вона є в Scipy, але я не хотів тут використовувати Scipy)
Абід Рахман K

Я отримую помилку: вихід [ri * 50: (ri + 1) * 50-1, ci * 50: (ci + 1) * 50-1] = warp [ri * 50: (ri + 1) * 50- 1, ci * 50: (ci + 1) * 50-1] .копія TypeError: long () аргументом повинен бути рядок або число, а не '
вбудований_функція_ або_метод

6

Ви можете спробувати використати якесь сіткове моделювання вашої довільної деформації. А оскільки судоку вже є сіткою, це не повинно бути занадто важким.

Таким чином, ви можете спробувати виявити межі кожного 3х3 субрегіону, а потім обернути кожну область окремо. Якщо виявлення буде успішним, воно дасть вам кращий наближення.


1

Хочу додати, що вищевказаний метод працює лише тоді, коли дошка судоку стоїть прямо, інакше тест на співвідношення висоти / ширини (або навпаки) тест на співвідношення висоти / ширини, швидше за все, не вдасться, і ви не зможете виявити краї судоку. (Я також хочу додати, що якщо лінії, не перпендикулярні рамкам зображення, операції з розмітки (dx і dy) все ще працюватимуть, оскільки лінії все одно матимуть ребра щодо обох осей.)

Щоб мати можливість виявляти прямі лінії, вам слід працювати над контурним або піксельним аналізом, таким як contourArea / limitingRectArea, лівий верхній і нижній правий точки ...

Редагувати: мені вдалося перевірити, чи утворює набір контурів лінію чи ні, застосувавши лінійну регресію та перевіривши помилку. Однак лінійна регресія виконується погано, коли нахил лінії занадто великий (тобто> 1000) або дуже близький до 0. Тому застосування тесту на співвідношення вище (у більшості схвалених відповідей) перед лінійною регресією є логічним і працює для мене.


1

Для видалення невизначених кутів я застосував гамма-корекцію зі значенням гамми 0,8.

Перед корекцією гамми

Намальовано червоне коло, щоб показати пропущений куточок.

Після корекції гамми

Код:

gamma = 0.8
invGamma = 1/gamma
table = np.array([((i / 255.0) ** invGamma) * 255
                  for i in np.arange(0, 256)]).astype("uint8")
cv2.LUT(img, table, img)

Це додатково до відповіді Абіда Рахмана, якщо відсутні деякі кутові моменти.


0

Я подумав, що це чудовий пост і чудове рішення ARK; дуже добре викладений і пояснений.

Я працював над подібною проблемою і будував всю справу. Були деякі зміни (тобто xrange для діапазону, аргументи в cv2.findContours), але це повинно вийти з поля (Python 3.5, Anaconda).

Це компіляція елементів, наведених вище, з деяким пропущеним кодом (тобто маркування точок).

'''

/programming/10196198/how-to-remove-convexity-defects-in-a-sudoku-square

'''

import cv2
import numpy as np

img = cv2.imread('test.png')

winname="raw image"
cv2.namedWindow(winname)
cv2.imshow(winname, img)
cv2.moveWindow(winname, 100,100)


img = cv2.GaussianBlur(img,(5,5),0)

winname="blurred"
cv2.namedWindow(winname)
cv2.imshow(winname, img)
cv2.moveWindow(winname, 100,150)

gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
mask = np.zeros((gray.shape),np.uint8)
kernel1 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(11,11))

winname="gray"
cv2.namedWindow(winname)
cv2.imshow(winname, gray)
cv2.moveWindow(winname, 100,200)

close = cv2.morphologyEx(gray,cv2.MORPH_CLOSE,kernel1)
div = np.float32(gray)/(close)
res = np.uint8(cv2.normalize(div,div,0,255,cv2.NORM_MINMAX))
res2 = cv2.cvtColor(res,cv2.COLOR_GRAY2BGR)

winname="res2"
cv2.namedWindow(winname)
cv2.imshow(winname, res2)
cv2.moveWindow(winname, 100,250)

 #find elements
thresh = cv2.adaptiveThreshold(res,255,0,1,19,2)
img_c, contour,hier = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)

max_area = 0
best_cnt = None
for cnt in contour:
    area = cv2.contourArea(cnt)
    if area > 1000:
        if area > max_area:
            max_area = area
            best_cnt = cnt

cv2.drawContours(mask,[best_cnt],0,255,-1)
cv2.drawContours(mask,[best_cnt],0,0,2)

res = cv2.bitwise_and(res,mask)

winname="puzzle only"
cv2.namedWindow(winname)
cv2.imshow(winname, res)
cv2.moveWindow(winname, 100,300)

# vertical lines
kernelx = cv2.getStructuringElement(cv2.MORPH_RECT,(2,10))

dx = cv2.Sobel(res,cv2.CV_16S,1,0)
dx = cv2.convertScaleAbs(dx)
cv2.normalize(dx,dx,0,255,cv2.NORM_MINMAX)
ret,close = cv2.threshold(dx,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
close = cv2.morphologyEx(close,cv2.MORPH_DILATE,kernelx,iterations = 1)

img_d, contour, hier = cv2.findContours(close,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
for cnt in contour:
    x,y,w,h = cv2.boundingRect(cnt)
    if h/w > 5:
        cv2.drawContours(close,[cnt],0,255,-1)
    else:
        cv2.drawContours(close,[cnt],0,0,-1)
close = cv2.morphologyEx(close,cv2.MORPH_CLOSE,None,iterations = 2)
closex = close.copy()

winname="vertical lines"
cv2.namedWindow(winname)
cv2.imshow(winname, img_d)
cv2.moveWindow(winname, 100,350)

# find horizontal lines
kernely = cv2.getStructuringElement(cv2.MORPH_RECT,(10,2))
dy = cv2.Sobel(res,cv2.CV_16S,0,2)
dy = cv2.convertScaleAbs(dy)
cv2.normalize(dy,dy,0,255,cv2.NORM_MINMAX)
ret,close = cv2.threshold(dy,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
close = cv2.morphologyEx(close,cv2.MORPH_DILATE,kernely)

img_e, contour, hier = cv2.findContours(close,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)

for cnt in contour:
    x,y,w,h = cv2.boundingRect(cnt)
    if w/h > 5:
        cv2.drawContours(close,[cnt],0,255,-1)
    else:
        cv2.drawContours(close,[cnt],0,0,-1)

close = cv2.morphologyEx(close,cv2.MORPH_DILATE,None,iterations = 2)
closey = close.copy()

winname="horizontal lines"
cv2.namedWindow(winname)
cv2.imshow(winname, img_e)
cv2.moveWindow(winname, 100,400)


# intersection of these two gives dots
res = cv2.bitwise_and(closex,closey)

winname="intersections"
cv2.namedWindow(winname)
cv2.imshow(winname, res)
cv2.moveWindow(winname, 100,450)

# text blue
textcolor=(0,255,0)
# points green
pointcolor=(255,0,0)

# find centroids and sort
img_f, contour, hier = cv2.findContours(res,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
centroids = []
for cnt in contour:
    mom = cv2.moments(cnt)
    (x,y) = int(mom['m10']/mom['m00']), int(mom['m01']/mom['m00'])
    cv2.circle(img,(x,y),4,(0,255,0),-1)
    centroids.append((x,y))

# sorting
centroids = np.array(centroids,dtype = np.float32)
c = centroids.reshape((100,2))
c2 = c[np.argsort(c[:,1])]

b = np.vstack([c2[i*10:(i+1)*10][np.argsort(c2[i*10:(i+1)*10,0])] for i in range(10)])
bm = b.reshape((10,10,2))

# make copy
labeled_in_order=res2.copy()

for index, pt in enumerate(b):
    cv2.putText(labeled_in_order,str(index),tuple(pt),cv2.FONT_HERSHEY_DUPLEX, 0.75, textcolor)
    cv2.circle(labeled_in_order, tuple(pt), 5, pointcolor)

winname="labeled in order"
cv2.namedWindow(winname)
cv2.imshow(winname, labeled_in_order)
cv2.moveWindow(winname, 100,500)

# create final

output = np.zeros((450,450,3),np.uint8)
for i,j in enumerate(b):
    ri = int(i/10) # row index
    ci = i%10 # column index
    if ci != 9 and ri!=9:
        src = bm[ri:ri+2, ci:ci+2 , :].reshape((4,2))
        dst = np.array( [ [ci*50,ri*50],[(ci+1)*50-1,ri*50],[ci*50,(ri+1)*50-1],[(ci+1)*50-1,(ri+1)*50-1] ], np.float32)
        retval = cv2.getPerspectiveTransform(src,dst)
        warp = cv2.warpPerspective(res2,retval,(450,450))
        output[ri*50:(ri+1)*50-1 , ci*50:(ci+1)*50-1] = warp[ri*50:(ri+1)*50-1 , ci*50:(ci+1)*50-1].copy()

winname="final"
cv2.namedWindow(winname)
cv2.imshow(winname, output)
cv2.moveWindow(winname, 600,100)

cv2.waitKey(0)
cv2.destroyAllWindows()
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.