Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Docker for Raspberry Pi + Python Server, not a bug, a suggestion, need Docker improuvement. #1105

Open
8bouledecristal opened this issue Mar 4, 2025 · 0 comments

Comments

@8bouledecristal
Copy link

I am working on Raspberry Pi 5 and OpenFace. I have succed to modify my docker images so that it is working on my Raspberry Pi.
My spec on my Raspberry Pi 5 :

    OS: Debian GNU/Linux 12 (bookworm)
    Kernel: 6.6.74+rpt-rpi-2712
    CPU: Cortex-A76 (4) @ 71% 2400.0000 1500.0000MHz
    Memory: 1474MiB / 8048MiB 

Here is my Dockerfile for my Raspberry Pi. I changed :

  • the image from Ubuntu from Ubuntu:18.04 to arm64v8/ubuntu:18.04
  • In the section == Building OpenFace == I modify the cmake with adding some flags :
    CXX=g++-8 CC=gcc-8 cmake -D CMAKE_BUILD_TYPE=RELEASE \
    -D OpenBLAS_INCLUDE_DIR=/usr/include \
    -D OpenBLAS_LIB=/usr/lib/aarch64-linux-gnu/libopenblas.so \
    -G Ninja .. \
    -DCMAKE_CXX_FLAGS="-march=armv8-a" \
    -DCMAKE_C_FLAGS="-march=armv8-a" && \

I mostly did this with ChatGPT : here is the prompt for explaination : https://chatgpt.com/share/6787e7eb-3a74-8010-865a-a659b565ea1d

# ==================== Building Model Layer ===========================
# This is a little trick to improve caching and minimize rebuild time
# and bandwidth. Note that RUN commands only cache-miss if the prior layers
# miss, or the dockerfile changes prior to this step.
# To update these patch files, be sure to run build with --no-cache
FROM alpine as model_data
RUN apk --no-cache --update-cache add wget
WORKDIR /data/patch_experts

RUN wget -q https://www.dropbox.com/s/7na5qsjzz8yfoer/cen_patches_0.25_of.dat &&\
    wget -q https://www.dropbox.com/s/k7bj804cyiu474t/cen_patches_0.35_of.dat &&\
    wget -q https://www.dropbox.com/s/ixt4vkbmxgab1iu/cen_patches_0.50_of.dat &&\
    wget -q https://www.dropbox.com/s/2t5t1sdpshzfhpj/cen_patches_1.00_of.dat

## ==================== Install Ubuntu Base libs ===========================
## This will be our base image for OpenFace, and also the base for the compiler
## image. We only need packages which are linked

FROM arm64v8/ubuntu:18.04 as ubuntu_base

LABEL maintainer="Michael McDermott <[email protected]>"

ARG DEBIAN_FRONTEND=noninteractive

# todo: minimize this even more
RUN apt-get update -qq  &&\
    apt-get install -qq curl  &&\
    apt-get install -qq --no-install-recommends \
    	libopenblas-dev liblapack-dev \
    	libavcodec-dev libavformat-dev libswscale-dev \
    	libtbb2 libtbb-dev libjpeg-dev \
	libpng-dev libtiff-dev  &&\
	rm -rfv /var/lib/apt/lists/*

RUN apt-get update && apt-get install -y libopenblas-dev

RUN echo OUI
RUN ldconfig -p | grep openblas

## ==================== Build-time dependency libs ======================
## This will build and install opencv and dlib into an additional dummy
## directory, /root/diff, so we can later copy in these artifacts,
## minimizing docker layer size
## Protip: ninja is faster than `make -j` and less likely to lock up system
FROM ubuntu_base as cv_deps

WORKDIR /root/build-dep
ARG DEBIAN_FRONTEND=noninteractive

RUN apt-get update -qq && apt-get install -qq -y \
        cmake ninja-build pkg-config build-essential checkinstall\
        g++-8 &&\
    rm -rf /var/lib/apt/lists/* &&\
    update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-8 800 --slave /usr/bin/g++ g++ /usr/bin/g++-8

##        llvm clang-3.7 libc++-dev libc++abi-dev  \
## ==================== Building dlib ===========================

RUN curl http://dlib.net/files/dlib-19.13.tar.bz2 -LO &&\
    tar xf dlib-19.13.tar.bz2 && \
    rm dlib-19.13.tar.bz2 &&\
    mv dlib-19.13 dlib &&\
    mkdir -p dlib/build &&\
    cd dlib/build &&\
    cmake -DCMAKE_BUILD_TYPE=Release -G Ninja .. &&\
    ninja && \
    ninja install && \
    DESTDIR=/root/diff ninja install &&\
    ldconfig

## ==================== Building OpenCV ======================
ENV OPENCV_VERSION=4.1.0

RUN curl https://github.com/opencv/opencv/archive/${OPENCV_VERSION}.tar.gz -LO &&\
    tar xf ${OPENCV_VERSION}.tar.gz && \
    rm ${OPENCV_VERSION}.tar.gz &&\
    mv opencv-${OPENCV_VERSION} opencv && \
    mkdir -p opencv/build && \
    cd opencv/build && \
    cmake -D CMAKE_BUILD_TYPE=RELEASE \
        -D CMAKE_INSTALL_PREFIX=/usr/local \
        -D WITH_TBB=ON -D WITH_CUDA=OFF \
        -DWITH_QT=OFF -DWITH_GTK=OFF\
        -G Ninja .. && \
    ninja && \
    ninja install &&\
    DESTDIR=/root/diff ninja install

## ==================== Building OpenFace ===========================
FROM cv_deps as openface
WORKDIR /root/openface

COPY ./ ./

COPY --from=model_data /data/patch_experts/* \
    /root/openface/lib/local/LandmarkDetector/model/patch_experts/


RUN mkdir -p build && cd build && \
    CXX=g++-8 CC=gcc-8 cmake -D CMAKE_BUILD_TYPE=RELEASE \
    -D OpenBLAS_INCLUDE_DIR=/usr/include \
    -D OpenBLAS_LIB=/usr/lib/aarch64-linux-gnu/libopenblas.so \
    -G Ninja .. \
    -DCMAKE_CXX_FLAGS="-march=armv8-a" \
    -DCMAKE_C_FLAGS="-march=armv8-a" && \
    ninja && \
    DESTDIR=/root/diff ninja install


## ==================== Streamline container ===========================
## Clean up - start fresh and only copy in necessary stuff
## This shrinks the image from ~8 GB to ~1.6 GB
FROM ubuntu_base as final

WORKDIR /root

# Copy in only necessary libraries
COPY --from=openface /root/diff /

# Since we "imported" the build artifacts, we need to reconfigure ld
RUN ldconfig


## ==================== Python ===========================

WORKDIR /root/openface/opengaze

COPY ./opengaze .

# Install Python and FastAPI dependencies
RUN apt-get update && apt-get install -y python3 python3-pip && \
    pip3 install -r requirements.txt
    # pip3 install --no-cache-dir fastapi uvicorn==0.16.0

# # Copy FastAPI application
# COPY main.py /root/main.py

# # Expose FastAPI port
EXPOSE 8000

I also had to modify the CMakeLists.txt :
from

if (${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU")
    execute_process(COMMAND ${CMAKE_CXX_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION)
    if (GCC_VERSION VERSION_LESS 8.0)
		MESSAGE(FATAL_ERROR "Need a 8.0 or newer GCC compiler. Current GCC: ${GCC_VERSION}")
    else ()
        set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse -msse2 -msse3")
    endif ()
endif ()

to

if (${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU")
    execute_process(COMMAND ${CMAKE_CXX_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION)
    if (GCC_VERSION VERSION_LESS 8.0)
		MESSAGE(FATAL_ERROR "Need a 8.0 or newer GCC compiler. Current GCC: ${GCC_VERSION}")
    else ()
	if(NOT CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64") 
        	set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse -msse2 -msse3")
	endif ()
    endif ()
endif ()

I also add at the end a python app with fastapi where I can send a photo to my docker with HTTP.

from fastapi import FastAPI, File, UploadFile, HTTPException
import uvicorn
import os
import subprocess
import csv
import json

app = FastAPI()

# Ensure the /tmp/openface directory exists
os.makedirs("/tmp/openface", exist_ok=True)

@app.get("/start-record/")
async def start_record() :
    command = []
    return

@app.post("/upload-photo/")
async def upload_photo(file: UploadFile = File(...)):
    
    # Define the path where the file will be saved
    file_path = os.path.join("/tmp/openface", file.filename.split("/")[-1])
    
    try:
        # Save the file to the specified path
        with open(file_path, "wb") as buffer:
            buffer.write(await file.read())
        
        # Check if the file was saved correctly
        if not(os.path.exists(file_path)):
            # return {"message": f"Photo '{file.filename}' was correctly downloaded and saved to /tmp/openface."}
            raise HTTPException(status_code=500, detail="Failed to save the photo.")
        
        # TODO faire le subprocess, importer pandas et renvoyer la réponse sous forme de json ou csv c'est mpoins lourd
        command = ["FeatureExtraction", "-gaze", "-f", file_path, "-of", "/tmp/openface/output.csv"]
        # command = f"FeatureExtraction -gaze -f {file_path} -of /tmp/openface/output.csv"
        # print(command)
        subprocess.check_call(command)
        # print("ok")
        
        csv_path = "/tmp/openface/output.csv"

        # Read the CSV file
        with open(csv_path, 'r') as csvfile:
            reader = csv.DictReader(
                csvfile, delimiter=',', skipinitialspace=True)

            # Filter out the rows that have no gaze data
            out = [{"face_id": row["face_id"],
                    "confidence": row["confidence"],
                    "gaze_angle_x": row["gaze_angle_x"],
                    "gaze_angle_y": row["gaze_angle_y"]
                    }
                   for row in reader]
        
            # print(out)

            # use gaze_data = list(reader) to get all the rows
            gaze_data = list(out)

        # Convert the gaze data to JSON string
        gaze_data_json = json.loads(json.dumps(gaze_data))
        return gaze_data_json
        
        
        # return f"FeatureExtraction -gaze -f {file_path} -of /tmp/openface/output.csv"
        
    except Exception as e:
        print(e)
        raise HTTPException(status_code=500, detail=str(e))

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

I am not an expert on Docker so I am looking for improvement to My Dockerfile.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant