使用 http 服务器在 Android 上显示来自连接到树莓派的网络摄像头的图像流

Show image stream from webcam attached to rasberry pi on Android using http server

我和我的团队正在大学从事机器人项目。我们有一个 Rasberry Pi,我们在其上连接了一个网络摄像头并使用 OpenCV 进行图像处理。 Rasberry Pi 进行一些处理并与板载 Arduino 通信,然后 Arduino 继续做一些有用的事情,例如向前移动机器人,转弯等。

该项目还涉及制作一个 Android 应用程序,该应用程序将(除其他外)用于控制机器人并显示安装在机器人上(连接到 Rasberry Pi)的摄像头的画面。我的问题涉及解决在我的 Android 应用程序上显示来自该网络摄像头的图像的问题。

为了交流,我使用 bottlePy (https://bottlepy.org/docs/dev/),一个 WSGI python 框架。这个(在树莓派上)的相关代码是:

from bottle import route, run, template
from bottle import static_file

@route('/static/<filename>')
def server_static(filename):
    return static_file(filename, root='./') 

run(host='0.0.0.0', port=8080)

当我打开 http://ip:8080/static/image.jpg 时,这段代码只是 returns 一张图片。

现在为了获取图像并将其保存到磁盘,以便服务器可以获取它,我在 Pi 上有另一个脚本 运行ning:

import numpy as np
import cv2
import time 

while 1:
    cam = cv2.VideoCapture(1)
    s, img = cam.read() # captures image
    cv2.imwrite("image.jpg",img,[int(cv2.IMWRITE_JPEG_QUALITY),50])
    time.sleep(0.1)

这个脚本可以单独 运行 或者我可以将这段代码移到我的服务器代码中,但是将它移到服务器代码中会出现一些问题,我将在 [=49] 中稍微解释一下=]代码。

android上的流不需要很流畅。但我是这样做的:

我创建了一个 运行nable 并使用处理程序每​​ 100 毫秒调用一次 运行nable:

runnable = object : Runnable {
        override fun run() {
            /* do what you need to do */
            try {
                val performBackgroundTask = DownloadImageTask(findViewById(R.id.imageView))
                // PerformBackgroundTask this class is the class that extends AsynchTask
                performBackgroundTask.execute("http://$id:8080/static/image.jpg")
            } catch (e: Exception) {
                // TODO Auto-generated catch block
            }
            /* and here comes the "trick" */
            handler.postDelayed(this, 100)
        }
    }

我的 AsyncTask 的代码:

class DownloadImageTask(val bmImage: ImageView) : AsyncTask<String,Void,Bitmap>() {

val tag = "DownloadImageTask"

override fun doInBackground(vararg urls: String?): Bitmap? {

    val urldisplay = urls[0]
    var mIcon11: Bitmap? = null
    try {
        val `in` = java.net.URL(urldisplay).openStream()
        if (`in` != null) {
            mIcon11 = BitmapFactory.decodeStream(`in`)
        }
    } catch (e: Exception) {
        Log.e(tag, e.message)
        e.printStackTrace()
    }

    return mIcon11
}

override fun onPostExecute(result: Bitmap?) {
    if (result != null)
        bmImage.setImageBitmap(result)
}
}

所以我只是更改了图像视图的来源。

问题来了,这些东西不同步!我得到部分图像(图像从下到上变黑到某个高度)。我相信这是因为当服务器发送图像时,脚本重写了图像。基本上,我有一个流在工作,但就像波动一样,流的图像不好。

我以为我只会在发送前保存图像,方法是将图像保存代码添加到 server_static() 函数中。但是这样,服务器需要时间来响应,并且在它可以发送图像之前,另一个请求被添加到队列中。然后当我在 Android 应用程序中停止流时,通过

handler.removeCallbacks(runnable)

服务器仍会继续发送图像一段时间,任何其他请求都会延迟。

我需要解决这个问题或完全另一种方法来完成这项工作。请帮助? (对不起,问题太长了)

经过反复试验,我找到了最适合我的解决方案。

首先,为了获得一致的 Jpeg 流,我创建了一个 Mjpeg 流并使其可在本地网络(HTTP 服务器)上访问。 Mjpeg(s) 确保顺序图像的流畅流动。这比将图像保存到本地存储然后通过 HTTP 服务器发送它们要好得多。

可在此处找到 Mjpeg 服务器的示例代码:https://gist.github.com/n3wtron/4624820, another is https://hardsoftlucid.wordpress.com/2013/04/11/mjpeg-server-for-webcam-in-python-with-opencv/ .

上述任一示例中的 python 代码在 Rasberry Pi 上可以是 运行。我更喜欢第二个,因为它允许对流进行多次访问。

现在 Android,我使用 https://github.com/niqdev/ipcam-view。这使得在 Android 上流式传输 mjpeg 流非常容易。但是,我遇到了一些问题。它不适用于所有类型的相机,偶尔会使我的应用程序崩溃。但我确定我可以解决它(更换相机有帮助)。此外,我需要通过单击按钮来启动和停止流。我还没能做到这一点。它开始但仅在我退出 activity 时停止。还有其他项目可以使 Android 的 mjpg 流式传输变得容易,如果我找到更好的项目,我会更新答案。在那之前,这是一个很好的解决方案。

编辑:在 Android 上显示 mjpeg 流的另一种更简单的方法: 当我使用 WebView 时,我得到了最好的结果。要开始直播,请使用

webView.loadUrl("http://localhost:8080/cam.mjpeg")

为了更好地适应视频,在您的 onCreate 函数中:

// All code in Kotlin, for Java, use functions instead of preperty access syntax
webView.settings.loadWithOverviewMode = true;
webView.settings.useWideViewPort = true;