Tuesday, November 29, 2016

Transition from C/C++/Java to Python

I am trying to learn Python myself, as it greatly expedites prototype development compared to C/C++/Java.
Here is quick conversion of C/C++/Java common codes into Python:

for (int i=0; i<N; i++) {
for i in range(N):

if (x==y) {

if x==y:


not True

else if {


printf("%d\n", i)

print i

printf("%d ", i)

print i,

for (int i : array) {

for i in array:

vector<int> v;

v = [1,2]

// C++
vector<int> u,v;
v = u;
import copy
v = copy.copy(u) // OR // v = u[:]

// C++ vector<int> &u, &v;
v = u;
v = u

Sunday, November 27, 2016

How to Compile OpenCV with Debugging Symbols

Here is how to compile OpenCV with debugging symbols so that you can view OpenCV Library's source code as you debug.

When configuring with cmake, run with the following option:
$ cmake -DCMAKE_BUILD_TYPE:STRING=RelWithDebInfo ...

This option will add -O2 -g -DNDEBUG flags to the compiler when you build OpenCV.

For Ubuntu/Debian, take a look this post to see how to compile OpenCV from sources.

By the way, if you build OpenCV with clang, then you probably want to use lldb instead of gdb. If you compile with g++, then you may want to gdb instead of lldb. If you are having trouble running gdb on your Mac, check out this post.

Saturday, November 26, 2016

Using Qt as OpenCV HighGUI Backend for Mac OS X

Although I am in love with my new Macbook 12", I must admit that its X11 is quite annoying when used as OpenCV HighGUI's backend--it does not resize the image!

I was so frustrated with it that I looked for an alternative backend, and here it is: Qt5. In this tutorial, I will go over the method of building OpenCV3 on Mac OS X with Qt5 as HighGUI's backend. For simplicity, I will make use of homebrew.

First, you will need to tap into science:
$ brew tap homebrew/science

Next, install opencv3 with qt5 option:
$ brew install opencv3 --with-qt5

This option will add -D WITH_QT=ON in OpenCV's cmake option, thus linking Qt.

That's it! If you are wondering what are other available options, run
$ brew options opencv3

For more info on the package, run
$ brew info opencv3

That's it! You should now be able to launch HighGUI window via Qt5!

Saturday, November 19, 2016

Template Class in C++: Simple Queue Class Example

In this tutorial, I would like to demonstrate C++ template class by providng a very simple Queue class example.

The following shows its source file queue.cpp and header file queue.hpp:

There are a couple of important things to note.

First, the header file actually #includes the source file. This is because Queue class is a template class. In C++, a template class declaration and definition cannot be separated, and therefore by #includeing the source file, the two files are effectively treated as a single file. Of course, you may choose to simply define all the methods in the header file and get rid of the queue.cpp file.

Second, copy constructor, copy operator, and destructors are defined explicitly, because it assigns dynamic memory allocation. Please refer to this post for more details on the Rule of Three.

Lastly, when you compile sources that make use of this template class, all you need to do is to #include the header file. For instance, the compile command should look like:
$ g++ main.cpp

Notice there is no need to compile queue.cpp file or queue.hpp file separately. All you need to do is to make sure that queue.hpp file #includequeue.cpp file, and main.cpp #includes queue.hpp file.

Sunday, November 13, 2016

How to Determine Target Architecture from Library or Executable Files

To determine the target architecture of a library file or executable file, simply type in
$ objdump -x <file> | grep architecture

For example, you could do
$ objdump -x a.out | grep architecture
architecture: i386:x86-64, flags 0x00000012:

So, we now know that it is for x64 architecture!

By the way, if you don't have objdump, you could get it for Debian
$ sudo apt-get install binutils
or for Mac OS X
$ brew install binutils && alias objdump=gobjdump

Monday, November 7, 2016

OpenCV for Android Integration with NDK in Studio 2.2+

Starting with Android Studio 2.2+, NDK integration has become much easier. Thanks a lot, Google!

In the previous post, I showed you how to import OpenCV Android Tutorial 2 sample app into Android Studio and build with NDK integration, which admittedly was quite complicated. I am happy to present much easier method here in terms of NDK integration, starting from Android Studio 2.2.

The only difference from the past post lies in the App module's build.gradle file. The new file should resemble this:
apply plugin: 'com.android.application'

android {
    compileSdkVersion 22
    buildToolsVersion "24.0.3"

    defaultConfig {
        applicationId "org.opencv.samples.tutorial2"
        minSdkVersion 8
        targetSdkVersion 22

        ndk {

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'

    externalNativeBuild {
        ndkBuild {
            path 'src/main/jni/Android.mk'


dependencies {
    compile project(':openCVLibrary310')

That's it! This will now let Android Studio successfully load C/C++ source files in the src/main/jni directory with valid syntax correction, etc.

Wednesday, November 2, 2016

Install Latest Version of OpenCV on Debian from Sources

Here is how to install the latest OpenCV version on Debian from sources. If you are looking for tutorials on Mac OS X, you may want to check out this post.

Before doing anything, make sure to install necessary packages:
$ sudo apt-get install -y build-essential cmake git libgtk2.0-dev pkg-config libavcodec-dev libavformat-dev libswscale-dev

By the way, if you are not sure how to setup sudo in Debian, please take a look here.

Now, download the latest sources from its official Github repository. This will take some time.
$ git clone https://github.com/opencv/opencv.git

Else, you may want to just check out Linux component from here.

Create release folder and run cmake:
$ mkdir opencv/release && cd opencv/release

For more OpenCV cmake options, take a look here starting at line 171. If you would like to be able to debug the OpenCV library, you will need to compile with debug symbols. This post explains how to do so.

Now, we are ready to compile and install:
$ make -j $(nproc)
$ sudo make install

Let's test and see if you can link the library. Create test.cpp file with the following:
#include <opencv2/core.hpp>
#include <iostream>
using namespace cv;
int main() {
Mat test(3,2,CV_8UC1); 
std::cout << test << std::endl;

return 0;

Compile and run:
$ g++ test.cpp $(pkg-config --libs opencv)
$ ./a.out
./a.out: error while loading shared libraries: libopencv_shape.so.3.1: cannot open shared object file: No such file or directory

OK. This is because ldconfig hasn't been updated.
$ sudo ldconfig
$ ./a.out
[ 10,  60;
  71,   0;
   0,   0]


How to Install VirtualBox Guest Additions on Debian

VirtualBox Guest Additions provides many features, yet it may not be easy to install on Debian system. Here is how to install Guest Additions on Debian guest machine.

In the guest Debian system, insert Guest Additions image by selecting Devices->Insert Guest Additions CD Image from VirtualBox menu. This will insert the image into the guest machine. 

The Debian system will automatically mount the image to /media/cdrom folder. Let's run it:
$ su -
# cd /media/cdrom
# ./VBoxLinuxAdditions.run
-su: ./VBoxLinuxAdditions.run: Permission denied

This is rather interesting. Permission denied even for root. By the way if you want to run sudo command instead of su in Debian, refer to this tutorial.

The reason for this is actually because the Guest Additions image has been mounted with noexec flag.

# mount | grep cdrom
/dev/sr0 on /media/cdrom0 type iso9660 (ro,nosuid,nodev,noexec,relatime,user)

As clearly seen, the Guest Additions CD image has been mounted with noexec flag set. That is why you couldn't run it even as root. Let's mount it again without noexec flag.

# cd / && mount -t iso9660 /dev/sr0 /media/cdrom
mount: /dev/sr0 is write-protected, mounting read-only
# mount | grep cdrom
/dev/sr0 on /media/cdrom0 type iso9660 (ro,relatime)

OK. The Guest Additions image has been mounted successfully. Let's run the install script;
# /media/cdrom/VBoxLinuxAdditions.run
Verifying archive integrity... All good.
Uncompressing VirtualBox 5.1.8 Guest Additions for Linux...........
VirtualBox Guest Additions installer
Copying additional installer modules ...
Installing additional modules ...
vboxadd.sh: Building Guest Additions kernel modules.
Failed to set up service vboxadd, please check the log file
/var/log/VBoxGuestAdditions.log for details.

Well, let's examine the log file:
# cat /var/log/VBoxGuestAdditions.log
vboxadd.sh: failed: Look at /var/log/vboxadd-install.log to find out what went wrong.
vboxadd.sh: failed: Please check that you have gcc, make, the header files for your Linux kernel and possibly perl installed..

As the log file states, we need to install some necessary packages first, because it needs to compile the Guest Additions from sources.
# apt-get update
# apt-get install -r gcc make linux-hearders-$(uname -r)

Finally, we are ready to install Guest Additions:
# /media/cdrom/VBoxLinuxAdditions.run
Verifying archive integrity... All good.
Uncompressing VirtualBox 5.1.8 Guest Additions for Linux...........
VirtualBox Guest Additions installer
Removing installed version 5.1.8 of VirtualBox Guest Additions...
Copying additional installer modules ...
Installing additional modules ...
vboxadd.sh: Building Guest Additions kernel modules.
vboxadd.sh: Starting the VirtualBox Guest Additions.

You may need to restart the the Window System (or just restart the guest system)
to enable the Guest Additions.
# reboot

You may want to reboot for this to take effect. Enjoy virtual Debian system!

Saturday, October 29, 2016

How to Disable Annoying Paste Function of Mouse Middle Button

If there is one thing I really hate about my current GNOME desktop environment is its default paste function mapped to the mouse middle button. This is simply so annoying that I was looking for a way to get rid of this. After some trials, I have found one that actually works very well, and I would like to share it with anyone who is also having this problem. This post is based on this and this.

First, install xinput package if already not installed.
$ sudo apt-get install -y xinput

Next, list input devices and look for your mouse:
$ xinput list | grep 'id='
⎡ Virtual core pointer                     id=2 [master pointer  (3)]
⎜   ↳ Virtual core XTEST pointer               id=4 [slave  pointer  (2)]
⎜   ↳ Microsoft Microsoft® Nano Transceiver v1.0 id=11 [slave  pointer  (2)]
⎜   ↳ Microsoft Microsoft® Nano Transceiver v1.0 id=12 [slave  pointer  (2)]
⎣ Virtual core keyboard                   id=3 [master keyboard (2)]
    ↳ Virtual core XTEST keyboard             id=5 [slave  keyboard (3)]
    ↳ Power Button                             id=6 [slave  keyboard (3)]
    ↳ Video Bus                               id=7 [slave  keyboard (3)]

OK, so it tells me what input devices are connected. Under Virtual core pointer, I see my Microsoft Mouse, which is mapped to 11 and 12. In my case, it was the first device:
$ xinput get-button-map 11
3 2 1 5 4 6 7 8 9 10 11 12 13

The second number represents mapping of the middle button, so I simply disable it by setting it to 0:
$ xinput set-button-map 11 3 0 1

That's it. I now confirm that the middle mouse button no longer functions! Well, I want to keep it this way all the time, so I created a script
$ echo "xinput set-button-map 11 3 0 1" > ~/disable_middle.sh
$ chmod u+x ~/disable_middle.sh

I made it execute every time GNOME starts up by creating diable-middle-button.desktop file in ~/.config/autostart/ folder with the following
[Desktop Entry]

Now, your mouse middle button will be disable every time you start up GNOME!

Wednesday, October 26, 2016

Three Minutes Daily Vim Tip: Paste without Strange Indentation Behavior

I may have encountered a strange behavior in vim when you try to paste some codes with system-generic method (i.e., Ctrl+V) rather than vim method (i.e., yy and p). Here is how to fix it:

:set paste

This option will let vim know that you will be pasting text, and vim is to paste the text as is, without trying to auto-indent. When you are done pasting, simply set

:set nopaste

Happy vimming!

Solution to "error while loading shared libraries"

I was trying to run a  simple executable that makes use of opencv library, and I encountered an error:
$ ./a.out
./a.out: error while loading shared libraries: libopencv_shape.so.3.1: cannot open shared object file: No such file or directory

I was certainly able to locate the file manually in the proper directory:
$ find / -name libopencv_shape.so.3.1

Very interesting. This is probably because I manually compiled and installed opencv3.1.0 from sources. In any case, here is the solution.

First, we need to look for the shared library path. The system dynamic linker locations are specified in /etc/ld.so.conf file, which probably includes .conf files in /etc/ld.so.conf.d/ folder. Each of the .conf files in the folder specifies the system dynamic linker locations, such as /lib/x86_64-linux-gnu.

Also, one can define the shell-variable LD_LIBRARY_PATH to include the directory of the shared library file that needs to be linked.

My case? It was a subtle. I certainly had the library folder included in one of the config files:
$ cat /etc/ld.so.conf.d/libc.conf
# libc default configuration

Yet, I was still getting the error. Why? That's because I needed to manually load config:
$ sudo ldconfig

I guess make install command of opencv did not automatically do this. For more info, please take a look at this document. You also might be interested in loading one-time shared library files without neither of the methods above, from here.

Tuesday, October 25, 2016

Three Minutes Daily Vim Tip: Disable F1 Key

<F1> is by default mapped to vim help, but this is rather annoying, especially when I have mapped <F2> and <F3> for switching between tabs. Here is how to disable <F1> key mapping in vim.

Simply add these two lines of codes into ~/.vimrc:
nmap <F1> :echo<CR>
imap <F1> <C-o>:echo<CR>

Note: if you using GNOME, then F1 is snatched by GNOME before vim, in which case you will likely get some help menu even if you have disabled F1 from vim. IN this case, simply disable F1 from the terminal --> preferences --> shortcuts --> help --> backspace.

Sunday, October 23, 2016

How to Mark C/C++ Files in Android Studio

UPDATED: please refer to this post for much simpler way! The instruction below is officially deprecated.

There are basically two ways to link native C/C++ files in Android Studio. One way is to use CMake, and the other is to use NDK-BUILD tool. In OpenCV Android samples, it does it by NDK-BUILD tool. as you can see here. However, the problem with NDK-BUILD method is that Android Studio does not treat JNI native source files as C/C++ files; it is quite annoying to code directly in Android Studio when you don't have function-completion features or auto-syntax checks, etc.

Here, I will show you how to trick Android Studio to mark JNI native source files as C/C++ files when building with NDK-BUILD method. Basically, I will add CMake file to link C/C++ file folder for compilation so that Android Studio will mark all C/C++ files in the folder as C/C++ with correct syntax guide. For more info on adding C/C++ support in Android Studio, refer to Google's official documentation.

First, Install NDK and CMake components for your Android Studio. Also make sure that you are running Android Studio 2.2 or higher.

Locate C/C++ files that are linked with your Android project. This is probably src/main/jni folder in the app module directory. Create an empty C/C++ file in the folder: src/main/jni/empty.cpp.

Next, create CMakeLists.txt file in the app's directory with the following content:
cmake_minimum_required(VERSION 3.4.1)
             src/main/jni/empty.cpp )

The last parameter should point to the newly created empty C/C++ file. Also make sure to add as many include directories as needed. 

Next, add the following lines in the app module's build.gradle file:
android {
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"

Now, sync gradle file and compile. You will notice that Android Studio now considers all C/C++ files in the src/main/jni folder as native C/C++ files. You can now directly edit your C/C++ files from Android Studio with syntax checks, method completion, etc!

Saturday, October 22, 2016

How to Restore Nexus Devices to Factory Images

I have Nexus 4 device which I do not use anymore. I decided use it for Android development. Here is how to flash/restore Nexus device to factory image. This post is basically a short-form recap of Google's official document.

Step 1, download the appropriate factory image at the right side of this webpage.

Step 2, unzip it and locate flash-all.sh file.

Step 3, restart your Android phone into fastboot mode. You can connect your Nexus and run the following command from a computer:
$ adb reboot bootloader
or use key-combos found here.

Step 4, while Nexus is connected to the computer and booted up in fastboot mode, execute flash-all.sh file from your computer as root. You need fastboot utility. If you have Android Studio installed, you already have it. Otherwise, you could download the command line SDK from here. Make sure to run with root privilege!
$ sudo ./flash-all.sh

That's it! It will automatically carry out some tasks and reboots, and restore your phone into factory image.

Thursday, October 20, 2016

Three Minutes Daily Vim Tip: Setting to Preloaded Colors

Vim comes with some color schemes. It will save some hassle if you can find one that looks good. Here is how to try them!

First, locate pre-loaded colors. Usually the files are in /usr/share/vim/vim73/colors/ directory, assuming you have vim7.4. Try
$ find / -name desert.vim 2>/dev/null


Now you can search the directory
$ ls /usr/share/vim/vim73/colors/*.vim

In vim, set the color by
:color desert

To set it as your default color, simply do
$ echo ":color desert" >> ~/.vimrc

Enjoy vim!

Saturday, October 15, 2016

Android Studio .GITIGNORE File

This is my personal Android Studio .gitignore file:

How to Integrate OpenCV for Android Tutorial 2 and Tutorial 3 Modules

OpenCV Android Tutorial 2 shows how to setup JNI to import C++ native code, while Tutorial 3 shows how to control Android camera to take a picture. In this tutorial, I will go over step by step how to integrate Tutorial 2 and Tutorial 3 modules; that is, we will be building an app that will both let us import native C++ OpenCV code and control Android camera to take picture.

To me, it seems easier to integrate JNI into Tutorial 3 module, so the following will simply add JNI capability to Tutorial 3 module.

First, import Tutorial 3 module from OpenCV Android.

Next, edit openCVTutorial3CameraControl/build.gradle file to use NDK, similar to below:
import org.apache.tools.ant.taskdefs.condition.Os
apply plugin: 'com.android.application'

android {
    compileSdkVersion 22
    buildToolsVersion "24.0.3"

    defaultConfig {
        applicationId "org.opencv.samples.tutorial3"
        minSdkVersion 8
        targetSdkVersion 22

        ndk {
            moduleName "camera_control" // this is the native module name

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'

    sourceSets.main {
        jniLibs.srcDir 'src/main/libs'
        jni.srcDirs = []

    task ndkBuild(type: Exec) {
        if (Os.isFamily(Os.FAMILY_WINDOWS)) {
            commandLine 'ndk-build.cmd', '-C', file('src/main').absolutePath
        } else {
            commandLine '/home/linuxnme/Android/Sdk/ndk-bundle/build/ndk-build', '-C', file('src/main').absolutePath // replace with your path

    tasks.withType(JavaCompile) {
        compileTask -> compileTask.dependsOn ndkBuild

dependencies {
    compile project(':openCVLibrary310')

Make sure to note NDK module name; in my case it is set to camera_control. Also, make sure to compile openCVLibrary with the same SdkVersion by editing the library's build.gradle file.

Next, edit Tutorial3Activity.java file to look like following:
public class Tutorial3Activity extends Activity implements CvCameraViewListener2, OnTouchListener {
    private static final String TAG = "OCVSample::Activity";
    public native void FindFeatures(long matAddrGr, long matAddrRgba);
    private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
        public void onManagerConnected(int status) {
            switch (status) {
                case LoaderCallbackInterface.SUCCESS:
                    Log.i(TAG, "OpenCV loaded successfully");
                } break;
                } break;

   public Mat onCameraFrame(CvCameraViewFrame inputFrame) {
        Mat mRgba =  inputFrame.rgba();
        Mat mGray = inputFrame.gray();
        FindFeatures(mGray.getNativeObjAddr(), mRgba.getNativeObjAddr());
        return mRgba;

Make sure to use the same native module name.

Next, copy jni folder from Tutorial 2. Edit Application.mk file to set
APP_ABI := all

Edit Android.mk file to change JNI module name:
LOCAL_MODULE    := camera_control

Also, edit line 12 of this file to correctly point to your jni folder of OpenCV Android SDK.

Edit jni_part.cpp to correct the function names as below:
extern "C" {
JNIEXPORT void JNICALL Java_org_opencv_samples_tutorial3_Tutorial3Activity_FindFeatures(JNIEnv*, jobject, jlong addrGray, jlong addrRgba);

JNIEXPORT void JNICALL Java_org_opencv_samples_tutorial3_Tutorial3Activity_FindFeatures(JNIEnv*, jobject, jlong addrGray, jlong addrRgba)
    Mat& mGr  = *(Mat*)addrGray;
    Mat& mRgb = *(Mat*)addrRgba;
    vector<KeyPoint> v;

Enjoy Android development!

Android Studio - How to View Method Parameters

On Linux, press <Ctrl> + P
On Mac, press <Command> + P

How to Follow a Symbolic Link All the Way

You may have heavily nested symbolic links, and here is how to find its final destination. For example, if you are looking for the java executable:

$ readlink -f $(which java)

That's it!

How to Change Default JDK with AlternativesI

Here is how to set Oracle's JDK as your default. You can apply this to set OpenJDK as default as well very easily.

First, you will need to download Oracle's JDK. Visit here to download the latest JDK for your system.

For Debian-based Linux, you simply need to download the tar.gz archive and extract to a folder. For example,
$ tar xvfz jdk-8u101-linux-x64.tar.gz
$ sudo mv jdk1.8.0_101 /opt/

Next, set Oracle's JDK as an alternative:
$ sudo update-alternatives --install /usr/bin/java java /opt/jdk1.8.0_101/bin/java 100
$ sudo update-alternatives --install /usr/bin/javac javac /opt/jdk1.8.0_101/bin/javac 100

Finally, set whichever version you would prefer:
$ sudo update-alternatives --config java
$ sudo update-alternatives --config javac

That's it!

Wednesday, October 12, 2016

Installation of Multiple Instances of the Same Android App

Sometimes, you have your template app, from which you develop different apps. However, Android will count apps that have branched from the same app as one app.

Say you have your template app T. You modify T and create app A. You also create app B by modifying another copy of T. Now, you want to install both apps A and B into your phone, but it won't work; one will overwrite the other, because A, B have branched from T.

One way to install both A and B on a single phone is to change their applicationid in the app's build.gradle file. In most case, the applicationid is identical to the main activity package name. Make sure to change the top-most name of applicationid. After the change, build clean and invalidate caches and restart Android Studio. 

Now, you should be able to install both A and B as long as their applicationid's are different.

How to Apply .gitignore File To an Existing Repository

When you update or add .gitignore file to not track unnecessary files, such as object files and binary files, this is what you can do to apply the change to an existing repository.

$ git commit -m "outstanding changes before applying .gitignore"
$ git rm -r --cached .
$ git add .
$ git commit -m "applying .gitignore"

That's it!

Saturday, October 8, 2016

How to Log Visitors' IP Addresses, OS, and Browser

This tutorial is based on the answers by deceze and Gaurang.

The following code will log the visitor's IP address and access time, OS, and browser:

$user_agent     =   $_SERVER['HTTP_USER_AGENT'];

function getOS() {
    global $user_agent;
    $os_platform    =   "Unknown OS Platform";
    $os_array       =   array(
                            '/windows nt 10/i'     =>  'Windows 10',
                            '/windows nt 6.3/i'     =>  'Windows 8.1',
                            '/windows nt 6.2/i'     =>  'Windows 8',
                            '/windows nt 6.1/i'     =>  'Windows 7',
                            '/windows nt 6.0/i'     =>  'Windows Vista',
                            '/windows nt 5.2/i'     =>  'Windows Server 2003/XP x64',
                            '/windows nt 5.1/i'     =>  'Windows XP',
                            '/windows xp/i'         =>  'Windows XP',
                            '/windows nt 5.0/i'     =>  'Windows 2000',
                            '/windows me/i'         =>  'Windows ME',
                            '/win98/i'              =>  'Windows 98',
                            '/win95/i'              =>  'Windows 95',
                            '/win16/i'              =>  'Windows 3.11',
                            '/macintosh|mac os x/i' =>  'Mac OS X',
                            '/mac_powerpc/i'        =>  'Mac OS 9',
                            '/linux/i'              =>  'Linux',
                            '/ubuntu/i'             =>  'Ubuntu',
                            '/iphone/i'             =>  'iPhone',
                            '/ipod/i'               =>  'iPod',
                            '/ipad/i'               =>  'iPad',
                            '/android/i'            =>  'Android',
                            '/blackberry/i'         =>  'BlackBerry',
                            '/webos/i'              =>  'Mobile'

    foreach ($os_array as $regex => $value) {
        if (preg_match($regex, $user_agent)) {
            $os_platform    =   $value;
    return $os_platform;

function getBrowser() {
    global $user_agent;
    $browser        =   "Unknown Browser";
    $browser_array  =   array(
                            '/msie/i'       =>  'Internet Explorer',
                            '/firefox/i'    =>  'Firefox',
                            '/safari/i'     =>  'Safari',
                            '/chrome/i'     =>  'Chrome',
                            '/edge/i'       =>  'Edge',
                            '/opera/i'      =>  'Opera',
                            '/netscape/i'   =>  'Netscape',
                            '/maxthon/i'    =>  'Maxthon',
                            '/konqueror/i'  =>  'Konqueror',
                            '/mobile/i'     =>  'Handheld Browser'

    foreach ($browser_array as $regex => $value) {
        if (preg_match($regex, $user_agent)) {
            $browser    =   $value;
    return $browser;

$user_os        =   getOS();
$user_browser   =   getBrowser();

$device_details =   $user_browser." ".$user_os;
$line = date('Y-m-d H:i:s') . " - $_SERVER[REMOTE_ADDR]" . " " . $device_details;

file_put_contents('visitors.log', $line . PHP_EOL, FILE_APPEND);

Make sure that the current directory is owned by www-data.
$ sudo chown www-data:www-data .

You will see a log file visitors.log in the current directory with a log similar to:
2016-10-08 12:33:13 - Chrome Linux

How to Allow a Visitor Upload Image Files on Your Web Server

This post is based on this tutorial with some minor modifications.

If you want to let a visitor upload an image file to your web server, here is how to do. I will assume that you have a http and php server running on your system. If not, please refer to this post.

First, create a simple upload.html file:
    <form action="upload.php" method="post" enctype="multipart/form-data">
            Select image to upload:
                <input type="file" name="fileToUpload" id="fileToUpload">
                <input type="submit" value="Upload Image" name="submit">

Next, create upload.php file:
$target_dir = "upload/";
$target_file = $target_dir . basename($_FILES["fileToUpload"]["name"]);
$uploadOk = 1;
$imageFileType = pathinfo($target_file,PATHINFO_EXTENSION);
// Check if image file is a actual image or fake image
if(isset($_POST["submit"])) {
    $check = getimagesize($_FILES["fileToUpload"]["tmp_name"]);
    if($check !== false) {
        echo "File is an image - " . $check["mime"] . ".<br>";
        $uploadOk = 1;
    } else {
        echo "File is not an image.<br>";
        $uploadOk = 0;
// Check file size
if ($_FILES["fileToUpload"]["size"] > 5000000) {
    echo "Sorry, your file is too large.<br>";
    $uploadOk = 0;
// Allow certain file formats
if($imageFileType != "jpg" && $imageFileType != "png" && $imageFileType != "jpeg"
&& $imageFileType != "gif" && $imageFileType != "webp" && $imageFileType != "jxr" ) {
    echo "Sorry, only jpg, jpeg, png, webp, jxr, & gif files are allowed. Also, the extension must be all LOWER case!<br>";
    $uploadOk = 0;
// Check if $uploadOk is set to 0 by an error
if ($uploadOk == 0) {
    echo "Sorry, your file was not uploaded.<br>";
// if everything is ok, try to upload file
} else {
    if (move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $target_file)) {
        echo "The file ". basename( $_FILES["fileToUpload"]["name"]). " has been uploaded.<br>";
        echo "<br>http://SERVER_IP/" . $target_dir . basename( $_FILES["fileToUpload"]["name"]) . "<br>";
    } else {
        echo "Sorry, there was an error uploading your file.<br>";

Make sure to replace SERVER_IP with your server's IP address. Now, you can browse to your upload.html file, choose an image file, and upload to the server.

Make sure that the target directory is owned by www-data, the web werver.
$ sudo chown www-data:www-data /var/www/html/upload

Make sure to implement more security checks. You surely don't want any random visitor upload random files on the server!

How to Find out Visitor's IP Address

Here is how to find out a visitor's IP address using PHP:
    echo $_SERVER['REMOTE_ADDR']

You can always embed this in HTML file:
    Your IP Address is <?php echo $_SERVER['REMOTE_ADDR'] ?>.

Note that the web server must be configured to parse the PHP code inside html file to display it correctly. You may want to refer tho this post to do this in Debian.