Todo mundo conhece o aparelho usado por médicos para ouvir o coração batendo. É o estetoscópio 🩺. Mas você sabia que usando a câmera do celular também é possível ver o coração batendo? Nesse artigo vou mostrar como fazer isso de forma bem simples usando um pouco de programação de computadores. O resultado é bem legal e é uma ótima atividade para fazer com quem está aprendendo lógica de programação.

Como funciona

O coração pulsa para levar sangue para todas as partes do corpo. Durante esse processo é possível detectar o fluxo sanguíneo passando por partes bem próximas à pele, como a digital do dedo. Ali vai existir uma diferença de cor bem sutil à medida que o sangue vai e vem.

Com a ajuda da lanterna do celular, quando pressiono o dedo contra a câmera, um vídeo gravando essa região vai conseguir capturar esse o fluxo sanguíneo.

Gravei alguns segundos do dedo pressionado contra a câmera do celular. A lanterna ajuda bastante a melhorar o resultado.

Á olho nu, parecem imagens iguais, mas existe uma pequena diferença na intensidade de cor delas. O vídeo inteiro que gravei pode ser visto à seguir.

O código

A ideia é ler cada frame do vídeo e calcular uma média simples dos valores de RGB de cada pixel. Para ajudar a extrair os frames do vídeo, utilizei uma biblioteca chamada JCodec. O código inteiro em linguagem Kotlin é o seguinte:

import org.jcodec.common.io.NIOUtils
import org.jcodec.api.FrameGrab
import org.jcodec.scale.AWTUtil
import java.awt.Color
import java.awt.image.BufferedImage
import java.nio.file.Paths
import java.util.Locale


fun BufferedImage.getColorAverage(): Double {
    var sum = 0.0
    for (x in 0 until getWidth(null)) {
        for (y in 0 until getHeight(null)) {
            val rgb = Color(getRGB(x, y))
            sum += (rgb.red + rgb.green + rgb.blue) / 3.0
        }
    }
    return sum / (getWidth(null) * getHeight(null))
}


fun main() {
    val videoDir = Paths.get(System.getProperty("user.dir")).resolve("src/main/resources")
    val videPath = videoDir.resolve(Paths.get("heart-video.mp4"))
    val grab = FrameGrab.createFrameGrab(NIOUtils.readableChannel(videPath.toFile()))
    generateSequence { grab.nativeFrame }
        .map { AWTUtil.toBufferedImage(it) }
        .map { it.getColorAverage() }
        .forEach {
            println("%.4f".format(Locale.US, it))
        
}

Esse código vai mostrar a média da intensidade de cor de cada frame. O vídeo que gravei tem 864 frames em cerca de 15 segundos. Como resultado, uma sequência com 864 números será apresentada, mais ou menos assim:

...
67.9833
67.7863
67.7662
68.0409
68.2835
68.2312
...

Visualizando os batimentos

Com a sequência de valores, vou jogar ela no Excel para conseguir montar um gráfico de forma bem simples. Também posso usar uma planilha do Google Docs. O resultado é o seguinte:

A captura das imagens não tem tanta precisão, o que faz os valores possuírem algum ruído que fazem o gráfico ter várias áreas intermediárias com altos e baixos. Para solucionar esse problema, apliquei um média móvel nos valores. O gráfico ficou bem mais comportado. Veja:

Conclusão

Programação de computadores está em praticamente tudo. Eu tive a ideia de fazer esse artigo depois de ser apresentado à um aplicativo para Android que faz exatamente isso: filmar o fluxo no dedo e mostrar um gráfico. Usei cerca de 30 minutos fazer tudo relativo ao código (e mais algum tempo escrevendo esse artigo).

Para quem é professor de lógica de programa e introdução à programação, penso que essa é uma ótima atividade. É algo muito próximo do cotidiano, praticamente todo mundo tem um celular, e o resultado pode ser visto imediatamente.

Links

código fonte esse trabalho está disponível no Github e o processamento do vídeo foi auxiliado pelo JCodec.