Android Stduio+Opencv+Socket传输视频流到服务器

本文详细介绍了如何在Android Studio中部署OpenCV库,并通过JNI方式接入,实现在Android应用中捕获摄像头视频流,并通过Socket实时传输到服务器。首先,从OpenCV官网下载SDK,然后在Android Studio项目中配置OpenCV模块,包括修改build.gradle文件、添加依赖及设置权限。接着,创建一个Application子类来管理Socket连接,确保长连接在多个界面间共享。在MainActivity中初始化Socket,StartActivity作为主界面,CameraGetActivity用于显示并处理摄像头画面,通过OpenCV的JavaCamera2View获取每一帧图像,Base64编码后通过Socket发送。服务端使用Python的socket和OpenCV接收并显示接收到的图像。最后,文章提供了完整的服务端和客户端代码实现。


前言

近期,由于老师的要求,我们需要实现将Android Studio摄像头获取的视频实时传输到服务端,而在手机端实时显示。我们决定采用Opencv与Socket实现该功能,网上相关教程较少,因此我们决定写篇文章用于记录一下。


一、如何在Android Studio上部署OpenCv

关于OpenCV在Android Studio上部署,网上已经有许多教程了,我们采用直接接入OpenCV的java SDK的方式。(感谢这位大佬的细致介绍)

1.下载opencv

进入OpenCV的官网下载opencv,在界面中选择Android,我下载的是OpenCV4.2.0
选择Android静待5秒即可下载OpenCV for Android

2.Android Studio创建项目

在Android 项目右上角选择SDK Manager - Android SDK - SDK Tools,确保下载了CMake, NDK。
选择SDK Manager
下载CMake, NDK

鉴于以后可能会使用ncnn的原因,我们采用native c++的方式创建了项目。
选择Native C++
此后的选择与参考了大佬的步骤

3.配置OpenCV

创建了项目后,点击File->New->Import Module,
在这里插入图片描述
引入Opencv - android中的java文件夹,我的路径如下

opencv-4.2.0-android-sdk\OpenCV-android-sdk\sdk

出现黄色警报后可以勾选import选项并更改名称为opencv:
在这里插入图片描述
选择Finish后导入了作为Module的OpenCV,此时选择build.gradle(注意,一定是OpenCV的build.gradle),接下来更改

apply plugin: 'com.android.application'

改为:

apply plugin: 'com.android.library'

并删除这一行

defaultConfig {
applicationId “org.opencv”
}

为了避免不必要的错误,尽可能调整app的build.gradle与opencv的build.gradle中

compileSdkVersion 29
buildToolsVersion “29.0.2”

此时,将app 的build.gradle中的

externalNativeBuild {
cmake {
cppFlags “-std=c++14”
}
}

更改为

 externalNativeBuild {
   
   
            cmake {
   
   
                arguments "-DANDROID_STL=c++_shared"
            }

用于连接opencv的共享库。
在最后

dependencies{}

之中加入

implementation project( ':opencv')

对应数值相同,如果仍可能出错,则应调整自己下载的OpenCV或SDK,点击Sync now,等待同步成功。
接下来应在AndroidMainifest.xml中申请摄像头与网络权限。

 <uses-sdk tools:overrideLibrary="android.support.compat, android.arch.lifecycle" />

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.INTERNET" />

    <uses-feature
        android:name="android.hardware.camera"
        android:required="false" />
    <uses-feature
        android:name="android.hardware.camera.autofocus"
        android:required="false" />
    <uses-feature
        android:name="android.hardware.camera.front"
        android:required="false" />
    <uses-feature
        android:name="android.hardware.camera.front.autofocus"
        android:required="false" />

在Andorid 项目目录

app\src\main

下创建文件夹,其名为(为与后续步骤一致,因此此处位置可以按我的方式放置)

jniLibs

并将下载的opencv-android中的libs文件复制到此文件夹下,其目录结构为
在这里插入图片描述
由于动态链接库*.so文件在cmke默认下是并不是按照以上的路径寻找的,因此需要在cpp文件夹下的CMakeLists.txt下加入

set_target_properties(libopencv_java4 PROPERTIES IMPORTED_LOCATION
        ${
   
   CMAKE_SOURCE_DIR}/../jniLibs/libs/${
   
   ANDROID_ABI}/libopencv_java4.so)

其中CMAKE_SOURCE_DIR表示了CMakeList.txt所在的文件夹,同时加入

add_library(libopencv_java4 SHARED IMPORTED)
target_link_libraries( # Specifies the target library.
 					   libopencv_java4 # 链接opencv的so
                       # Links the target library to the log library
                       # included in the NDK.
                       ${
   
   log-lib} )

此时opencv已经可以使用了,我们通过以上步骤可实现用JNI的方式引入了OpenCV。

二、用户端代码实现

1.利用Application实现多界面共享Socket

由于Socket在Activity中创建时,其生命周期与Activity生命周期一致,为实现Socket在不同界面的共享,我们采用了Application的方式。自定义一个Class,创建的java class名为MySocket

package com.example.myapplication;

import android.app.Application;

import java.net.Socket;

public class MySocket extends Application {
   
   
    Socket socket = null;

    public Socket getSocket() {
   
   
        System.out.println(socket);
        return socket;
    }

    public void setSocket(Socket socket) {
   
   
        this.socket = socket;
    }
}

并在AndroidManifest.xml文件中的application标签下添加:

 android:name= ".MySocket"

在后续调用中,Application可以将MySocket的生命周期调整与APP生命周期一致,因此可以实现APP与服务器的长连接。

2.更改MainActivity

在此我们并不打算将MainActivitzy作为主界面,而是用来初始化MySocekt,MainActivity的代码如下:

import androidx.appcompat.app.AppCompatActivity;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;

import java.net.Socket;

public class MainActivity extends Activity {
   
   

    @Override
    protected void onCreate(Bundle savedInstanceState) {
   
   
        super.onCreate(savedInstanceState);
        Thread myThread=new Thread(){
   
   //创建子线程
            @Override
            public void run() {
   
   
                try{
   
   
                    Socket socket = new Socket("127.0.0.1",Integer.parseInt("5050")); //此处可更换自己的服务器IP与端口号
                    ((MySocket)getApplication()).setSocket(socket);//初始化MySocket
                    System.out.println("ok");
                }catch (Exception e){
   
   
                    e.printStackTrace();
                }
            }
        };
        myThread.start();//启动线程
        setContentView(R.layout.activity_main);
        MainActivity.this.startActivity(new Intent(MainActivity.this.getApplicationContext(), StartActivity.class));//此处用于打开主界面
        MainActivity.this.finish();
    }
}

主要注意Socket的初始化,并且Socket要放在子线程中。

2.创建StartActivity做主界面

本段并没有特别的注意事项,只需要设计一个打开摄像头的按钮即可,以下是activity_start.xml的代码

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".StartActivity">
    <LinearLayout
        android:layout_height="match_parent"
        android:layout_width="match_parent"
        android:gravity="center"
        android:layout_weight="1">
        <Button
            android:layout_width="280dp"
            android:layout_height="32dp"
            android:text="测试按钮"
            android:id="@+id/camera"
            android:textColor="#000"
            tools:ignore="InvalidId" />
    </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

其界面代码为

package com.example.myapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class StartActivity extends AppCompatActivity {
   
   
    private Button button;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
   
   
        button = findViewById(R.id.camera);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_start);
        button.setOnClickListener(new View.OnClickListener(){
   
   
            @Override
            public void onClick(View v) {
   
   
                Intent intent = new 
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值