05.07.2019       Выпуск 289 (01.07.2019 - 07.07.2019)       Статьи

Преобразуем изображение в звук — что можно услышать?

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

Читать>>




Экспериментальная функция:

Ниже вы видите текст статьи по ссылке. По нему можно быстро понять ссылка достойна прочтения или нет

Просим обратить внимание, что текст по ссылке и здесь может не совпадать.

Привет Хабр.

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

а вдруг украдут идею за миллион

), но сама концепция показалась интересной. Имея некоторый опыт обработки сигналов, я решил поэкспериментировать самостоятельно.

Что из этого получилось, подробности и примеры файлов под катом.

Преобразуем 2D в 1D

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

кривой Гильберта

.

Она по своей сути похожа на фрактал, и идея в том, что при увеличении разрешения изображения, относительное расположение объектов не меняется (если объект был в верхнем левом углу картинки, то он

останется там же

). Различные размерности кривых Гильберта могут дать нам разные изображения: 32x32 для N=5, 64x64 для N=6, и так далее. «Обходя» изображение по этой кривой, мы получаем линию, одномерный объект.

Следующий вопрос это размер картинки. Интуитивно хочется взять изображение побольше, но тут есть большое «но»: даже картинка 512х512, это 262144 точек. Если преобразовать каждую точку в звуковой импульс, то при частоте дискретизации 44100, мы получим последовательность длиной в целых 6 секунд, а это слишком долго — изображения должны обновляться быстро, например с использованием web-камеры. Делать частоту дискретизации выше бесмысленно, мы получим ультразвуковые частоты, неслышимые ухом (хотя для совы или летучей мыши может и пойдет). В итоге

методом научного тыка

было выбрано разрешение 128х128, которое даст импульсы длиной 0.37c — с одной стороны, это достаточно быстро чтобы ориентироваться в реальном времени, с другой вполне достаточно, чтобы уловить на слух какие-то изменения в форме сигнала.

Обработка изображения

Первым шагом мы загружаем изображение, преобразуем его в ч/б и масштабируем до нужного размера. Размер изображения зависит от размерности кривой Гильберта.

from PIL import Image
from hilbertcurve.hilbertcurve import HilbertCurve
import numpy as np
from scipy.signal import butter, filtfilt


# Create Hilbert curve
dimension = 7
hilbert = HilbertCurve(dimension, n=2)
print("Hilbert curve dimension:", dimension)  # Maximum distance along curve
print("Max_dist:", hilbert.max_h)  # Maximum distance along curve
print("Max_coord:", hilbert.max_x)  # Maximum coordinate value in any dimension

# Load PIL image
f_name = "image01.png"
img = Image.open(f_name)
width, height = img.size
out_size = hilbert_curve.max_x + 1
if width != out_size:
    img = img.resize((out_size, out_size), Image.ANTIALIAS)

# Get image as grayscale numpy array
img_grayscale = img.convert(mode='L')
img_data = np.array(img_grayscale)

Следующим шагом формируем звуковую волну. Тут разумеется, может быть великое множество алгоритмов и ноухау, для теста я просто взял яркостную составляющую. Разумеется, наверняка есть способы лучше.

width, height = img_grayscale.size

sound_data = np.zeros(width*height)
for ii in range(width*height):
    coord_x, coord_y = hilbert_curve.coordinates_from_distance(ii)
    pixel_l = img_data[coord_x][coord_y]

    # Inverse colors (paper-like, white = 0, black = 255)
    pixel_l = 255 - pixel_l

    # Adjust values 0..255 to 0..8192
    ampl = pixel_l*32

    sound_data[ii] = ampl

Из кода, надеюсь, все понятно. Функция coordinates_from_distance делает за нас всю работу по преобразованию координат (х, у) в расстояние на кривой Гильберта, значение яркости L мы инвертируем и преобразуем в цвет.

Это еще не все. Т.к. на изображении могут быть большие блоки одного цвета, это может привести к появлению в звуке «dc-компоненты» — длинного ряда отличных от нуля значений, например [100,100,100,...]. Чтобы их убрать, применим к нашему массиву high-pass filter (

фильтр Баттерворта

) с частотой среза 50Гц (совпадение с частотой сети случайно). Синтез фильтров есть в библиотеке scipy, которым мы и воспользуемся.

def butter_highpass(cutoff, fs, order=5):
    nyq = 0.5 * fs
    normal_cutoff = cutoff / nyq
    b, a = butter(order, normal_cutoff, btype='high', analog=False)
    return b, a

def butter_highpass_filter(data, cutoff, fs, order=5):
    b, a = butter_highpass(cutoff, fs, order)
    y = filtfilt(b, a, data)
    return y

# Apply high pass filter to remove dc component

cutoff_hz = 50
sample_rate = 44100
order = 5
wav_data = butter_highpass_filter(sound_data, cutoff_hz, sample_rate, order)

Последним шагом сохраним изображение. Т.к. длина одного импульса короткая, мы повторяем его 10 раз, это будет на слух более приближено к реальному повторяющемуся изображению, например с веб-камеры.

# Clip data to int16 range
sound_output = np.clip(wav_data, -32000, 32000).astype(np.int16)

# Save
repeat = 10
sound_output_ntimes = np.tile(sound_output, repeat)
wav_name = "ouput.wav"
scipy.io.wavfile.write(wav_name, sample_rate, sound_output_ntimes)

Результаты

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

Тест-1

Изображению соответствует такой звуковой сигнал:

WAV:

cloud.mail.ru/public/nt2R/2kwBvyRup Тест-2

Идея этого теста — сравнить «звучание» объекта другой формы. Звуковой сигнал:

WAV:

cloud.mail.ru/public/2rLu/4fCNRxCG2

Можно заметить, что звучание действительно другое, и на слух разница есть.

Тест-3

Идея теста — проверить объект меньшего размера. Звуковой сигнал:

WAV:

cloud.mail.ru/public/5GLV/2HoCHvoaY

В принципе, чем меньше размеры объекта, тем меньше будет «всплесков» в звуке, так что зависимость тут вполне прямая.

Правка:

Как подсказали в комментариях, можно использовать преобразование Фурье для непосредственной конвертации картинки в звук. Сделанный по-быстрому тест показывает такие результаты (картинки те же):

Тест-1:

cloud.mail.ru/public/2C5Z/5MEQ8Swjo

Тест-2:

cloud.mail.ru/public/2dxp/3sz8mjAib

Тест-3:

cloud.mail.ru/public/3NjJ/ZYrfdTYrk

Тесты звучат интересно, по крайней мере, для маленького и большого квадратов (файлы 1 и 3) разница на слух хорошо ощутима. А вот форма фигур (1 и 2) практически не различается, так что тут тоже есть над чем подумать. Но в целом, звучание полученное с помощью FFT, на слух мне нравится больше.

Заключение

Данный тест, разумеется, не диссертация, а просто proof of concept, сделанный за несколько часов свободного времени. Но даже так, оно в принципе работает, и разницу ощущать на слух вполне реально. Я не знаю, можно ли научиться ориентироваться в пространстве по таким звукам, гипотетически наверно можно после некоторой тренировки. Хотя тут огромное поле для улучшений и экспериментов, например, можно использовать стереозвук, что позволит лучше разделять объекты с разных сторон, можно экспериментировать с другими способами конвертации изображения в звук, например, кодировать цвет разными частотами, и пр. И наконец, перспективным тут является использование 3d-камер, способных воспринимать глубину (увы, такой камеры в наличии нет). Кстати, с помощью несложного кода на OpenCV, вышеприведенный алгоритм можно адаптировать к использованию web-камеры, что позволит экспериментировать с динамическими изображениями.

Ну и как обычно, всем удачных экспериментов.






Разместим вашу рекламу

Пиши: mail@pythondigest.ru

Нашли опечатку?

Выделите фрагмент и отправьте нажатием Ctrl+Enter.

Система Orphus