> Source: https://meetrix.io/blogs/kurento-opencv-filter/
> Markdown copy of that page. Cite the URL above, not this file.

Development

# How to Create an OpenCV Filter for Kurento Media Server

[By Buddhika Jayawardhana](https://meetrix.io/blogs/authors/buddhika-jayawardhana/) • September 15, 2026 • 6 min read

Kurento's big differentiator among WebRTC media servers is that it lets you run your own code on the video as it flows. The easiest way in is an OpenCV filter: Kurento hands your code each frame as an OpenCV matrix, you change it, and it goes out to viewers. In this guide the filter writes text onto the video, which sounds trivial but covers every step a real computer vision module needs.

We first published this in 2018 for Kurento 6.7 on Ubuntu 16.04, with a companion [sample repository](https://github.com/meetrix/kurento-custom-opencv-module-tutorial). The code idea is unchanged; the tooling around it has moved on, and the steps below follow Kurento 7.3's [module writing documentation](https://doc-kurento.readthedocs.io/en/latest/user/writing_modules.html) and the current sources on [Kurento's GitHub](https://github.com/Kurento/kurento).

## How Kurento modules work

A Kurento module has two halves. The **server side** is a C++ library that Kurento loads, containing your media element. The **interface** is a `.kmd.json` file describing the element and its methods, from which Kurento generates client code for JavaScript and Java. Your application creates the element in a pipeline, between a `WebRtcEndpoint` and wherever the video goes next, the same way it would use a built-in filter.

For an OpenCV module, the part you write is one function: `process(cv::Mat &mat)`, called for every frame.

## Set up the build machine

Build on **Ubuntu 24.04**, the same release Kurento 7.3 runs on. Kurento's docs are explicit that mixing systems between building and running a module causes C++ ABI problems. Add the Kurento repository exactly as in [installing Kurento Media Server on Ubuntu 24.04](https://meetrix.io/blogs/install-kurento-media-server-ubuntu/), then install the development package:

```bash
sudo apt-get update
sudo apt-get install --no-install-recommends --yes kurento-media-server-dev
```

That package brings the headers, CMake helpers and the scaffolding tool. The 2018 version of this guide used a `xenial` repository line and `apt-key`; neither applies any more.

## Scaffold the module

The scaffold tool generates the module structure. Its arguments are a namespace prefix, the module name in PascalCase, the name in snake\_case, and whether it is an OpenCV module:

```bash
kurento-module-scaffold Meetrix HelloWorld hello_world true
```

The prefix avoids clashes with other modules, and `true` generates the OpenCV variant. Change into the generated module directory, then run CMake once to generate the implementation files from the interface definition:

```bash
cd hello-world/   # use the directory the scaffold created
mkdir build && cd build
cmake ..
```

The implementation files appear under `src/server/implementation/objects/`. The one to edit ends in `OpenCVImpl.cpp`.

## Write the frame processing

The generated `process` method throws a "not implemented" exception. Replace its body:

```cpp
void HelloWorldOpenCVImpl::process (cv::Mat &mat)
{
  cv::putText (mat, textToPrint, cv::Point (positionX, positionY),
               cv::FONT_HERSHEY_PLAIN, 2, cv::Scalar (255, 255, 255), 2);
}
```

The frame is a normal `cv::Mat`, so anything OpenCV can do to an image, you can do here: detect faces, blur regions, draw overlays. Whatever you leave in `mat` when the function returns is what viewers see.

This runs for every frame

At 30 frames per second you have about 33 milliseconds per frame, shared with decoding and re-encoding. Keep allocations out of `process`, load models once in the constructor rather than per frame, and measure CPU with several streams before putting heavy vision code in front of users.

## Add a method you can call

Static text is not very useful. To let the application change the text and position at runtime, declare a method in the interface file under `src/server/interface/` (the file ending in `.kmd.json`):

```json
"methods": [
  {
    "name": "setText",
    "doc": "Sets the overlay text and its position",
    "params": [
      { "name": "text", "doc": "Text to print", "type": "String" },
      { "name": "x", "doc": "Position x", "type": "int" },
      { "name": "y", "doc": "Position y", "type": "int" }
    ]
  }
]
```

Add the state and the setter to the OpenCV implementation class. In the header:

```cpp
private:
  std::string textToPrint = "Hello from Meetrix";
  int positionX = 100;
  int positionY = 100;

public:
  void setText (const std::string &text, int x, int y);
```

In the `OpenCVImpl.cpp` file:

```cpp
void HelloWorldOpenCVImpl::setText (const std::string &text, int x, int y)
{
  textToPrint = text;
  positionX = x;
  positionY = y;
}
```

Rerun `cmake ..` so the element's generated `Impl` class picks up the new method, then forward the call from it to the OpenCV class, following the pattern of the generated code. Because `setText` is called from the API thread while `process` runs on the media thread, a production module should protect those fields with a mutex.

## Build, package and install

Build from the `build` directory:

```bash
cmake ..
make
```

For anything beyond local testing, build a Debian package from the module's root directory, so the module installs, upgrades and uninstalls cleanly on the media server:

```bash
cd ..
dpkg-buildpackage -b -uc -us
sudo dpkg -i ../*.deb
```

Check that Kurento sees it, then restart the server:

```bash
kurento-media-server --list
sudo systemctl restart kurento-media-server
```

The old guide also documented a `-fPIC` linker error that needed a manual CMake edit. The generated build files for current Kurento handle that, so only chase it if you see the `relocation R_X86_64_32` error yourself.

### The libgtk2.0-dev and cvShowImage error

A run that stops with "install libgtk2.0-dev and pkg-config, then re-run cmake or configure script in function 'cvShowImage'" means the OpenCV in use was built without a GUI backend. It comes from calling a display function such as `cv::imshow` inside `process`. A media server has no screen, so there is nothing to display to. Remove the call and write your changes back into `mat`, or save a frame with `cv::imwrite` while debugging. Installing `libgtk2.0-dev` and rebuilding OpenCV silences the message, but it is the wrong fix on a server.

## Use it from JavaScript

Generate the JavaScript client from the same interface file:

```bash
cd build
cmake -DGENERATE_JS_CLIENT_PROJECT=TRUE ..
```

The client project appears in `build/js`. Its `package.json` contains the module's package name, and `lib/index.js` shows the exported class name. Install it into your Node.js application, register it with the Kurento client, and create the filter by combining the module name with the class name:

```javascript
const kurento = require('kurento-client');
kurento.register('kurento-module-helloworld'); // name from build/js/package.json

const webRtcEndpoint = await pipeline.create('WebRtcEndpoint');
const filter = await pipeline.create('helloworld.HelloWorld');

await webRtcEndpoint.connect(filter);
await filter.connect(webRtcEndpoint);

await filter.setText('Hello from Meetrix', 120, 80);
```

A quick way to try it is one of Kurento's Node.js tutorials that already uses a filter, such as the chroma example: swap its filter for yours and remove the chroma-specific calls. When the page loads and you see your text over your camera image, the whole chain from C++ to browser works.

If the filter works locally but not for remote users, the problem is almost always networking rather than the module. [Setting up a Coturn TURN server](https://meetrix.io/blogs/install-coturn-ubuntu/) covers what Kurento needs behind NAT, and [the open source WebRTC media server comparison](https://meetrix.io/blogs/best-open-source-webrtc-media-servers-2026/) is worth a look if you are still deciding whether Kurento's processing model fits your project.

## Frequently Asked Questions

What is a Kurento OpenCV filter?

A custom Kurento module that receives every video frame as an OpenCV cv::Mat, lets your C++ code modify it, and passes the result down the media pipeline. It is the simplest way to add computer vision, overlays or image processing to live WebRTC streams in Kurento.

Can I build a Kurento module on a different Ubuntu version than the server?

Don't. Kurento's documentation warns that modules must be built on the same system Kurento was built for, because of C++ ABI compatibility. For Kurento 7.3 that means building on Ubuntu 24.04, the same release your media server runs.

How do I check that Kurento loaded my module?

Run kurento-media-server --list after installing the module package. Your module's name should appear in the list of available modules. If it doesn't, the library is not in Kurento's module path or failed to load, and the server log says why.

Does an OpenCV filter slow down the video?

It can. Your process function runs for every frame, and the frames must be decoded before and re-encoded after the filter. Keep per-frame work small, avoid allocating large buffers on each call, and watch CPU as you add streams.

How do I fix the libgtk2.0-dev error in cvShowImage?

Stop calling cv::imshow from your filter. The message means OpenCV was built without a GUI backend, and a media server has no display anyway. Modify the frame in place, or write it out with cv::imwrite while debugging.

Meetrix Store

Jitsi Meet

Self-hosted video calls for 50 to 500 users

[Deploy it](https://meetrix.io/store/jitsi-meet/)

Meetrix Store New

Deploy what this guide covers, pre-configured.

-    [Jitsi Meet Self-hosted video calls for 50 to 500 users](https://meetrix.io/store/jitsi-meet/)
-    [Coturn TURN/STUN for WebRTC, no per-minute relay fees](https://meetrix.io/store/coturn/)
-    [Mattermost Team chat, a self-hosted Slack alternative](https://meetrix.io/store/mattermost/)
-    [RustDesk Remote desktop AMI, a TeamViewer alternative](https://meetrix.io/store/rustdesk/)
-    [Supabase Postgres, Auth, Storage and Realtime, self-hosted](https://meetrix.io/store/supabase/)
-    [OpenVPN Encrypted remote access, no per-user fees](https://meetrix.io/store/openvpn/)

[Browse all products](https://meetrix.io/store/)
