Alterna
Alterna

Reputation: 21

C++ OpenCV - How to display a resized video frame to ASCII?

I made a program that turns a video into a resized ASCII animation, but the Mat frame only captures a portion of the top left corner of the full video. The depth and channel of the Mat is 0 and 1, accordingly. The program also doesn't work without the at<uchar>, even though the Mat returns as empty?

Expected output size Actual output size

#include <iostream>
#include <vector>
#include <chrono>
#include <thread>
#include <Windows.h>
#include <mmsystem.h>
#include <opencv2/opencv.hpp>

#pragma comment(lib, "winmm.lib")

void checkForOpenCV()
{
    if(cv::getCPUTickCount == 0)
    {
        std::cout << "OpenCV is not detected." << '\n';
    }
    else
    {
        std::cout << "OpenCV is detected." << '\n';
    }
}

cv::VideoCapture openVid(std::string &filePath)
{
    cv::VideoCapture cap{filePath};
    if(!cap.isOpened())
    {
        std::cout << "The file cannot be accessed." << '\n';
        exit(-1);
    }
    else
    {
        std::cout << "File is accessed." << '\n';
    }

    return cap;
}

char brightness2Ascii(int brightness, std::string asciiCharset)
{
    int index(brightness * asciiCharset.size() / 256);
    return asciiCharset[index];
}

int main()
{
    checkForOpenCV();

    std::string filePath{R"(C:\Users\Harold.DESKTOP-UJ6F4M4\Videos\video.mp4)"};
    cv::VideoCapture cap{openVid(filePath)};

    double fps{cap.get(cv::CAP_PROP_FPS)};
    std::cout << fps << '\n';

    int width(cap.get(cv::CAP_PROP_FRAME_WIDTH));
    int height(cap.get(cv::CAP_PROP_FRAME_HEIGHT));
    int aspectRatio{12};
    std::cout << width << " x " << height << '\n';

    std::vector<cv::Mat> totalFrames{};
    cv::Mat frame{};

    int depth{frame.depth()};
    int channels{frame.channels()};

    while(cap.read(frame))
    {
        cv::resize(frame, frame, cv::Size(width / aspectRatio, height / aspectRatio), 0, 0, cv::INTER_LINEAR);
        totalFrames.push_back(frame.clone());
    }
    cap.release();

    
    for(const auto &frame:totalFrames)
    {
        std::string asciiArt{};
        for(int i{0}; i < frame.rows; i++)
        {
            for(int j{0}; j < frame.cols; j++)
            {
                const std::string asciiCharset{" .'-=*%#@"}; // {" .'-=*%#@"} {"@ "}
                int brightness{frame.at<uchar>(i, j)};
                char pixelChar{brightness2Ascii(brightness, asciiCharset)};
                asciiArt += pixelChar;
            }
            asciiArt += '\n';
        }
std::cout << depth << '\n' << channels << '\n';
        std::cout << asciiArt;
        cv::waitKey(fps / 2);

    }

    return 0;
}

I tried creating a separate Mat resizedFrame, as well as declaring a separate width and height for the asciiChar to no avail. Initially, I thought the problem occured from resizing the frame after preloading all of it, but after relocating the resize() in the preloading loop, I'm not so sure anymore.

Upvotes: 1

Views: 113

Answers (1)

Alterna
Alterna

Reputation: 21

Turns out I needed to convert the frame into a GRAY Mat if I'll use at<uchar> alongside it. I didn't do that because the video was black and white already, but OpenCV turned it into BGR anyway. For non-gray Mats, use at<Vec3b>() instead.

 for(const auto &frame : totalFrames)
 {
 cv::cvtColor(frame, grayFrame, cv::COLOR_BGR2GRAY);
 cv::resize(grayFrame, grayFrame, cv::Size(2 * width / aspectRatio, height / aspectRatio), 0, 0, cv::INTER_LINEAR);

 std::string asciiArt{};
 for(int i{0}; i < grayFrame.rows; i++)
 {
     for(int j{0}; j < grayFrame.cols; j++)
     {
         const std::string asciiCharset{".'-=*%#@"}; // {" .'-=*%#@"} {"@ "}
         int brightness{grayFrame.at<uchar>(i, j)};
         char pixelChar{brightness2Ascii(brightness, asciiCharset)};
         asciiArt += pixelChar;
     }
     asciiArt += '\n';
 }
 std::cout << asciiArt;
 Sleep(1000 / fps);

}

Upvotes: 1

Related Questions