<?xml version="1.0" encoding="utf-8"?> 
<rss version="2.0">

<channel>

<title>Блог об аналитике, визуализации данных, data science и BI, заметки с тегом: skimage</title>
<link>http://test.leftjoin.ru/tags/skimage/</link>
<description></description>
<generator>E2 (v3365; Aegea)</generator>

<item>
<title>Обработка изображения с чеком для поиска QR-кода через библиотеку skimage</title>
<guid isPermaLink="false">44</guid>
<link>http://test.leftjoin.ru/all/obrabotka-izobrazheniya-s-chekom-dlya-poiska-qr-koda/</link>
<comments>http://test.leftjoin.ru/all/obrabotka-izobrazheniya-s-chekom-dlya-poiska-qr-koda/</comments>
<description>
&lt;p&gt;Есть много разных сканеров для QR, но не всегда изображение обладает хорошим качеством. В компьютерном зрении для этого используется Image Pre-processing: предобработка изображения. Сегодня рассмотрим, как средствами библиотеки &lt;span class="inline-code"&gt;scikit-image&lt;/span&gt; помочь QR-сканеру найти код на картинке.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;from matplotlib import pyplot as plt
import skimage
from skimage import util, exposure, io, measure, feature
from scipy import ndimage as ndi
import numpy as np
import cv2&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Проблема&lt;/h2&gt;
&lt;p&gt;Попробуем просканировать чек из материала &lt;a href="http://test.leftjoin.ru/all/sobiraem-dannye-s-chekov-gipermarketov-na-python/" class="nu"&gt;«&lt;u&gt;Собираем данные с чеков гипермаркетов на Python&lt;/u&gt;»&lt;/a&gt;. Прочтём картинку методом &lt;span class="inline-code"&gt;imread&lt;/span&gt; библиотеки &lt;span class="inline-code"&gt;matplotlib&lt;/span&gt; и покажем его на экране:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;img = plt.imread('чек.jpg')
plt.imshow(img)&lt;/code&gt;&lt;/pre&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/1.jpg" width="273" height="252" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Кажется, в такой каше сложно что-либо разобрать. Воспользуемся готовой функцией для чтения чтения QR-кода из библиотеки &lt;span class="inline-code"&gt;opencv&lt;/span&gt;:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;def qr_reader(img):
    detector = cv2.QRCodeDetector()
    data, bbox, _ = detector.detectAndDecode(img)
    if data:
        print(data)
    else:
        print('Ничего не нашлось!')&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;И обратимся к ней, чтобы просканировать наше изображение:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;qr_reader(img)&lt;/code&gt;&lt;/pre&gt;&lt;pre class="e2-text-code"&gt;&lt;code&gt;Ничего не нашлось!&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;И это можно понять: обилие лишних пикселей мешает сканеру распознать здесь QR-код. Тем не менее, мы можем помочь сканеру, указав где находится искомая область.&lt;/p&gt;
&lt;h2&gt;Решение&lt;/h2&gt;
&lt;p&gt;Сделаем так: уберём с картинки всё лишнее, найдём координаты прямоугольника с QR-кодом, чтобы затем передать в функцию &lt;span class="inline-code"&gt;qr_reader&lt;/span&gt; не исходное изображение, а исключительно QR-код. Первым делом уменьшим шум, используя &lt;a href="https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.median_filter.html"&gt;медианный фильтр&lt;/a&gt; и сконвертируем изображение из rgb в gray: QR-код состоит всего из двух цветов, так что работать с остальными нам не нужно.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;image = ndi.median_filter(util.img_as_float(img), size=9)
image = skimage.color.rgb2gray(image)
plt.imshow(image, cmap='gray')&lt;/code&gt;&lt;/pre&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/2.jpg" width="273" height="252" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Медианный фильтр размыл изображение, и разбросанные одинокие пиксели стали менее отчётливыми, а QR теперь выделяется на их фоне. Попробуем применить &lt;a href="https://scikit-image.org/docs/dev/api/skimage.exposure.html"&gt;adjust_gamma&lt;/a&gt; к изображению. Эта функция возводит в степень gamma значение каждого пикселя: чем меньше будет этот параметр — тем меньше будет значение пикселя и тем ближе к белому он будет становиться. Попробуем взять &lt;span class="inline-code"&gt;gamma&lt;/span&gt; за 0.5.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;pores_gamma = exposure.adjust_gamma(image, gamma=0.5)
plt.imshow(pores_gamma, cmap='gray')&lt;/code&gt;&lt;/pre&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/3.jpg" width="273" height="252" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Заметно, что QR стал ещё отчетливее прочего на фото. Воспользуемся этим: все пиксели, значение которых меньше 0.3 сделаем 0, а остальных — 1.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;thresholded = (pores_gamma &amp;lt;= 0.3)
plt.imshow(thresholded, cmap='gray')&lt;/code&gt;&lt;/pre&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/4.jpg" width="273" height="252" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;А теперь воспользуемся &lt;a href="https://scikit-image.org/docs/dev/auto_examples/edges/plot_canny.html"&gt;детектором границ canny&lt;/a&gt; для полученного изображения &lt;span class="inline-code"&gt;thresholded&lt;/span&gt;. Этот оператор сам сглаживает изображение и ищет градиенты: границы находятся там, где градиент принимает максимальное значение. С повышением параметра sigma детектор &lt;span class="inline-code"&gt;canny&lt;/span&gt; перестает замечать менее отчетливые границы.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;edge = feature.canny(thresholded, sigma=6)
plt.imshow(edge)&lt;/code&gt;&lt;/pre&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/5.jpg" width="273" height="252" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Наконец, получим координаты границ: для этого нарисуем контуры. Получаем их методом &lt;span class="inline-code"&gt;find_contours&lt;/span&gt; и рисуем поверх изображения &lt;span class="inline-code"&gt;edge&lt;/span&gt;. Объекты массива &lt;span class="inline-code"&gt;contours&lt;/span&gt; — координаты по осям X и Y.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;contours = measure.find_contours(edge, 0.5)
plt.imshow(edge)
for contour in contours:
    plt.plot(contour[:,1], contour[:,0], linewidth=2)&lt;/code&gt;&lt;/pre&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/6.jpg" width="273" height="252" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Возьмём максимальные и минимальные координаты по X и по Y: это будут границы видимого прямоугольника.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;positions = np.concatenate(contours, axis=0)
min_pos_x = int(min(positions[:,1]))
max_pos_x = int(max(positions[:,1]))
min_pos_y = int(min(positions[:,0]))
max_pos_y = int(max(positions[:,0]))&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Теперь, имея координаты, можем на исходном изображении обвести область с кодом:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;start = (min_pos_x, min_pos_y)
end = (max_pos_x, max_pos_y)
cv2.rectangle(img, start, end, (255, 0, 0), 5)
io.imshow(img)&lt;/code&gt;&lt;/pre&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/8.jpg" width="300" height="280" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Попробуем срезать оригинальное изображение по этим координатам:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;new_img = img[min_pos_y:max_pos_y, min_pos_x:max_pos_x]
plt.imshow(new_img)&lt;/code&gt;&lt;/pre&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/7.jpg" width="311" height="252" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;И передадим новое изображение в функцию &lt;span class="inline-code"&gt;qr_reader&lt;/span&gt;:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;qr_reader(new_img)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Получаем в ответе:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;t=20190320T2303&amp;amp;s=5803.00&amp;amp;fn=9251440300007971&amp;amp;i=141637&amp;amp;fp=4087570038&amp;amp;n=1&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Это то, чего мы и хотели. Конечно, скрипт не будет универсальным, ведь в каждом изображении будут свои недостатки: где-то шума будет больше, где-то фотография размыта, где-то не будет хватать контраста. Поэтому в отдельных случаях потребуется вносить и иные корректировки в изображение. На следующем этапе обработки фотографии мы воспользуемся уже готовой библиотекой.&lt;/p&gt;
</description>
<pubDate>Wed, 03 Jun 2020 12:35:17 +0300</pubDate>
</item>


</channel>
</rss>