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
O código fonte esse trabalho está disponível no Github e o processamento do vídeo foi auxiliado pelo JCodec.