turn this to use tensorflow lite and show me how to install tensorflow lite : #include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <cmath>
#include <algorithm>
#include <filesystem> // Requires C++17
#include <opencv2/opencv.hpp>

// TensorFlow headers
#include <tensorflow/core/public/session.h>
#include <tensorflow/core/protobuf/meta_graph.pb.h>
#include <tensorflow/core/platform/env.h>
#include <tensorflow/core/framework/tensor.h>

// Namespace aliases
namespace tf = tensorflow;
namespace fs = std::filesystem;

// Function declarations
tf::Tensor make_kernel(const std::vector<float>& data, int rows, int cols);
tf::Tensor simple_conv(tf::Tensor& x, tf::Tensor& k, std::unique_ptr<tf::Session>& session);
tf::Tensor gradientx(tf::Tensor& x, std::unique_ptr<tf::Session>& session);
tf::Tensor gradienty(tf::Tensor& x, std::unique_ptr<tf::Session>& session);
bool checkMatch(const std::vector<int>& lineset);
std::vector<int> pruneLines(std::vector<int>& lineset, int image_dim, int margin = 20);
std::vector<float> skeletonize_1d(const std::vector<float>& arr);
void getChessLines(const std::vector<float>& hdx, const std::vector<float>& hdy,
                   float hdx_thresh, float hdy_thresh, cv::Size image_shape,
                   std::vector<int>& lines_x, std::vector<int>& lines_y, bool& is_match);
std::vector<cv::Mat> getChessTiles(cv::Mat& a, std::vector<int>& lines_x, std::vector<int>& lines_y);
void process_image(const std::string& image_path, const std::string& output_dir,
                   std::unique_ptr<tf::Session>& session);

int main(int argc, char** argv) {
    // Initialize TensorFlow session
    tf::SessionOptions options;
    std::unique_ptr<tf::Session> session(tf::NewSession(options));
    if (!session) {
        std::cerr << "Could not create TensorFlow session." << std::endl;
        return -1;
    }

    // Specify input and output directories
    std::string input_folder = "train_images"; // Replace with your input folder
    std::string output_folder = "tiles";       // Replace with your desired output folder

    // Create output directory if it doesn't exist
    if (!fs::exists(output_folder)) {
        fs::create_directory(output_folder);
    }

    // Process images in the input folder
    for (const auto& entry : fs::directory_iterator(input_folder)) {
        if (entry.is_regular_file()) {
            std::string filename = entry.path().filename().string();
            std::string extension = entry.path().extension().string();
            if (extension == ".png" || extension == ".jpg" || extension == ".jpeg") {
                std::string image_path = entry.path().string();
                process_image(image_path, output_folder, session);
            }
        }
    }

    // Close the TensorFlow session
    session->Close();

    return 0;
}

// Function implementations

// 1. Create TensorFlow kernel tensor from data
tf::Tensor make_kernel(const std::vector<float>& data, int rows, int cols) {
    tf::Tensor kernel(tf::DT_FLOAT, tf::TensorShape({rows, cols, 1, 1}));
    auto kernel_mapped = kernel.tensor<float, 4>();

    for (int i = 0; i < rows; ++i) {
        for (int j = 0; j < cols; ++j) {
            kernel_mapped(i, j, 0, 0) = data[i * cols + j];
        }
    }
    return kernel;
}

// 2. Perform simple convolution using TensorFlow session
tf::Tensor simple_conv(tf::Tensor& x, tf::Tensor& k, std::unique_ptr<tf::Session>& session) {
    // Create the graph
    tf::GraphDef graph_def;
    tf::Scope root = tf::Scope::NewRootScope();

    // Input placeholder
    auto input = tf::ops::Placeholder(root.WithOpName("input"), tf::DT_FLOAT);

    // Convolution operation
    std::vector<int> strides = {1, 1, 1, 1};
    auto conv = tf::ops::DepthwiseConv2dNative(root.WithOpName("conv"), input, k, strides, "SAME");

    // Initialize variables
    tf::GraphDef graph;
    root.ToGraphDef(&graph);

    // Run the session
    std::vector<tf::Tensor> outputs;
    TF_CHECK_OK(session->Create(graph));
    TF_CHECK_OK(session->Run({{"input", x}}, {"conv"}, {}, &outputs));

    // Remove the created graph to avoid conflicts in subsequent calls
    session->Close();
    session.reset(tf::NewSession(tf::SessionOptions()));

    // Return the result tensor
    return outputs[0];
}

// 3. Compute gradient in x-direction
tf::Tensor gradientx(tf::Tensor& x, std::unique_ptr<tf::Session>& session) {
    std::vector<float> data = {-1., 0., 1., -1., 0., 1., -1., 0., 1.};
    tf::Tensor kernel = make_kernel(data, 3, 3);
    return simple_conv(x, kernel, session);
}

// 4. Compute gradient in y-direction
tf::Tensor gradienty(tf::Tensor& x, std::unique_ptr<tf::Session>& session) {
    std::vector<float> data = {-1., -1., -1., 0., 0., 0., 1., 1., 1.};
    tf::Tensor kernel = make_kernel(data, 3, 3);
    return simple_conv(x, kernel, session);
}

// 5. Check if line set matches expected pattern
bool checkMatch(const std::vector<int>& lineset) {
    std::vector<int> linediff(lineset.size() - 1);
    for (size_t i = 0; i < lineset.size() - 1; ++i) {
        linediff[i] = lineset[i + 1] - lineset[i];
    }
    int x = 0;
    int cnt = 0;
    for (const auto& line : linediff) {
        if (std::abs(line - x) < 5) {
            cnt++;
        } else {
            cnt = 0;
            x = line;
        }
    }
    return cnt == 5;
}

// 6. Prune lines to remove margins and irregularities
std::vector<int> pruneLines(std::vector<int>& lineset, int image_dim, int margin = 20) {
    // Remove lines near the margins
    lineset.erase(std::remove_if(lineset.begin(), lineset.end(),
                                 [margin, image_dim](int x) {
                                     return (x <= margin) || (x >= image_dim - margin);
                                 }),
                  lineset.end());

    if (lineset.empty()) return lineset;

    std::vector<int> linediff(lineset.size() - 1);
    for (size_t i = 0; i < lineset.size() - 1; ++i) {
        linediff[i] = lineset[i + 1] - lineset[i];
    }
    int x = 0;
    int cnt = 0;
    int start_pos = 0;
    for (size_t i = 0; i < linediff.size(); ++i) {
        int line = linediff[i];
        if (std::abs(line - x) < 5) {
            cnt++;
            if (cnt == 5) {
                int end_pos = i + 2;
                return std::vector<int>(lineset.begin() + start_pos, lineset.begin() + end_pos);
            }
        } else {
            cnt = 0;
            x = line;
            start_pos = i;
        }
    }
    return lineset;
}

// 7. Skeletonize a 1D array
std::vector<float> skeletonize_1d(const std::vector<float>& arr) {
    std::vector<float> _arr = arr;
    for (size_t i = 0; i < _arr.size() - 1; ++i) {
        if (_arr[i] <= _arr[i + 1]) {
            _arr[i] = 0;
        }
    }
    for (size_t i = _arr.size() - 1; i > 0; --i) {
        if (_arr[i - 1] > _arr[i]) {
            _arr[i] = 0;
        }
    }
    return _arr;
}

// 8. Get chessboard lines from Hough transforms
void getChessLines(const std::vector<float>& hdx, const std::vector<float>& hdy,
                   float hdx_thresh, float hdy_thresh, cv::Size image_shape,
                   std::vector<int>& lines_x, std::vector<int>& lines_y, bool& is_match) {
    // Generate Gaussian window
    int window_size = 21;
    double sigma = 4.0;
    std::vector<double> gausswin(window_size);
    int half_size = window_size / 2;
    double sum = 0.0;
    for (int i = 0; i < window_size; ++i) {
        gausswin[i] = std::exp(-0.5 * std::pow((i - half_size) / sigma, 2));
        sum += gausswin[i];
    }
    // Normalize
    for (int i = 0; i < window_size; ++i) {
        gausswin[i] /= sum;
    }

    // Blur signals using convolution with Gaussian window
    std::vector<float> blur_x(hdx.size());
    std::vector<float> blur_y(hdy.size());
    std::vector<float> hdx_thresh_binary(hdx.size());
    std::vector<float> hdy_thresh_binary(hdy.size());

    for (size_t i = 0; i < hdx.size(); ++i) {
        hdx_thresh_binary[i] = hdx[i] > hdx_thresh ? 1.0f : 0.0f;
    }
    for (size_t i = 0; i < hdy.size(); ++i) {
        hdy_thresh_binary[i] = hdy[i] > hdy_thresh ? 1.0f : 0.0f;
    }

    // Convolve with Gaussian window
    for (size_t i = 0; i < hdx.size(); ++i) {
        double sum_x = 0.0;
        for (int j = 0; j < window_size; ++j) {
            int idx = i + j - half_size;
            if (idx >= 0 && idx < hdx.size()) {
                sum_x += hdx_thresh_binary[idx] * gausswin[j];
            }
        }
        blur_x[i] = static_cast<float>(sum_x);
    }

    for (size_t i = 0; i < hdy.size(); ++i) {
        double sum_y = 0.0;
        for (int j = 0; j < window_size; ++j) {
            int idx = i + j - half_size;
            if (idx >= 0 && idx < hdy.size()) {
                sum_y += hdy_thresh_binary[idx] * gausswin[j];
            }
        }
        blur_y[i] = static_cast<float>(sum_y);
    }

    // Skeletonize signals
    std::vector<float> skel_x = skeletonize_1d(blur_x);
    std::vector<float> skel_y = skeletonize_1d(blur_y);

    // Find line positions
    for (size_t i = 0; i < skel_x.size(); ++i) {
        if (skel_x[i] > 0) {
            lines_x.push_back(static_cast<int>(i));
        }
    }
    for (size_t i = 0; i < skel_y.size(); ++i) {
        if (skel_y[i] > 0) {
            lines_y.push_back(static_cast<int>(i));
        }
    }

    // Prune lines
    lines_x = pruneLines(lines_x, image_shape.width);
    lines_y = pruneLines(lines_y, image_shape.height);

    // Check if lines match expected pattern
    is_match = (lines_x.size() == 7) && (lines_y.size() == 7) &&
               checkMatch(lines_x) && checkMatch(lines_y);
}

// 9. Extract individual chess tiles
std::vector<cv::Mat> getChessTiles(cv::Mat& a, std::vector<int>& lines_x, std::vector<int>& lines_y) {
    int stepx = static_cast<int>(std::round(cv::mean(lines_x)[0] / (lines_x.size() - 1)));
    int stepy = static_cast<int>(std::round(cv::mean(lines_y)[0] / (lines_y.size() - 1)));

    // Pad the image if necessary
    int padl_x = 0, padr_x = 0, padl_y = 0, padr_y = 0;
    if (lines_x[0] - stepx < 0) padl_x = std::abs(lines_x[0] - stepx);
    if (lines_x.back() + stepx > a.cols - 1) padr_x = lines_x.back() + stepx - a.cols + 1;
    if (lines_y[0] - stepy < 0) padl_y = std::abs(lines_y[0] - stepy);
    if (lines_y.back() + stepy > a.rows - 1) padr_y = lines_y.back() + stepy - a.rows + 1;

    cv::Mat a2;
    cv::copyMakeBorder(a, a2, padl_y, padr_y, padl_x, padr_x, cv::BORDER_REPLICATE);

    std::vector<int> setsx, setsy;
    setsx.push_back(lines_x[0] - stepx + padl_x);
    for (const auto& x : lines_x) setsx.push_back(x + padl_x);
    setsx.push_back(lines_x.back() + stepx + padl_x);

    setsy.push_back(lines_y[0] - stepy + padl_y);
    for (const auto& y : lines_y) setsy.push_back(y + padl_y);
    setsy.push_back(lines_y.back() + stepy + padl_y);

    int num_squares = 64;
    std::vector<cv::Mat> squares(num_squares);

    int idx = 0;
    for (int j = 0; j < 8; ++j) {
        for (int i = 0; i < 8; ++i) {
            int x1 = setsx[i];
            int x2 = setsx[i + 1];
            int y1 = setsy[j];
            int y2 = setsy[j + 1];

            // Adjust sizes to ensure squares are of equal size
            if ((x2 - x1) != stepx) {
                x2 = x1 + stepx;
            }
            if ((y2 - y1) != stepy) {
                y2 = y1 + stepy;
            }

            cv::Rect rect(x1, y1, x2 - x1, y2 - y1);
            squares[idx++] = a2(rect);
        }
    }
    return squares;
}

// 10. Process a single image
void process_image(const std::string& image_path, const std::string& output_dir,
                   std::unique_ptr<tf::Session>& session) {
    // Load the image
    cv::Mat image = cv::imread(image_path);
    if (image.empty()) {
        std::cerr << "Failed to load image: " << image_path << std::endl;
        return;
    }
    // Convert to grayscale
    cv::Mat gray;
    cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY);

    // Preprocessing
    cv::Mat equ;
    cv::equalizeHist(gray, equ);
    cv::Mat norm_image;
    equ.convertTo(norm_image, CV_32F, 1.0 / 255.0);

    // Convert OpenCV Mat to TensorFlow Tensor
    tf::Tensor input_tensor(tf::DT_FLOAT, tf::TensorShape({1, norm_image.rows, norm_image.cols, 1}));
    auto input_tensor_mapped = input_tensor.tensor<float, 4>();
    for (int y = 0; y < norm_image.rows; ++y) {
        for (int x = 0; x < norm_image.cols; ++x) {
            float pixel = norm_image.at<float>(y, x);
            input_tensor_mapped(0, y, x, 0) = pixel;
        }
    }

    // Compute the gradients
    tf::Tensor grad_x = gradientx(input_tensor, session);
    tf::Tensor grad_y = gradienty(input_tensor, session);

    // Clip the gradients
    tf::Tensor Dx_pos(tf::DT_FLOAT, grad_x.shape());
    tf::Tensor Dx_neg(tf::DT_FLOAT, grad_x.shape());
    tf::Tensor Dy_pos(tf::DT_FLOAT, grad_y.shape());
    tf::Tensor Dy_neg(tf::DT_FLOAT, grad_y.shape());

    auto grad_x_mapped = grad_x.tensor<float, 4>();
    auto grad_y_mapped = grad_y.tensor<float, 4>();
    auto Dx_pos_mapped = Dx_pos.tensor<float, 4>();
    auto Dx_neg_mapped = Dx_neg.tensor<float, 4>();
    auto Dy_pos_mapped = Dy_pos.tensor<float, 4>();
    auto Dy_neg_mapped = Dy_neg.tensor<float, 4>();

    for (int i = 0; i < grad_x.NumElements(); ++i) {
        float val_x = grad_x.flat<float>()(i);
        Dx_pos.flat<float>()(i) = std::clamp(val_x, 0.0f, 255.0f);
        Dx_neg.flat<float>()(i) = std::clamp(val_x, -255.0f, 0.0f);
    }

    for (int i = 0; i < grad_y.NumElements(); ++i) {
        float val_y = grad_y.flat<float>()(i);
        Dy_pos.flat<float>()(i) = std::clamp(val_y, 0.0f, 255.0f);
        Dy_neg.flat<float>()(i) = std::clamp(val_y, -255.0f, 0.0f);
    }

    // Compute the Hough transform
    tf::Tensor hough_Dx(tf::DT_FLOAT, tf::TensorShape({norm_image.cols}));
    tf::Tensor hough_Dy(tf::DT_FLOAT, tf::TensorShape({norm_image.rows}));
    auto hough_Dx_mapped = hough_Dx.tensor<float, 1>();
    auto hough_Dy_mapped = hough_Dy.tensor<float, 1>();

    for (int x = 0; x < norm_image.cols; ++x) {
        float sum_pos = 0.0f;
        float sum_neg = 0.0f;
        for (int y = 0; y < norm_image.rows; ++y) {
            sum_pos += Dx_pos_mapped(0, y, x, 0);
            sum_neg += -Dx_neg_mapped(0, y, x, 0);
        }
        hough_Dx_mapped(x) = (sum_pos * sum_neg) / (norm_image.rows * norm_image.rows);
    }

    for (int y = 0; y < norm_image.rows; ++y) {
        float sum_pos = 0.0f;
        float sum_neg = 0.0f;
        for (int x = 0; x < norm_image.cols; ++x) {
            sum_pos += Dy_pos_mapped(0, y, x, 0);
            sum_neg += -Dy_neg_mapped(0, y, x, 0);
        }
        hough_Dy_mapped(y) = (sum_pos * sum_neg) / (norm_image.cols * norm_image.cols);
    }

    // Convert Hough transforms to vectors
    std::vector<float> hough_Dx_vec(hough_Dx_mapped.data(), hough_Dx_mapped.data() + hough_Dx_mapped.size());
    std::vector<float> hough_Dy_vec(hough_Dy_mapped.data(), hough_Dy_mapped.data() + hough_Dy_mapped.size());

    // Adaptive thresholding
    int a = 1;
    bool is_match = false;
    std::vector<int> lines_x, lines_y;

    while (a < 5) {
        float threshold_x = *std::max_element(hough_Dx_vec.begin(), hough_Dx_vec.end()) * static_cast<float>(a) / 5.0f;
        float threshold_y = *std::max_element(hough_Dy_vec.begin(), hough_Dy_vec.end()) * static_cast<float>(a) / 5.0f;

        getChessLines(hough_Dx_vec, hough_Dy_vec, threshold_x, threshold_y, norm_image.size(), lines_x, lines_y, is_match);

        if (is_match) {
            break;
        } else {
            a += 1;
        }
    }

    if (is_match) {
        std::cout << "7 horizontal and vertical lines found, slicing up squares" << std::endl;
        std::vector<cv::Mat> squares = getChessTiles(gray, lines_x, lines_y);
        std::cout << "Tiles generated: (" << squares[0].rows << "x" << squares[0].cols << ")*" << squares.size() << std::endl;

        // Extract filename and FEN
        fs::path img_file = fs::path(image_path).filename();
        std::string img_filename = img_file.string();
        std::string fen = img_filename.substr(0, img_filename.find_last_of("."));
        std::string img_save_dir = output_dir + "/" + fen;

        if (!fs::exists(img_save_dir)) {
            fs::create_directory(img_save_dir);
            std::cout << "Created dir " << img_save_dir << std::endl;
        }

        std::string letters = "ABCDEFGH";
        for (size_t i = 0; i < squares.size(); ++i) {
            std::ostringstream oss;
            oss << img_save_dir << "/" << fen << "_" << letters[i % 8] << (i / 8 + 1) << ".png";

            if (i % 8 == 0) {
                std::cout << "#" << i << ": saving " << oss.str() << "..." << std::endl;
            }

            // Resize to 32x32 and save
            cv::Mat resized;
            cv::resize(squares[i], resized, cv::Size(32, 32), 0, 0, cv::INTER_AREA);
            cv::imwrite(oss.str(), resized);
        }

    } else {
        std::cout << "No squares to save for " << image_path << std::endl;
    }
}