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

Короткий ролик демонстрирует, как выглядит номер электробуса при съемке с помощью камеры raspberry pi:



ссылка на rutube

Заметно, что присутствует мерцание. Данный эффект не позволяет сделать качественный снимок, на котором будет присутствовать номер транспортного средства. На снимке будет либо видна часть номера, либо он вовсе будет отсутствовать. Как повезет.

Как прочитать номер для дальнейшего распознавания? Первое, что приходит на ум — это попытаться рассинхронизировать камеру с частотой обновления изображения на номерном знаке электробуса. Но что, если эта частота у каждого транспортного средства своя. Да и как это сделать на примере с picamera (предполагается использование одноплатника raspberry pi)?

Оказывается, есть еще один вариант решения задачи. Данный подход, как выяснилось, активно используется в астрономии при «фотографировании» звезд. Чтобы изображение звезды получилось качественнее, в некоторых случаях производится «сложение» снимков.

Продемонстрируем данный эффект на базе raspberry pi и python:

import time
import picamera
import cv2,os
import numpy as np
import glob
from datetime import datetime

frames = 3

def filenames():
    frame = 0
    while frame < frames:
        yield 'photos/image%02d.jpg' % frame
        frame += 1

with picamera.PiCamera(resolution='720p', framerate=30) as camera:
    camera.start_preview(fullscreen=False, window = (100, 20, 640, 480))
    #camera.vflip = True
    camera.rotation = 270
    # Give the camera some warm-up time
    time.sleep(2)
    start = time.time()
    camera.capture_sequence(filenames(), use_video_port=True)
    finish = time.time()
print('Captured %d frames at %.2ffps' % (
    frames,
    frames / (finish - start)))

os.chdir('photos/')

files=[]
for file in glob.glob('*.jpg'):
        #print(file)        
        files.append(file)

x=0

def glue(img1,img2):    
    return cv2.addWeighted(img1, 0.5, img2, 0.5, 0)

for f in range(len(files)-1):
    img1 = cv2.imread(files[x])
    img2 = cv2.imread(files[x+1])
    dst = glue(img1,img2)
    dst+=dst          
    x+=1

cv2.imwrite(f'{str(datetime.now()).split(" ")[1]}.png',dst)
#cv2.imshow('Blended Image',dst)  
#cv2.waitKey(0)
#cv2.destroyAllWindows()

for file in glob.glob('image*.jpg'):
        os.remove(file)       


Как несложно догадаться, основная «магия» происходит в функции glue, объединяющей снимки в нечто новое.



С помощью такого нехитрого подхода можно получать совершенно необычные снимки:



Но и в том числе решить прикладную задачу:



p.s. Возможно, классические методы cv не будут открытием для кого-то. Однако, об их (методах) существовании порой забываешь и начинаешь закапываться в дебри реализации.

Комментарии (18)


  1. engine9
    18.09.2024 09:38
    +4

    Похожая техника, очень необычный результат.


    1. Radisto
      18.09.2024 09:38

      Говорят, лягушки так видят


  1. xpbim3_xpbim3
    18.09.2024 09:38
    +5

    Странно что цветовые каналы выщелкнуло в переполнение.

    Вообще говоря cv тут нафик не нужна, это и в контексте numpy отлично сработает:

    result = (img1*0.5 + img2*0.5)

    result_clipped = np.clip(result, 0, 255).astype(np.uint8)

    Причем я бы кастанул в грейскейл и это сработает даже со стэком n изображений в тензорной форме, например положим что img_stack.shape == (n, 1, y, x) типа np.uint8

    то можно дать:

    result = np.sum(img_stack.astype(int), axis = 0) / n


    1. SquareRootOfZero
      18.09.2024 09:38
      +2

      Странно что цветовые каналы выщелкнуло в переполнение.

      Я, возможно, чего-то не догоняю, конкретно, что автор делает вот в этих вот двух строчках:

      dst = glue(img1,img2)
      dst+=dst

      Сперва в dst кладётся среднее арифметическое двух соседних по времени кадров (ОК), потом содержимое dst удваивается путём сложения самого с собой - видимо, тут и возникает переполнение, ибо как ему тут не возникнуть. Затем цикл уходит на следующую итерацию и значение dst затирается средним двух следующих кадров? Т. е., после завершения цикла пишется удвоенное среднее двух последних, а все предыдущие итерации были мартышкиным трудом?


    1. zoldaten Автор
      18.09.2024 09:38

      @xpbim3_xpbim3
      | Вообще говоря cv тут нафик не нужна, это и в контексте numpy отлично сработает:

      не нужна, если просто сложением заниматься и не делать снимки с камеры. но их приходится делать.

      да и результат, не отличается от того, что предложил SquareRootOfZero -


  1. Zara6502
    18.09.2024 09:38
    +1

    мне кажется можно воспользоваться контролем времени затухания "люминофора" которого нет

    то есть например второй кадр суммируется с первым, у которого понижена яркость например на 25%, третий из 1-2-3 кадров так же -25% и -50%. Таким образом яркие участки будут уходить в белое, а "моргающие" точки в серое.


  1. DimPal
    18.09.2024 09:38
    +3

    А если не суммировать, а взять максимум яркости от двух соседних кадров?


    1. zoldaten Автор
      18.09.2024 09:38

      каким образом ?


      1. DimPal
        18.09.2024 09:38

        Методом попиксельного сложения соседних кадров (с арифметикой насыщения). Если удасться сделать с компенсацией движения, то ещё лучше. Хотя в сферическом случае через-кадрового пропадания светящейся надписи и без компенсации станет лучше.


        1. zoldaten Автор
          18.09.2024 09:38

          я не совсем вас понимаю. в коде можете продемонстрировать ?


          1. DimPal
            18.09.2024 09:38

            Извиняюсь - чего то меня заклилнило, не то написал. Не "попиксельное сложение", а попиксельный максимум из двух соседних кадров.


            1. SquareRootOfZero
              18.09.2024 09:38

              Попиксельный максимум двух соседних кадров будет в коде как-то так:

              import cv2
              
              # two consecutive frames as grayscale
              img1 = cv2.imread("frame1.jpg", cv2.IMREAD_GRAYSCALE)
              img2 = cv2.imread("frame2.jpg", cv2.IMREAD_GRAYSCALE)
              
              cond = img2 > img1      # condition where img2 pixel value is greater
              img1[cond] = img2[cond] # replace img1 pixel with img2 pixel
              cv2.imwrite("maxvalue.jpg", img1)

              Проверить эффект я не смог - не на чем, в видео каждый второй кадр содержит весь номер целиком и ярко, ну, выбрал максимум, ну и чо. Плюс кадры часто сильно смещаются друг относительно друга, а решение проблемы стабилизации автором поста не заявлено. Но на стабильном мерцающем наборе чо б ему не работать. Если цвет цифр всегда один и тот же, можно ещё вместо яркости попробовать брать максимум по hue, наверное.


              1. zoldaten Автор
                18.09.2024 09:38

                взял 2 снимка, идущие подряд. т.е. даже не через кадр и не из середины видеоряда:

                получилось следующее:

                это будет сложнее распознать. а так, идея хорошая.


                1. zoldaten Автор
                  18.09.2024 09:38

                  p.s.

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


  1. SquareRootOfZero
    18.09.2024 09:38

    Два вопроса:

    1. Идея со средневзвешенной яркостью понятна, но какую задачу автор решает, складывая получившуюся картинку саму с собой, по-сути, удваивая яркость (и, за счёт переполнения цветовых каналов, собственно, и получая свои "необычные" снимки)?

    2. Чоза дичь происходит в финальном цикле?

    3. Если распатронить ролик с ютуба на кадры, то там как минимум на каждом втором кадре номер автобуса присутствует целиком, ярко и чётко - это результат сжатия видео или изначально камера так сняла? А то, может, и огород городить не стоило?


    1. zoldaten Автор
      18.09.2024 09:38

      1. При сложении снимков получается снимок, на котором возможно гарантированно детектировать box с номером.

      2. Про дичь: слипаются в безумном танце 3 кадра.

      3. Если "разобрать" видео на кадры, действительно на одном из кадров можно "поймать" box, где более-менее отчетливо виден номер. Однако, для этого нужно: записать видео, разобрать видео на кадры, проанализировать несколько кадров, отбраковывая плохие. Это дорого для одноплатника. Кроме того, это не будет работать, если мерцающая строка длинная, т.е. нужен не только номер, но и надписи и т.п.


      1. SquareRootOfZero
        18.09.2024 09:38

        1. В смысле, удвоить яркость без учёта переполнения - это так и задумано?

        2. Там не три кадра, там первый-второй, потом результат затирается на второй-третий.

        3. Зачем писать видео, если можно просто сделать быстро несколько кадров подряд (вы ж их и так делаете) и выбрать лучший?


        1. zoldaten Автор
          18.09.2024 09:38

          1. а как вы выберете лучший ?