Monday, March 27, 2023

Convert a sequence of JPG images into a MP4 video using FFMPEG

If you have a sequence of images like the screen shot below, it is quite straightforward to use ffmpeg to concatenate them into a single video file. 

Obviously, you need to specify the image file name and sequence pattern, the frame rate to show each image as shown in the example ffmpeg command below.

$ ffmpeg -hide_banner \
-f image2 \
-framerate 1 \
-start_number 1 \
-i %02d.jpeg \
-vcodec mpeg4  \

where -f specifies the input format as image,

-framerate specifies the rate in Hz at which to display each image frame,

-start_number specifies the starting number of the image frame sequence,

-i specifies the file name and sequence pattern, e.g. %02d means a pattern of 2 digits with a prefix character '0' for single digit numbers,

-vcodec tells ffmpeg to output mpeg4 format.

 Running the command will display the following output and generate the output video file output.mp4.

Monday, January 30, 2023

Fixing a kernel panic error when installing Ubuntu 20.04 in VirtualBox

I was trying to create an Ubuntu 20.04 virtual machine using Oracle VirtualBox but I kept encountering this error with the message "...end kernel panic - not syncing: Attempted to kill the idle task!..." The screenshot below shows the error in VirtualBox.

Eventually, I found out the error was caused by inadequate alllocated CPU resources in VirtualBox. By default, the number of CPU allocated for the VM is 1, as shown in the screen shot below.

Simply increasing the number of CPU to at least 2 helped to solve the kernel panic error in this case.

Monday, December 12, 2022

How to read an array from a ROS1 launch file into a C++ vector variable

I was having problems reading an array of doubles in a ROS launch file into a vector variable in a C++ ROS1 program node. After some digging around, I found I was doing it the wrong way; instead of using the <param> tag in the ROS launch file, I should be using the <rosparam> tag. 

The example ROS launch file listing shows the correct way to enter an array, e.g. [0.01, 0.1, 0.2] with the <rosparam> tag:

?xml version="1.0" encoding="UTF-8"?>
  <node pkg="learning_tutorial" type="my_node" name="my_node">
        <param name="my_string_param" value="hello" />
        <rosparam param="my_array_param">[0.01, 0.1, 0.2]</rosparam>

Then in the ROS C++ code, I could do the following to read the array:

// ...etc...

using namespace ros;
using namespace std;

vector<double> myArrayParam;
NodeHandle nh;

// Read the my_array_param from the launch file into the myArrayParam variable
nh.param<vector<double>> ( "my_array_param", myArrayParam, { 1, 2, 3});

// Just print out the array parameter
ROS_INFO ( "My array: %f, %f, %f", myArrayParam[0], myArrayParam[1], myArrayParam[2]);
// ...etc...

Hope this helps somebody.

Monday, December 5, 2022

Using GIMP to copy an image layer into a channel

In GIMP, it is possible to copy a colored image layer into a single grayscale channel. Here's how to do the job.

Create an empty channel

  1. In GIMP, open up the image file.

  2. Click the Channels tab on the bottom right.

  3. Then click the Create a new channel icon.

    The New Channel dialog box appears.

  4. Optional. In the Channel name field, type in a name, e.g. flats. In the Fill opacity field, slide to 50.0.
  5. Click OK.

    A new channel is created.

Copy and paste the colored image into the channel

  1. Click the Layers tab. Click the image layer, e.g. bored-tiger.jpg to make sure it is active.

  2. In the menu, choose Select | All. Then choose Edit | Copy.

    The layer is copied to the clipboard.

  3. Click the Channels tab. Click the previously created channel, e.g. flats.

  4. In the menu, select Edit | Paste in Place

  5. Then click the Layers tab.

    A Floating Selection (Pasted Layer) is visible in the Layers list.

  6. Click the green Anchor the floating layer icon.

    The pasted layer is added to the active channel.

  7. Optional. Click the Channels tab.

    Note the channel thumbnail has been updated with a grayscale image of the pasted layer.

Monday, November 28, 2022

Using GIMP's color channels to remove blue guide lines from inked line art

Penciled comic book art typically have blue guide lines (and text) as shown in the screen shot below. 

The sketch can be downloaded from this site if you want to practice with it. There are a number of ways to remove the blue lines from the image. I will be using the Color channels panel to remove the guide lines.

The steps:

Remove pixels from the Red and Green channels

  1. In GIMP, open up the image. Click the Channels tab on the bottom right.

  2. Click the Blue channel to deselect it.

  3. In the Tool palette, click the Fill icon.

  4. Toggle on the FG color fill. Make sure the foreground color is black since we want to remove the Red and Green channels data.

  5. Then, click anywhere on the canvas.

    The background becomes blue.

Create the line art in the Red and Green channels

  1.  In the Channels tab, press the mouse right click button on the Blue channel.

    A popup menu appears.

  2. Choose Channel to Selection.

    The blue channel's non-zero pixels are selected.

  3. In the Tool box, click the Fill icon. Toggle on the BG color fill option. (Note: the background color should be white). Then click anywhere in the selection on the canvas.

    The background becomes white.

  4. In the Channels tab, click the Blue channel to select it again.  Then in the menu, choose Select | None to clear the selection.

Use Threshold to clean up the line work

  1. In the menu, select Colors | Threshold.

    The Threshold dialog box appears.

  2.  Drag the black triangle until you are satisfied with the contrast between the line work and the background.

  3. Save your work.

Monday, November 7, 2022

Shell script to batch bulk convert *.flac files to *.mp3

I have many music files in flac format and I wanted to convert them to a more compressed mp3 format with ffmpeg on Ubuntu so I can upload them to a storage limited portable music player. To ease the conversion task, I decided to write this simple shell script to do the job. In brief, the script will do the following:

  • find all the files with the extension .flac in the current directory
  • replace the file name extension .flac with the .mp3 extension
  • create a temporary script that calls the ffmpeg command to convert
  • run the temporary script

The listing of the shell script is shown below.  

# Define the internal field separator as a newline

# Find all the *.flac files in the current directory and perform the conversion
for f in `find . -name "*.flac" `;
	# Use the input flac file name prefix and replace the .flac extension with a .mp3 extension
	f=$(echo $f | cut -c 3-)
	outfile=$(basename $f .flac)
	echo "Convert $f->$outfile..."
	# Form the ffmpeg command to convert the input flac file to mp3
	cmd="ffmpeg -hide_banner -i \"$f\" -ab 320k -map_metadata 0 -id3v2_version 3 \"$outfile\" "
	# Create a temporary shell script for running the conversion
	echo $cmd > /tmp/
	# Run the conversion to mp3
	bash /tmp/
	# Clean up
	rm /tmp/

To use this shell script, you can do the following:

  1. Save the code listing above to a file e.g. in a directory, e.g. /path/to

  2. Open up a Linux Terminal.

  3. In the Terminal, type in the command to change directory to the location of the flac files, e.g. /path/to/music/

    $ cd /path/to/music

  4. At the prompt, type in the command to run the shell script.

    $ bash /path/to/

    The flac files are converted to mp3 files.

Monday, October 31, 2022

Simple C++ example to send serial AT commands to and receive data from a modem

I tried using many C/C++ libraries trying to coax a 5G modem to respond to my input AT commands for a long time but I was not successful. The command I was trying to send was the Quectel modem command to query for PDN channels:


After a while, I figured out I had to simulate a keyboard Enter press in code to actually tell the modem the command is complete. So all I had to do was append the carriage return (\r) and new line (\n) characters to the AT command string, e.g:

string cmd = "AT+CGDCONT?\r\n";

A working C++ code example is shown below:

#include <string>
#include <iostream>
#include <cstdio>
#include <unistd.h>

// Using header only library from 
#include "Serial.h"

using namespace std;

int main ( int argc, char** argv) {

        string commPort = "/dev/ttyUSB2";
        unsigned int baud = 115200;
        serial::Serial serial; ( commPort, baud);

        if ( !serial.isOpen()) {
                cout << "comm port is not open" << endl;
                return 1;

        // A modem AT query command
        string cmd = "AT+CGDCONT?\r\n";
        vector<uint8_t> cmdVec (cmd.begin(), cmd.end());

        // send command to the modem
        size_t bytesSent = serial.transmitAsync(cmdVec);
        cout << "Bytes sent " << bytesSent << endl;

        int received_bytes = -1;

        while (received_bytes != 0 ) {
                // read one byte from the modem and timeout if the
                // there is no response in more than 1 sec.
                future<vector<uint8_t>> future = serial.receiveAsync(1, 1000);
                vector<uint8_t> const received_data = future.get();
                received_bytes = received_data.size();

                string str(received_data.begin(), received_data.end());
                cout << "[" << received_data[0] << "] " << endl;
        // Close the serial port

        cout << "End of process" << endl;

        return 0;


The example prints out the data sent by the modem to the calling program, as shown below:

Note: this example is using the modern serial header only C++ library from this site: