├── .formatter.exs ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── c_src ├── erl_cv_nif.cpp ├── erl_cv_util.cpp ├── erl_cv_util.hpp ├── queue.cpp └── queue.hpp ├── config └── config.exs ├── lib ├── erl_cv_nif.ex ├── open_cv.ex └── open_cv │ ├── util.ex │ └── video_capture.ex ├── mix.exs ├── mix.lock └── test ├── open_cv_test.exs └── test_helper.exs /.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where 3rd-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | opencv-*.tar 24 | 25 | /priv 26 | *.so 27 | /.vscode 28 | *.jpg -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Connor Rigby 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ifeq ($(ERL_EI_INCLUDE_DIR),) 2 | $(error ERL_EI_INCLUDE_DIR not set. Invoke via mix) 3 | endif 4 | 5 | CFLAGS += -Wall -Wextra -fPIC -O2 -I$(ERL_EI_INCLUDE_DIR) 6 | LDFLAGS += -fPIC -shared -L$(ERL_EI_LIBDIR) -lopencv_core -lopencv_videoio 7 | 8 | ifeq ($(MIX_TARGET),host) 9 | CFLAGS += -I/usr/include/opencv4/ 10 | else 11 | CFLAGS += $(ERL_CFLAGS) 12 | LDFLAGS += $(ERL_LDFLAGS) 13 | endif 14 | 15 | .DEFAULT_GOAL: all 16 | .PHONY: all clean 17 | 18 | all: priv priv/erl_cv_nif.so 19 | 20 | priv: 21 | mkdir -p priv 22 | 23 | priv/erl_cv_nif.so: c_src/erl_cv_util.cpp c_src/erl_cv_nif.cpp c_src/queue.cpp 24 | $(CXX) $(CFLAGS) $(LDFLAGS) c_src/erl_cv_util.cpp c_src/erl_cv_nif.cpp c_src/queue.cpp -o priv/erl_cv_nif.so 25 | 26 | clean: 27 | $(RM) priv/erl_cv_nif.so -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenCv 2 | 3 | OpenCv NIF Bindings for Erlang/Elixir. 4 | 5 | ## Current status 6 | 7 | Currently almost nothing works. Opening a video device and capturing jpeg frames 8 | works currently. 9 | 10 | ## Usage 11 | 12 | Capturing a jpeg encoded frame frame can be done by: 13 | 14 | ```elixir 15 | Interactive Elixir (1.7.3) - press Ctrl+C to exit (type h() ENTER for help) 16 | iex(1)> {:ok, conn} = OpenCv.new() 17 | {:ok, #Reference<0.1511271170.1095630852.139975>} 18 | iex(2)> {:ok, cap} = OpenCv.VideoCapture.open(conn, '/dev/video0') 19 | {:ok, #Reference<0.1511271170.1095630848.138924>} 20 | iex(3)> true = OpenCv.VideoCapture.is_opened(conn, cap) 21 | true 22 | iex(4)> {:ok, frame} = OpenCv.VideoCapture.read(conn, cap) 23 | {:ok, #Reference<0.1511271170.1095630848.138925>} 24 | iex(5)> jpg = OpenCv.imencode(conn, frame, '.jpg', []) 25 | <<255, 216, 255, 224, 0, 16, 74, 70, 73, 70, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 255, 26 | 219, 0, 67, 0, 2, 1, 1, 1, 1, 1, 2, 1, 1, 1, 2, 2, 2, 2, 2, 4, 3, 2, 2, 2, 2, 27 | 5, 4, 4, 3, ...>> 28 | iex(6)> File.write("img.jpg", jpg) 29 | :ok 30 | 31 | ``` 32 | 33 | ## Building 34 | 35 | This currently supports opencv3 and opencv4. It may support opencv2, but I have 36 | not tested. Nerves builds are currently supported given you have a 37 | system that has opencv installed. [this](https://github.com/FarmBot-Labs/farmbot_system_rpi3) 38 | system has opencv 3 installed. I've only tested build on linux, and it is likely 39 | that paths for the Makefile may be wrong. 40 | 41 | ## Installation 42 | 43 | To pull in this package directly from GitHub, amend your list of 44 | dependencies in `mix.exs` as follows: 45 | 46 | ```elixir 47 | def deps do 48 | [ 49 | {:open_cv, github: "ConnorRigby/elixir-opencv", branch: "master"} 50 | ] 51 | end 52 | ``` 53 | 54 | If [available in Hex](https://hex.pm/docs/publish), the package can be installed 55 | by adding `open_cv` to your list of dependencies in `mix.exs`: 56 | 57 | ```elixir 58 | def deps do 59 | [ 60 | {:open_cv, "~> 0.1.0"} 61 | ] 62 | end 63 | ``` 64 | 65 | Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) 66 | and published on [HexDocs](https://hexdocs.pm). Once published, the docs can 67 | be found at [https://hexdocs.pm/open_cv](https://hexdocs.pm/open_cv). 68 | -------------------------------------------------------------------------------- /c_src/erl_cv_nif.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 - 2017 Maas-Maarten Zeeman 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | #include 19 | #include 20 | 21 | #include "erl_nif.h" 22 | #include "erl_cv_util.hpp" 23 | #include "queue.hpp" 24 | 25 | #include "opencv2/opencv.hpp" 26 | 27 | #define MAX_PATHNAME 512 28 | 29 | static ErlNifResourceType *erl_cv_type = NULL; 30 | typedef struct { 31 | ErlNifTid tid; 32 | ErlNifThreadOpts* opts; 33 | ErlNifPid notification_pid; 34 | queue *commands; 35 | } erl_cv_connection; 36 | 37 | static ErlNifResourceType *erl_cv_mat_type = NULL; 38 | typedef struct { 39 | cv::Mat* mat; 40 | } erl_cv_mat; 41 | 42 | static ErlNifResourceType *erl_cv_video_capture_type = NULL; 43 | typedef struct { 44 | cv::VideoCapture* cap; 45 | } erl_cv_video_capture; 46 | 47 | typedef enum { 48 | cmd_unknown, 49 | cmd_stop, 50 | cmd_video_capture_open, 51 | cmd_video_capture_close, 52 | cmd_video_capture_is_opened, 53 | cmd_video_capture_grab, 54 | cmd_video_capture_retrieve, 55 | cmd_video_capture_read, 56 | cmd_video_capture_get, 57 | cmd_video_capture_set, 58 | cmd_imencode, 59 | cmd_new_mat, 60 | } command_type; 61 | 62 | typedef struct { 63 | command_type type; 64 | 65 | ErlNifEnv *env; 66 | ERL_NIF_TERM ref; 67 | ErlNifPid pid; 68 | ERL_NIF_TERM arg; 69 | } erl_cv_command; 70 | 71 | static ERL_NIF_TERM atom_erl_cv; 72 | 73 | static ERL_NIF_TERM push_command(ErlNifEnv *env, erl_cv_connection *conn, erl_cv_command *cmd); 74 | 75 | static void 76 | command_destroy(void *obj) 77 | { 78 | erl_cv_command *cmd = (erl_cv_command *) obj; 79 | 80 | if(cmd->env != NULL) 81 | enif_free_env(cmd->env); 82 | 83 | enif_free(cmd); 84 | } 85 | 86 | static erl_cv_command* 87 | command_create() 88 | { 89 | erl_cv_command *cmd = (erl_cv_command *) enif_alloc(sizeof(erl_cv_command)); 90 | if(cmd == NULL) 91 | return NULL; 92 | 93 | cmd->env = enif_alloc_env(); 94 | if(cmd->env == NULL) { 95 | command_destroy(cmd); 96 | return NULL; 97 | } 98 | 99 | cmd->type = cmd_unknown; 100 | cmd->ref = 0; 101 | cmd->arg = 0; 102 | return cmd; 103 | } 104 | 105 | static ERL_NIF_TERM 106 | do_vc_open(ErlNifEnv *env, erl_cv_connection*, const ERL_NIF_TERM arg) 107 | { 108 | ERL_NIF_TERM ret; 109 | char filename[MAX_PATHNAME]; 110 | unsigned int size; 111 | erl_cv_video_capture* ecap; 112 | 113 | size = enif_get_string(env, arg, filename, MAX_PATHNAME, ERL_NIF_LATIN1); 114 | if(size <= 0) 115 | return make_error_tuple(env, "invalid_filename"); 116 | 117 | ecap = (erl_cv_video_capture*) enif_alloc_resource(erl_cv_video_capture_type, sizeof(erl_cv_video_capture)); 118 | if(!ecap) 119 | return make_error_tuple(env, "no_memory"); 120 | ecap->cap = new cv::VideoCapture(filename); 121 | 122 | ret = enif_make_resource(env, ecap); 123 | enif_release_resource(ecap); 124 | return make_ok_tuple(env, ret); 125 | } 126 | 127 | static ERL_NIF_TERM 128 | do_vc_close(ErlNifEnv *env, erl_cv_connection*, const ERL_NIF_TERM arg) 129 | { 130 | erl_cv_video_capture* ecap; 131 | if(!enif_get_resource(env, arg, erl_cv_video_capture_type, (void **) &ecap)) 132 | return enif_make_badarg(env); 133 | 134 | if(ecap->cap) { 135 | delete ecap->cap; 136 | } 137 | 138 | return make_atom(env, "ok"); 139 | } 140 | 141 | static ERL_NIF_TERM 142 | do_vc_is_opened(ErlNifEnv *env, erl_cv_connection*, const ERL_NIF_TERM arg) 143 | { 144 | erl_cv_video_capture* ecap; 145 | if(!enif_get_resource(env, arg, erl_cv_video_capture_type, (void **) &ecap)) 146 | return enif_make_badarg(env); 147 | if(ecap->cap == NULL) 148 | return make_error_tuple(env, "not_open"); 149 | return ecap->cap->isOpened() ? enif_make_atom(env, "true") : enif_make_atom(env, "false"); 150 | } 151 | 152 | static ERL_NIF_TERM 153 | do_vc_grab(ErlNifEnv *env, erl_cv_connection*, const ERL_NIF_TERM arg) 154 | { 155 | erl_cv_video_capture* ecap; 156 | if(!enif_get_resource(env, arg, erl_cv_video_capture_type, (void **) &ecap)) 157 | return enif_make_badarg(env); 158 | 159 | if(ecap->cap == NULL) 160 | return make_error_tuple(env, "not_open"); 161 | 162 | if(!ecap->cap->isOpened()) 163 | return make_error_tuple(env, "not_open"); 164 | 165 | return ecap->cap->grab() ? make_atom(env, "true") : make_atom(env, "false"); 166 | } 167 | 168 | static ERL_NIF_TERM 169 | do_vc_retrieve(ErlNifEnv *env, erl_cv_connection*, const ERL_NIF_TERM arg) 170 | { 171 | erl_cv_video_capture *ecap; 172 | erl_cv_mat *emat; 173 | int flag; 174 | int argc; 175 | const ERL_NIF_TERM *argv; 176 | if(!enif_get_tuple(env, arg, &argc, &argv)) 177 | return enif_make_badarg(env); 178 | 179 | if(argc != 2) 180 | return enif_make_badarg(env); 181 | 182 | if(!enif_get_resource(env, argv[0], erl_cv_video_capture_type, (void **) &ecap)) 183 | return enif_make_badarg(env); 184 | 185 | if(!enif_get_int(env, argv[1], &flag)) 186 | return make_error_tuple(env, "invalid_flag"); 187 | 188 | ERL_NIF_TERM emat_term; 189 | emat = (erl_cv_mat*) enif_alloc_resource(erl_cv_mat_type, sizeof(erl_cv_mat)); 190 | if(!emat) 191 | return make_error_tuple(env, "no_memory"); 192 | emat->mat = new cv::Mat(); 193 | 194 | if(ecap->cap == NULL) 195 | return make_error_tuple(env, "not_open"); 196 | 197 | if(!ecap->cap->isOpened()) 198 | return make_error_tuple(env, "not_open"); 199 | 200 | if (!ecap->cap->retrieve(*emat->mat, flag)) 201 | return make_atom(env, "false"); 202 | 203 | if(emat->mat->empty()) { 204 | emat_term = make_atom(env, "nil"); 205 | } else { 206 | emat_term = enif_make_resource(env, emat); 207 | } 208 | enif_release_resource(emat); 209 | return make_ok_tuple(env, emat_term); 210 | } 211 | 212 | static ERL_NIF_TERM 213 | do_vc_read(ErlNifEnv *env, erl_cv_connection*, const ERL_NIF_TERM arg) 214 | { 215 | erl_cv_video_capture *ecap; 216 | erl_cv_mat *emat; 217 | ERL_NIF_TERM ret; 218 | if(!enif_get_resource(env, arg, erl_cv_video_capture_type, (void **) &ecap)) 219 | return enif_make_badarg(env); 220 | 221 | emat = (erl_cv_mat*) enif_alloc_resource(erl_cv_mat_type, sizeof(erl_cv_mat)); 222 | 223 | if(!emat) 224 | return make_error_tuple(env, "no_memory"); 225 | emat->mat = new cv::Mat(); 226 | 227 | if(ecap->cap == NULL) 228 | return make_error_tuple(env, "not_open"); 229 | 230 | if(!ecap->cap->isOpened()) 231 | return make_error_tuple(env, "not_open"); 232 | 233 | if(!ecap->cap->read(*emat->mat)) 234 | return make_atom(env, "false"); 235 | 236 | if(emat->mat->empty()) { 237 | ret = make_atom(env, "nil"); 238 | } else { 239 | ret = enif_make_resource(env, emat); 240 | } 241 | enif_release_resource(emat); 242 | return make_ok_tuple(env, ret); 243 | } 244 | 245 | static ERL_NIF_TERM 246 | do_vc_get(ErlNifEnv *env, erl_cv_connection*, const ERL_NIF_TERM arg) 247 | { 248 | erl_cv_video_capture *ecap; 249 | int argc, propid; 250 | double value; 251 | const ERL_NIF_TERM *argv; 252 | 253 | if(!enif_get_tuple(env, arg, &argc, &argv)) 254 | return enif_make_badarg(env); 255 | 256 | if(argc != 2) 257 | return enif_make_badarg(env); 258 | 259 | enif_fprintf(stderr, "%T ????\r\n", argv[0]); 260 | if(!enif_get_resource(env, argv[0], erl_cv_video_capture_type, (void **) &ecap)) 261 | return enif_make_badarg(env); 262 | 263 | if(ecap->cap == NULL) 264 | return make_error_tuple(env, "not_open"); 265 | 266 | if(!enif_get_int(env, argv[1], &propid)) 267 | return make_error_tuple(env, "invalid_propid"); 268 | 269 | value = ecap->cap->get(propid); 270 | return enif_make_double(env, value); 271 | } 272 | 273 | static ERL_NIF_TERM 274 | do_vc_set(ErlNifEnv *env, erl_cv_connection*, const ERL_NIF_TERM arg) 275 | { 276 | erl_cv_video_capture *ecap; 277 | int argc, propid; 278 | double value; 279 | const ERL_NIF_TERM* argv; 280 | 281 | if(!enif_get_tuple(env, arg, &argc, &argv)) 282 | return enif_make_badarg(env); 283 | 284 | if(argc != 3) 285 | return enif_make_badarg(env); 286 | 287 | if(!enif_get_resource(env, argv[0], erl_cv_video_capture_type, (void **) &ecap)) 288 | return enif_make_badarg(env); 289 | 290 | if(ecap->cap == NULL) 291 | return make_error_tuple(env, "not_open"); 292 | 293 | if(!enif_get_int(env, argv[1], &propid)) 294 | return make_error_tuple(env, "invalid_propid"); 295 | 296 | if(!enif_get_double(env, argv[2], &value)) 297 | return make_error_tuple(env, "invalid_propvalue"); 298 | 299 | return ecap->cap->set(propid, value) ? enif_make_atom(env, "true") : enif_make_atom(env, "false"); 300 | } 301 | 302 | static ERL_NIF_TERM 303 | do_imencode(ErlNifEnv *env, erl_cv_connection*, const ERL_NIF_TERM arg) 304 | { 305 | erl_cv_mat *inemat; 306 | 307 | int strSize; 308 | unsigned int listLength; 309 | int argc; 310 | const ERL_NIF_TERM* argv; 311 | 312 | if(!enif_get_tuple(env, arg, &argc, &argv)) 313 | return enif_make_badarg(env); 314 | 315 | if(argc != 3) 316 | return enif_make_badarg(env); 317 | 318 | // emat resource 319 | if(!enif_get_resource(env, argv[0], erl_cv_mat_type, (void **) &inemat)) 320 | return enif_make_badarg(env); 321 | 322 | // encoding extension 323 | if(!enif_get_list_length(env, argv[1], &listLength)) 324 | return make_error_tuple(env, "invalid_string"); 325 | char ext[listLength+1]; 326 | strSize = enif_get_string(env, argv[1], ext, listLength+1, ERL_NIF_LATIN1); 327 | 328 | // encoding params 329 | if(!enif_get_list_length(env, argv[2], &listLength)) 330 | return make_error_tuple(env, "invalid_params"); 331 | std::vector params; 332 | ERL_NIF_TERM head; 333 | ERL_NIF_TERM tail; 334 | for(unsigned int i = 0; i buff; 348 | cv::imencode(ext, *inemat->mat, buff, params); 349 | return make_binary(env, buff.data(), buff.size()); 350 | } 351 | 352 | static ERL_NIF_TERM 353 | do_new_mat(ErlNifEnv *env, erl_cv_connection*, const ERL_NIF_TERM arg) 354 | { 355 | erl_cv_mat *inemat; 356 | erl_cv_mat *outemat; 357 | ERL_NIF_TERM ret; 358 | 359 | outemat = (erl_cv_mat*) enif_alloc_resource(erl_cv_mat_type, sizeof(erl_cv_mat)); 360 | if(!outemat) 361 | return make_error_tuple(env, "no_memory"); 362 | 363 | if(!enif_get_resource(env, arg, erl_cv_mat_type, (void **) &inemat)) { 364 | cv::Mat inMat = *inemat->mat; 365 | outemat->mat = new cv::Mat(inMat); 366 | } else { 367 | outemat->mat = new cv::Mat(); 368 | } 369 | ret = enif_make_resource(env, outemat); 370 | enif_release_resource(outemat); 371 | return make_ok_tuple(env, ret); 372 | } 373 | 374 | static ERL_NIF_TERM 375 | evaluate_command(erl_cv_command *cmd, erl_cv_connection *conn) 376 | { 377 | switch(cmd->type) { 378 | // Video Capture 379 | case cmd_video_capture_open: 380 | return do_vc_open(cmd->env, conn, cmd->arg); 381 | case cmd_video_capture_close: 382 | return do_vc_close(cmd->env, conn, cmd->arg); 383 | case cmd_video_capture_is_opened: 384 | return do_vc_is_opened(cmd->env, conn, cmd->arg); 385 | case cmd_video_capture_grab: 386 | return do_vc_grab(cmd->env, conn, cmd->arg); 387 | case cmd_video_capture_retrieve: 388 | return do_vc_retrieve(cmd->env, conn, cmd->arg); 389 | case cmd_video_capture_read: 390 | return do_vc_read(cmd->env, conn, cmd->arg); 391 | case cmd_video_capture_get: 392 | return do_vc_get(cmd->env, conn, cmd->arg); 393 | case cmd_video_capture_set: 394 | return do_vc_set(cmd->env, conn, cmd->arg); 395 | 396 | // Utility 397 | case cmd_imencode: 398 | return do_imencode(cmd->env, conn, cmd->arg); 399 | case cmd_new_mat: 400 | return do_new_mat(cmd->env, conn, cmd->arg); 401 | default: 402 | return make_error_tuple(cmd->env, "invalid_command"); 403 | } 404 | } 405 | 406 | static ERL_NIF_TERM 407 | push_command(ErlNifEnv *env, erl_cv_connection *conn, erl_cv_command *cmd) { 408 | if(!queue_push(conn->commands, cmd)) 409 | return make_error_tuple(env, "command_push_failed"); 410 | 411 | return make_atom(env, "ok"); 412 | } 413 | 414 | static ERL_NIF_TERM 415 | make_answer(erl_cv_command *cmd, ERL_NIF_TERM answer) 416 | { 417 | return enif_make_tuple3(cmd->env, atom_erl_cv, cmd->ref, answer); 418 | } 419 | 420 | static void * 421 | erl_cv_connection_run(void *arg) 422 | { 423 | erl_cv_connection *conn = (erl_cv_connection *) arg; 424 | erl_cv_command *cmd; 425 | int continue_running = 1; 426 | 427 | while(continue_running) { 428 | cmd = (erl_cv_command*)queue_pop(conn->commands); 429 | 430 | if(cmd->type == cmd_stop) { 431 | continue_running = 0; 432 | } else { 433 | enif_send(NULL, &cmd->pid, cmd->env, make_answer(cmd, evaluate_command(cmd, conn))); 434 | } 435 | 436 | command_destroy(cmd); 437 | } 438 | 439 | return NULL; 440 | } 441 | 442 | /* 443 | * Start the processing thread 444 | */ 445 | static ERL_NIF_TERM 446 | erl_cv_start(ErlNifEnv* env, int, const ERL_NIF_TERM[]) 447 | { 448 | erl_cv_connection *conn; 449 | ERL_NIF_TERM conn_resource; 450 | 451 | /* Initialize the resource */ 452 | conn = (erl_cv_connection *) enif_alloc_resource(erl_cv_type, sizeof(erl_cv_connection)); 453 | if(!conn) 454 | return make_error_tuple(env, "no_memory"); 455 | 456 | /* Create command queue */ 457 | conn->commands = queue_create(); 458 | if(!conn->commands) { 459 | enif_release_resource(conn); 460 | return make_error_tuple(env, "command_queue_create_failed"); 461 | } 462 | 463 | /* Start command processing thread */ 464 | conn->opts = enif_thread_opts_create((char*) "erl_video_capture_thread_opts"); 465 | if(enif_thread_create((char*) "erl_cv_connection", &conn->tid, erl_cv_connection_run, conn, conn->opts) != 0) { 466 | enif_release_resource(conn); 467 | return make_error_tuple(env, (char*)"thread_create_failed"); 468 | } 469 | 470 | conn_resource = enif_make_resource(env, conn); 471 | enif_release_resource(conn); 472 | 473 | return make_ok_tuple(env, conn_resource); 474 | } 475 | 476 | static ERL_NIF_TERM 477 | erl_video_capture_open(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 478 | { 479 | erl_cv_connection *conn; 480 | erl_cv_command *cmd = NULL; 481 | ErlNifPid pid; 482 | 483 | if(argc != 4) 484 | return enif_make_badarg(env); 485 | if(!enif_get_resource(env, argv[0], erl_cv_type, (void **) &conn)) 486 | return enif_make_badarg(env); 487 | if(!enif_is_ref(env, argv[1])) 488 | return make_error_tuple(env, "invalid_ref"); 489 | if(!enif_get_local_pid(env, argv[2], &pid)) 490 | return make_error_tuple(env, "invalid_pid"); 491 | if(!enif_is_list(env, argv[3])) 492 | return make_error_tuple(env, "invalid_arg"); 493 | 494 | /* Note, no check is made for the type of the argument */ 495 | cmd = command_create(); 496 | if(!cmd) 497 | return make_error_tuple(env, "command_create_failed"); 498 | 499 | cmd->type = cmd_video_capture_open; 500 | cmd->ref = enif_make_copy(cmd->env, argv[1]); 501 | cmd->pid = pid; 502 | cmd->arg = enif_make_copy(cmd->env, argv[3]); 503 | 504 | return push_command(env, conn, cmd); 505 | } 506 | 507 | static ERL_NIF_TERM 508 | erl_video_capture_close(ErlNifEnv *env, int, const ERL_NIF_TERM argv[]) 509 | { 510 | erl_cv_connection *conn; 511 | erl_cv_command *cmd = NULL; 512 | ErlNifPid pid; 513 | 514 | if(!enif_get_resource(env, argv[0], erl_cv_type, (void **) &conn)) 515 | return enif_make_badarg(env); 516 | if(!enif_is_ref(env, argv[1])) 517 | return make_error_tuple(env, "invalid_ref"); 518 | if(!enif_get_local_pid(env, argv[2], &pid)) 519 | return make_error_tuple(env, "invalid_pid"); 520 | if(!enif_is_ref(env, argv[3])) 521 | return make_error_tuple(env, "invalid_arg"); 522 | 523 | cmd = command_create(); 524 | if(!cmd) 525 | return make_error_tuple(env, "command_create_failed"); 526 | 527 | cmd->type = cmd_video_capture_close; 528 | cmd->ref = enif_make_copy(cmd->env, argv[1]); 529 | cmd->pid = pid; 530 | cmd->arg = enif_make_copy(cmd->env, argv[3]); 531 | return push_command(env, conn, cmd); 532 | } 533 | 534 | /** 535 | * Returns true if video capturing has been initialized already. 536 | If the previous call to VideoCapture constructor or VideoCapture::open() succeeded, the method returns true. 537 | * https://docs.opencv.org/3.4.5/d8/dfe/classcv_1_1VideoCapture.html#a9d2ca36789e7fcfe7a7be3b328038585 538 | */ 539 | static ERL_NIF_TERM 540 | erl_video_capture_is_opened(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 541 | { 542 | erl_cv_connection *conn; 543 | erl_cv_command *cmd = NULL; 544 | ErlNifPid pid; 545 | 546 | if(argc != 4) 547 | return enif_make_badarg(env); 548 | if(!enif_get_resource(env, argv[0], erl_cv_type, (void **) &conn)) 549 | return enif_make_badarg(env); 550 | if(!enif_is_ref(env, argv[1])) 551 | return make_error_tuple(env, "invalid_ref"); 552 | if(!enif_get_local_pid(env, argv[2], &pid)) 553 | return make_error_tuple(env, "invalid_pid"); 554 | if(!enif_is_ref(env, argv[3])) 555 | return make_error_tuple(env, "invalid_arg"); 556 | 557 | cmd = command_create(); 558 | if(!cmd) 559 | return make_error_tuple(env, "command_create_failed"); 560 | 561 | cmd->type = cmd_video_capture_is_opened; 562 | cmd->ref = enif_make_copy(cmd->env, argv[1]); 563 | cmd->pid = pid; 564 | cmd->arg = enif_make_copy(cmd->env, argv[3]); 565 | return push_command(env, conn, cmd); 566 | } 567 | 568 | /** 569 | * Grabs the next frame from video file or capturing device. 570 | * https://docs.opencv.org/3.4.5/d8/dfe/classcv_1_1VideoCapture.html#ae38c2a053d39d6b20c9c649e08ff0146 571 | */ 572 | static ERL_NIF_TERM 573 | erl_video_capture_grab(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 574 | { 575 | erl_cv_connection *conn; 576 | erl_cv_command *cmd = NULL; 577 | ErlNifPid pid; 578 | 579 | if(argc != 4) 580 | return enif_make_badarg(env); 581 | if(!enif_get_resource(env, argv[0], erl_cv_type, (void **) &conn)) 582 | return enif_make_badarg(env); 583 | if(!enif_is_ref(env, argv[1])) 584 | return make_error_tuple(env, "invalid_ref"); 585 | if(!enif_get_local_pid(env, argv[2], &pid)) 586 | return make_error_tuple(env, "invalid_pid"); 587 | if(!enif_is_ref(env, argv[3])) 588 | return make_error_tuple(env, "invalid_arg"); 589 | 590 | cmd = command_create(); 591 | if(!cmd) 592 | return make_error_tuple(env, "command_create_failed"); 593 | 594 | cmd->type = cmd_video_capture_grab; 595 | cmd->ref = enif_make_copy(cmd->env, argv[1]); 596 | cmd->pid = pid; 597 | cmd->arg = enif_make_copy(cmd->env, argv[3]); 598 | return push_command(env, conn, cmd); 599 | } 600 | 601 | /** 602 | * grab and decode a frame 603 | * https://docs.opencv.org/3.2.0/d8/dfe/classcv_1_1VideoCapture.html#a9ac7f4b1cdfe624663478568486e6712 604 | */ 605 | static ERL_NIF_TERM 606 | erl_video_capture_retrieve(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 607 | { 608 | erl_cv_connection *conn; 609 | erl_cv_command *cmd = NULL; 610 | ErlNifPid pid; 611 | 612 | if(argc != 4) 613 | return enif_make_badarg(env); 614 | if(!enif_get_resource(env, argv[0], erl_cv_type, (void **) &conn)) 615 | return enif_make_badarg(env); 616 | if(!enif_is_ref(env, argv[1])) 617 | return make_error_tuple(env, "invalid_ref"); 618 | if(!enif_get_local_pid(env, argv[2], &pid)) 619 | return make_error_tuple(env, "invalid_pid"); 620 | if(!enif_is_tuple(env, argv[3])) 621 | return make_error_tuple(env, "invalid_arg"); 622 | 623 | cmd = command_create(); 624 | if(!cmd) 625 | return make_error_tuple(env, "command_create_failed"); 626 | 627 | cmd->type = cmd_video_capture_retrieve; 628 | cmd->ref = enif_make_copy(cmd->env, argv[1]); 629 | cmd->pid = pid; 630 | cmd->arg = enif_make_copy(cmd->env, argv[3]); 631 | return push_command(env, conn, cmd); 632 | } 633 | 634 | /** 635 | * Grabs, decodes and returns the next video frame. 636 | * https://docs.opencv.org/3.4.5/d8/dfe/classcv_1_1VideoCapture.html#a473055e77dd7faa4d26d686226b292c1 637 | */ 638 | static ERL_NIF_TERM 639 | erl_video_capture_read(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 640 | { 641 | erl_cv_connection *conn; 642 | erl_cv_command *cmd = NULL; 643 | ErlNifPid pid; 644 | 645 | if(argc != 4) 646 | return enif_make_badarg(env); 647 | if(!enif_get_resource(env, argv[0], erl_cv_type, (void **) &conn)) 648 | return enif_make_badarg(env); 649 | if(!enif_is_ref(env, argv[1])) 650 | return make_error_tuple(env, "invalid_ref"); 651 | if(!enif_get_local_pid(env, argv[2], &pid)) 652 | return make_error_tuple(env, "invalid_pid"); 653 | if(!enif_is_ref(env, argv[3])) 654 | return make_error_tuple(env, "invalid_arg"); 655 | 656 | cmd = command_create(); 657 | if(!cmd) 658 | return make_error_tuple(env, "command_create_failed"); 659 | 660 | cmd->type = cmd_video_capture_read; 661 | cmd->ref = enif_make_copy(cmd->env, argv[1]); 662 | cmd->pid = pid; 663 | cmd->arg = enif_make_copy(cmd->env, argv[3]); 664 | return push_command(env, conn, cmd); 665 | } 666 | 667 | /** 668 | * Returns the specified VideoCapture property. 669 | * https://docs.opencv.org/3.4.5/d8/dfe/classcv_1_1VideoCapture.html#aa6480e6972ef4c00d74814ec841a2939 670 | */ 671 | static ERL_NIF_TERM 672 | erl_video_capture_get(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 673 | { 674 | erl_cv_connection *conn; 675 | erl_cv_command *cmd = NULL; 676 | ErlNifPid pid; 677 | 678 | if(argc != 4) 679 | return enif_make_badarg(env); 680 | if(!enif_get_resource(env, argv[0], erl_cv_type, (void **) &conn)) 681 | return enif_make_badarg(env); 682 | if(!enif_is_ref(env, argv[1])) 683 | return make_error_tuple(env, "invalid_ref"); 684 | if(!enif_get_local_pid(env, argv[2], &pid)) 685 | return make_error_tuple(env, "invalid_pid"); 686 | if(!enif_is_tuple(env, argv[3])) 687 | return make_error_tuple(env, "invalid_arg"); 688 | 689 | cmd = command_create(); 690 | if(!cmd) 691 | return make_error_tuple(env, "command_create_failed"); 692 | 693 | cmd->type = cmd_video_capture_get; 694 | cmd->ref = enif_make_copy(cmd->env, argv[1]); 695 | cmd->pid = pid; 696 | cmd->arg = enif_make_copy(cmd->env, argv[3]); 697 | return push_command(env, conn, cmd); 698 | } 699 | 700 | /** 701 | * Sets a property in the VideoCapture. 702 | * https://docs.opencv.org/3.4.5/d8/dfe/classcv_1_1VideoCapture.html#a8c6d8c2d37505b5ca61ffd4bb54e9a7c 703 | */ 704 | static ERL_NIF_TERM 705 | erl_video_capture_set(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 706 | { 707 | erl_cv_connection *conn; 708 | erl_cv_command *cmd = NULL; 709 | ErlNifPid pid; 710 | 711 | if(argc != 4) 712 | return enif_make_badarg(env); 713 | if(!enif_get_resource(env, argv[0], erl_cv_type, (void **) &conn)) 714 | return enif_make_badarg(env); 715 | if(!enif_is_ref(env, argv[1])) 716 | return make_error_tuple(env, "invalid_ref"); 717 | if(!enif_get_local_pid(env, argv[2], &pid)) 718 | return make_error_tuple(env, "invalid_pid"); 719 | if(!enif_is_tuple(env, argv[3])) 720 | return make_error_tuple(env, "invalid_arg"); 721 | 722 | cmd = command_create(); 723 | if(!cmd) 724 | return make_error_tuple(env, "command_create_failed"); 725 | 726 | cmd->type = cmd_video_capture_set; 727 | cmd->ref = enif_make_copy(cmd->env, argv[1]); 728 | cmd->pid = pid; 729 | cmd->arg = enif_make_copy(cmd->env, argv[3]); 730 | return push_command(env, conn, cmd); 731 | } 732 | 733 | /** 734 | * Encode an image 735 | * https://docs.opencv.org/3.4/d4/da8/group__imgcodecs.html#ga26a67788faa58ade337f8d28ba0eb19e 736 | */ 737 | static ERL_NIF_TERM 738 | erl_cv_imencode(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 739 | { 740 | erl_cv_connection *conn; 741 | erl_cv_command *cmd = NULL; 742 | ErlNifPid pid; 743 | 744 | if(argc != 4) 745 | return enif_make_badarg(env); 746 | if(!enif_get_resource(env, argv[0], erl_cv_type, (void **) &conn)) 747 | return enif_make_badarg(env); 748 | if(!enif_is_ref(env, argv[1])) 749 | return make_error_tuple(env, "invalid_ref"); 750 | if(!enif_get_local_pid(env, argv[2], &pid)) 751 | return make_error_tuple(env, "invalid_pid"); 752 | if(!enif_is_tuple(env, argv[3])) 753 | return make_error_tuple(env, "invalid_arg"); 754 | 755 | cmd = command_create(); 756 | if(!cmd) 757 | return make_error_tuple(env, "command_create_failed"); 758 | 759 | cmd->type = cmd_imencode; 760 | cmd->ref = enif_make_copy(cmd->env, argv[1]); 761 | cmd->pid = pid; 762 | cmd->arg = enif_make_copy(cmd->env, argv[3]); 763 | return push_command(env, conn, cmd); 764 | } 765 | 766 | /** 767 | * Constructs a new Mat 768 | * https://docs.opencv.org/3.4.5/d3/d63/classcv_1_1Mat.html#af1d014cecd1510cdf580bf2ed7e5aafc 769 | */ 770 | static ERL_NIF_TERM 771 | erl_cv_new_mat(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 772 | { 773 | erl_cv_connection *conn; 774 | erl_cv_command *cmd = NULL; 775 | ErlNifPid pid; 776 | 777 | if(argc != 4) 778 | return enif_make_badarg(env); 779 | if(!enif_get_resource(env, argv[0], erl_cv_type, (void **) &conn)) 780 | return enif_make_badarg(env); 781 | if(!enif_is_ref(env, argv[1])) 782 | return make_error_tuple(env, "invalid_ref"); 783 | if(!enif_get_local_pid(env, argv[2], &pid)) 784 | return make_error_tuple(env, "invalid_pid"); 785 | if(!enif_is_ref(env, argv[3])) 786 | return make_error_tuple(env, "invalid_arg"); 787 | 788 | cmd = command_create(); 789 | if(!cmd) 790 | return make_error_tuple(env, "command_create_failed"); 791 | 792 | cmd->type = cmd_new_mat; 793 | cmd->ref = enif_make_copy(cmd->env, argv[1]); 794 | cmd->pid = pid; 795 | cmd->arg = enif_make_copy(cmd->env, argv[3]); 796 | return push_command(env, conn, cmd); 797 | } 798 | 799 | 800 | static void 801 | destruct_cv_connection(ErlNifEnv*, void *arg) 802 | { 803 | enif_fprintf(stderr, "destruct cv_conn\r\n"); 804 | erl_cv_connection *conn = (erl_cv_connection *) arg; 805 | erl_cv_command *close_cmd = command_create(); 806 | 807 | 808 | /* Send the stop command 809 | */ 810 | close_cmd->type = cmd_stop; 811 | queue_push(conn->commands, close_cmd); 812 | 813 | /* Wait for the thread to finish 814 | */ 815 | enif_thread_join(conn->tid, NULL); 816 | 817 | enif_thread_opts_destroy(conn->opts); 818 | 819 | while(queue_has_item(conn->commands)) { 820 | command_destroy(queue_pop(conn->commands)); 821 | } 822 | queue_destroy(conn->commands); 823 | } 824 | 825 | static void 826 | destruct_cv_mat(ErlNifEnv*, void *arg) 827 | { 828 | enif_fprintf(stderr, "destruct cv_mat\r\n"); 829 | erl_cv_mat *emat = (erl_cv_mat *)arg; 830 | if(emat->mat) { 831 | delete emat->mat; 832 | } 833 | } 834 | 835 | static void 836 | destruct_cv_video_capture(ErlNifEnv*, void *arg) 837 | { 838 | enif_fprintf(stderr, "destruct cv_cap\r\n"); 839 | erl_cv_video_capture *ecap = (erl_cv_video_capture *)arg; 840 | if(ecap->cap) { 841 | delete ecap->cap; 842 | } 843 | } 844 | 845 | /* 846 | * Load the nif. Initialize some stuff and such 847 | */ 848 | static int 849 | on_load(ErlNifEnv* env, void**, ERL_NIF_TERM) 850 | { 851 | ErlNifResourceType *rt; 852 | 853 | rt = enif_open_resource_type(env, "erl_cv_nif", "erl_cv_type", 854 | destruct_cv_connection, ERL_NIF_RT_CREATE, NULL); 855 | if(!rt) 856 | return -1; 857 | erl_cv_type = rt; 858 | 859 | rt = enif_open_resource_type(env, "erl_cv_nif", "erl_cv_video_capture_type", 860 | destruct_cv_video_capture, ERL_NIF_RT_CREATE, NULL); 861 | if(!rt) 862 | return -1; 863 | erl_cv_video_capture_type = rt; 864 | 865 | rt = enif_open_resource_type(env, "erl_cv_nif", "erl_cv_mat_type", 866 | destruct_cv_mat, ERL_NIF_RT_CREATE, NULL); 867 | if(!rt) 868 | return -1; 869 | erl_cv_mat_type = rt; 870 | 871 | atom_erl_cv = make_atom(env, "erl_cv_nif"); 872 | return 0; 873 | } 874 | 875 | static int on_reload(ErlNifEnv*, void**, ERL_NIF_TERM) 876 | { 877 | return 0; 878 | } 879 | 880 | static int on_upgrade(ErlNifEnv*, void**, void**, ERL_NIF_TERM) 881 | { 882 | return 0; 883 | } 884 | 885 | static ErlNifFunc nif_funcs[] = { 886 | // VideoCapture 887 | {"start", 0, erl_cv_start, 0}, 888 | {"video_capture_open", 4, erl_video_capture_open, 0}, 889 | {"video_capture_close", 4, erl_video_capture_close, 0}, 890 | {"video_capture_is_opened", 4, erl_video_capture_is_opened, 0}, 891 | {"video_capture_grab", 4, erl_video_capture_grab, 0}, 892 | {"video_capture_retreive", 4, erl_video_capture_retrieve, 0}, 893 | {"video_capture_read", 4, erl_video_capture_read, 0}, 894 | {"video_capture_get", 4, erl_video_capture_get, 0}, 895 | {"video_capture_set", 4, erl_video_capture_set, 0}, 896 | 897 | // Utility 898 | {"imencode", 4, erl_cv_imencode, 0}, 899 | {"new_mat", 4, erl_cv_new_mat, 0} 900 | }; 901 | ERL_NIF_INIT(erl_cv_nif, nif_funcs, on_load, on_reload, on_upgrade, NULL); 902 | -------------------------------------------------------------------------------- /c_src/erl_cv_util.cpp: -------------------------------------------------------------------------------- 1 | #include "erl_nif.h" 2 | #include "erl_cv_util.hpp" 3 | 4 | ERL_NIF_TERM make_atom(ErlNifEnv *env, const char *atom_name) 5 | { 6 | ERL_NIF_TERM atom; 7 | 8 | if(enif_make_existing_atom(env, atom_name, &atom, ERL_NIF_LATIN1)) 9 | return atom; 10 | 11 | return enif_make_atom(env, atom_name); 12 | } 13 | 14 | ERL_NIF_TERM make_ok_tuple(ErlNifEnv *env, ERL_NIF_TERM value) 15 | { 16 | return enif_make_tuple2(env, make_atom(env, "ok"), value); 17 | } 18 | 19 | ERL_NIF_TERM make_error_tuple(ErlNifEnv *env, const char *reason) 20 | { 21 | return enif_make_tuple2(env, make_atom(env, "error"), make_atom(env, reason)); 22 | } 23 | 24 | ERL_NIF_TERM make_binary(ErlNifEnv *env, const void *bytes, unsigned int size) 25 | { 26 | ErlNifBinary blob; 27 | ERL_NIF_TERM term; 28 | 29 | if(!enif_alloc_binary(size, &blob)) { 30 | /* TODO: fix this */ 31 | return make_atom(env, "error"); 32 | } 33 | 34 | memcpy(blob.data, bytes, size); 35 | term = enif_make_binary(env, &blob); 36 | enif_release_binary(&blob); 37 | 38 | return term; 39 | } -------------------------------------------------------------------------------- /c_src/erl_cv_util.hpp: -------------------------------------------------------------------------------- 1 | #ifndef ERL_CV_UTIL_H 2 | #define ERL_CV_UTIL_H 3 | 4 | #include "erl_nif.h" 5 | #include 6 | #include 7 | 8 | ERL_NIF_TERM make_atom(ErlNifEnv*, const char*); 9 | ERL_NIF_TERM make_ok_tuple(ErlNifEnv*, ERL_NIF_TERM); 10 | ERL_NIF_TERM make_error_tuple(ErlNifEnv*, const char*); 11 | ERL_NIF_TERM make_binary(ErlNifEnv*, const void*, unsigned int); 12 | 13 | #endif -------------------------------------------------------------------------------- /c_src/queue.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of Emonk released under the MIT license. 2 | // See the LICENSE file for more information. 3 | 4 | /* Adapted by: Maas-Maarten Zeeman 7 | #include 8 | 9 | #include "queue.hpp" 10 | 11 | struct qitem_t 12 | { 13 | struct qitem_t* next; 14 | void* data; 15 | }; 16 | 17 | typedef struct qitem_t qitem; 18 | 19 | struct queue_t 20 | { 21 | ErlNifMutex *lock; 22 | ErlNifCond *cond; 23 | qitem *head; 24 | qitem *tail; 25 | void *message; 26 | int length; 27 | }; 28 | 29 | queue * 30 | queue_create() 31 | { 32 | queue *ret; 33 | 34 | ret = (queue *) enif_alloc(sizeof(struct queue_t)); 35 | if(ret == NULL) goto error; 36 | 37 | ret->lock = NULL; 38 | ret->cond = NULL; 39 | ret->head = NULL; 40 | ret->tail = NULL; 41 | ret->message = NULL; 42 | ret->length = 0; 43 | 44 | ret->lock = enif_mutex_create((char*)"queue_lock"); 45 | if(ret->lock == NULL) goto error; 46 | 47 | ret->cond = enif_cond_create((char*)"queue_cond"); 48 | if(ret->cond == NULL) goto error; 49 | 50 | return ret; 51 | 52 | error: 53 | if(ret->lock != NULL) 54 | enif_mutex_destroy(ret->lock); 55 | if(ret->cond != NULL) 56 | enif_cond_destroy(ret->cond); 57 | if(ret != NULL) 58 | enif_free(ret); 59 | return NULL; 60 | } 61 | 62 | void 63 | queue_destroy(queue *queue) 64 | { 65 | ErlNifMutex *lock; 66 | ErlNifCond *cond; 67 | int length; 68 | 69 | enif_mutex_lock(queue->lock); 70 | lock = queue->lock; 71 | cond = queue->cond; 72 | length = queue->length; 73 | 74 | queue->lock = NULL; 75 | queue->cond = NULL; 76 | queue->head = NULL; 77 | queue->tail = NULL; 78 | queue->length = -1; 79 | enif_mutex_unlock(lock); 80 | 81 | assert(length == 0 && "Attempting to destroy a non-empty queue."); 82 | enif_cond_destroy(cond); 83 | enif_mutex_destroy(lock); 84 | enif_free(queue); 85 | } 86 | 87 | int 88 | queue_has_item(queue *queue) 89 | { 90 | int ret; 91 | 92 | enif_mutex_lock(queue->lock); 93 | ret = (queue->head != NULL); 94 | enif_mutex_unlock(queue->lock); 95 | 96 | return ret; 97 | } 98 | 99 | int 100 | queue_push(queue *queue, void *item) 101 | { 102 | qitem * entry = (qitem *) enif_alloc(sizeof(struct qitem_t)); 103 | if(entry == NULL) 104 | return 0; 105 | 106 | entry->data = item; 107 | entry->next = NULL; 108 | 109 | enif_mutex_lock(queue->lock); 110 | 111 | assert(queue->length >= 0 && "Invalid queue size at push"); 112 | 113 | if(queue->tail != NULL) 114 | queue->tail->next = entry; 115 | 116 | queue->tail = entry; 117 | 118 | if(queue->head == NULL) 119 | queue->head = queue->tail; 120 | 121 | queue->length += 1; 122 | 123 | enif_cond_signal(queue->cond); 124 | enif_mutex_unlock(queue->lock); 125 | 126 | return 1; 127 | } 128 | 129 | void* 130 | queue_pop(queue *queue) 131 | { 132 | qitem *entry; 133 | void* item; 134 | 135 | enif_mutex_lock(queue->lock); 136 | 137 | /* Wait for an item to become available. 138 | */ 139 | while(queue->head == NULL) 140 | enif_cond_wait(queue->cond, queue->lock); 141 | 142 | assert(queue->length >= 0 && "Invalid queue size at pop."); 143 | 144 | /* Woke up because queue->head != NULL 145 | * Remove the entry and return the payload. 146 | */ 147 | entry = queue->head; 148 | queue->head = entry->next; 149 | entry->next = NULL; 150 | 151 | if(queue->head == NULL) { 152 | assert(queue->tail == entry && "Invalid queue state: Bad tail."); 153 | queue->tail = NULL; 154 | } 155 | 156 | queue->length -= 1; 157 | 158 | enif_mutex_unlock(queue->lock); 159 | 160 | item = entry->data; 161 | enif_free(entry); 162 | 163 | return item; 164 | } 165 | 166 | int 167 | queue_send(queue *queue, void *item) 168 | { 169 | enif_mutex_lock(queue->lock); 170 | assert(queue->message == NULL && "Attempting to send multiple messages."); 171 | queue->message = item; 172 | enif_cond_signal(queue->cond); 173 | enif_mutex_unlock(queue->lock); 174 | return 1; 175 | } 176 | 177 | void * 178 | queue_receive(queue *queue) 179 | { 180 | void *item; 181 | 182 | enif_mutex_lock(queue->lock); 183 | 184 | /* Wait for an item to become available. 185 | */ 186 | while(queue->message == NULL) 187 | enif_cond_wait(queue->cond, queue->lock); 188 | 189 | item = queue->message; 190 | queue->message = NULL; 191 | 192 | enif_mutex_unlock(queue->lock); 193 | 194 | return item; 195 | } 196 | -------------------------------------------------------------------------------- /c_src/queue.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of Emonk released under the MIT license. 2 | // See the LICENSE file for more information. 3 | 4 | /* adapted by: Maas-Maarten Zeeman :ok 10 | {:error, {:reload, _}} -> :ok 11 | {:error, reason} -> Logger.warn("Failed to load nif: #{inspect(reason)}") 12 | end 13 | end 14 | 15 | def start(), do: :erlang.nif_error("erl_video_capture not loaded") 16 | 17 | # Video Capture 18 | def video_capture_open(_conn, _ref, _pid, _filename), 19 | do: :erlang.nif_error("erl_video_capture not loaded") 20 | 21 | def video_capture_close(_conn, _ref, _pid, _cap), 22 | do: :erlang.nif_error("erl_video_capture not loaded") 23 | 24 | def video_capture_is_opened(_conn, _ref, _pid, _cap), 25 | do: :erlang.nif_error("erl_video_capture not loaded") 26 | 27 | def video_capture_grab(_conn, _ref, _pid, _cap), 28 | do: :erlang.nif_error("erl_video_capture not loaded") 29 | 30 | def video_capture_retreive(_conn, _ref, _pid, _cap_flag), 31 | do: :erlang.nif_error("erl_video_capture not loaded") 32 | 33 | def video_capture_read(_conn, _ref, _pid, _cap), 34 | do: :erlang.nif_error("erl_video_capture not loaded") 35 | 36 | def video_capture_get(_conn, _ref, _pid, _cap_propid), 37 | do: :erlang.nif_error("erl_video_capture not loaded") 38 | 39 | def video_capture_set(_conn, _ref, _pid, _cap_propid_value), 40 | do: :erlang.nif_error("erl_video_capture not loaded") 41 | 42 | def imencode(_mat, _ref, _pid, _ext_params), do: :erlang.nif_error("nif not loaded") 43 | def new_mat(_mat, _ref, _pid, _arg), do: :erlang.nif_error("nif not loaded") 44 | end 45 | -------------------------------------------------------------------------------- /lib/open_cv.ex: -------------------------------------------------------------------------------- 1 | defmodule OpenCv do 2 | import OpenCv.Util 3 | 4 | @default_timeout 5000 5 | 6 | def new do 7 | :erl_cv_nif.start() 8 | end 9 | 10 | def mat(conn, arg \\ nil, timeout \\ @default_timeout) do 11 | ref = make_ref() 12 | :ok = :erl_cv_nif.new_mat(conn, ref, self(), arg) 13 | receive_answer(ref, timeout) 14 | end 15 | 16 | def imencode(conn, mat, ext, params, timeout \\ @default_timeout) do 17 | ref = make_ref() 18 | :ok = :erl_cv_nif.imencode(conn, ref, self(), {mat, ext, params}) 19 | receive_answer(ref, timeout) 20 | end 21 | 22 | def test do 23 | {:ok, conn} = OpenCv.new() 24 | {:ok, cap} = OpenCv.VideoCapture.open(conn, '/dev/video0') 25 | true = OpenCv.VideoCapture.is_opened(conn, cap) 26 | {:ok, frame} = OpenCv.VideoCapture.read(conn, cap) 27 | jpg = OpenCv.imencode(conn, frame, '.jpg', []) 28 | File.write("img.jpg", jpg) 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/open_cv/util.ex: -------------------------------------------------------------------------------- 1 | defmodule OpenCv.Util do 2 | require Logger 3 | 4 | def receive_answer(ref, timeout) do 5 | start = :os.timestamp() 6 | 7 | receive do 8 | {:erl_cv_nif, ^ref, resp} -> 9 | resp 10 | 11 | {:erl_cv_nif, _, _} = stale -> 12 | Logger.error("Stale answer: #{inspect(stale)}") 13 | passed_mics = :timer.now_diff(:os.timestamp(), start) |> div(1000) 14 | 15 | new_timeout = 16 | case timeout - passed_mics do 17 | passed when passed < 0 -> 0 18 | to -> to 19 | end 20 | 21 | receive_answer(ref, new_timeout) 22 | after 23 | timeout -> {:error, {:timeout, ref}} 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/open_cv/video_capture.ex: -------------------------------------------------------------------------------- 1 | defmodule OpenCv.VideoCapture do 2 | require Logger 3 | import OpenCv.Util 4 | 5 | @default_timeout 5000 6 | 7 | def open(conn, devpath, timeout \\ @default_timeout) do 8 | ref = make_ref() 9 | :ok = :erl_cv_nif.video_capture_open(conn, ref, self(), devpath) 10 | receive_answer(ref, timeout) 11 | end 12 | 13 | def close(conn, cap, timeout \\ @default_timeout) do 14 | ref = make_ref() 15 | :ok = :erl_cv_nif.video_capture_close(conn, ref, self(), cap) 16 | receive_answer(ref, timeout) 17 | end 18 | 19 | def is_opened(conn, cap, timeout \\ @default_timeout) do 20 | ref = make_ref() 21 | :ok = :erl_cv_nif.video_capture_is_opened(conn, ref, self(), cap) 22 | receive_answer(ref, timeout) 23 | end 24 | 25 | def grab(conn, cap, timeout \\ @default_timeout) do 26 | ref = make_ref() 27 | :ok = :erl_cv_nif.video_capture_grab(conn, ref, self(), cap) 28 | receive_answer(ref, timeout) 29 | end 30 | 31 | def retreive(conn, cap, flag \\ 0, timeout \\ @default_timeout) do 32 | ref = make_ref() 33 | :ok = :erl_cv_nif.video_capture_retreive(conn, ref, self(), {cap, flag}) 34 | receive_answer(ref, timeout) 35 | end 36 | 37 | def read(conn, cap, timeout \\ @default_timeout) do 38 | ref = make_ref() 39 | :ok = :erl_cv_nif.video_capture_read(conn, ref, self(), cap) 40 | receive_answer(ref, timeout) 41 | end 42 | 43 | def get(conn, cap, propid, timeout \\ @default_timeout) do 44 | ref = make_ref() 45 | :ok = :erl_cv_nif.video_capture_get(conn, ref, self(), {cap, propid}) 46 | receive_answer(ref, timeout) 47 | end 48 | 49 | def set(conn, cap, propid, propval, timeout \\ @default_timeout) do 50 | ref = make_ref() 51 | :ok = :erl_cv_nif.video_capture_set(conn, ref, self(), {cap, propid, propval}) 52 | receive_answer(ref, timeout) 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule OpenCv.MixProject do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :open_cv, 7 | version: "0.1.0", 8 | elixir: "~> 1.7", 9 | start_permanent: Mix.env() == :prod, 10 | make_clean: ["clean"], 11 | make_env: make_env(), 12 | make_cwd: __DIR__, 13 | compilers: [:elixir_make] ++ Mix.compilers(), 14 | deps: deps() 15 | ] 16 | end 17 | 18 | # Run "mix help compile.app" to learn about applications. 19 | def application do 20 | [ 21 | extra_applications: [:logger] 22 | ] 23 | end 24 | 25 | # Run "mix help deps" to learn about dependencies. 26 | defp deps do 27 | [ 28 | {:elixir_make, "~> 0.4", runtime: false} 29 | ] 30 | end 31 | 32 | defp make_env do 33 | case System.get_env("ERL_EI_INCLUDE_DIR") do 34 | nil -> 35 | %{ 36 | "MAKE_CWD" => __DIR__, 37 | "MIX_TARGET" => System.get_env("MIX_TARGET") || "host", 38 | "ERL_EI_INCLUDE_DIR" => Path.join([:code.root_dir(), "usr", "include"]), 39 | "ERL_EI_LIBDIR" => Path.join([:code.root_dir(), "usr", "lib"]) 40 | } 41 | 42 | _ -> 43 | %{"MAKE_CWD" => __DIR__, "MIX_TARGET" => System.get_env("MIX_TARGET") || "host"} 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "elixir_make": {:hex, :elixir_make, "0.4.2", "332c649d08c18bc1ecc73b1befc68c647136de4f340b548844efc796405743bf", [:mix], [], "hexpm"}, 3 | } 4 | -------------------------------------------------------------------------------- /test/open_cv_test.exs: -------------------------------------------------------------------------------- 1 | defmodule OpenCvTest do 2 | use ExUnit.Case 3 | doctest OpenCv 4 | end 5 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | --------------------------------------------------------------------------------