IP Camera ONVIF Protocol

OpenCV 2025. 3. 1. 10:24 |
반응형

 

ONVIF 프로토콜을 지원하는 IP 카메라를 사용해 보자.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import cv2
 
#capture = cv2.VideoCapture(0, cv2.CAP_DSHOW)
# 노트북에 연결된 카메라는 대부분 cv2.CAP_DSHOW 옵션이 필요 없지만
# 데스크탑에 USB로 연결된 카메라 사용시에는 cv2.CAP_DSHOW 옵션이 필요 할 수 있다.
 
#cap = cv2.VideoCapture('rtsp://admin:@192.168.0.44')
# NVR 사용
 
cap = cv2.VideoCapture('rtsp://admin:123456@192.168.0.161:554/stream0')
# NVR 없이 카메라만 연결 Sub Stream
# 형식: rtsp://[ID]:[PW]@[IP주소]:[PORT번호]/[기타설정]
 
#cap = cv2.VideoCapture('rtsp://admin:123456@192.168.0.161:554/stream1')
# NVR 없이 카메라만 연결 Main Stream
 
# 카메라 Configure - Stream manager - Video Setting - Encoding Format 에서
# H265 로 설정하면 아래와 같은 에러 메세지가 발생한다.
# [hevc @ 0000024133eada80] PPS id out of range: 0
# H264 로 설정하면 괜찮다.
 
if not cap.isOpened():
    print("Not opened")
    exit()
 
while cv2.waitKey(1< 0:
    ret, frame = cap.read()
    if not ret:
        print("False returned")
        exit()
    cv2.imshow("video", frame)
 
cap.release()
cv2.destroyAllWindows()
 

 

만약 Sub Stream(Stream0)이 아닌 Main Stream(Stream1)을 사용하면 영상은 큰 문제 없이 계속 출력되지만 아래와 같은 에러 메세지가 계속 나타난다. (Sub Stream에서도 종종 에러가 발생하기도 했다)

 

 

이런 에러 때문인지는 모르겠지만 OpenCV에서 rtsp를 이용해 출력한 영상은 카메라에서 자체 지원하는 웹뷰 등을 이용해 확인한 영상보다 노이즈가 심하다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import queue
import threading
import cv2
 
q=queue.Queue()
stop = False
 
def Receive():
    print("start Reveiving")
    
    cap = cv2.VideoCapture('rtsp://admin:123456@192.168.0.161:554/stream0')
    if not cap.isOpened():
        print("Not opened")
        exit()
    
    global stop
    ret = True
    while ret and not stop:
        ret, frame = cap.read()
        lock.acquire()
        if not ret:
            print("False returned")
            continue
        q.put(frame)
        lock.release()
    
    cap.release()
    cv2.destroyAllWindows()
 
def Display():
     print("Start Displaying")
 
     global stop
     while cv2.waitKey(1< 0:
         lock.acquire()
         if not q.empty():
             frame=q.get()
             cv2.imshow("stream", frame)
         lock.release()
     stop = True
 
if __name__ == '__main__':
    lock = threading.Lock()
    
    t1 = threading.Thread(target = Receive)
    t2 = threading.Thread(target = Display)
    t1.start()
   t2.start()
 

 

영상을 받아오는 부분과 출력하는 부분을 다른 스레드로 구분하고 각각의 스레드에서 영상에 접근할때 충돌을 방지하기 위해 Primitive Lock을 사용해 봤지만 별 효과는 없다.

오히려 Main Stream(Stream1)에서 Sub Stream(Stream0)으로 바꿔 영상의 크기를 줄이는게 에러 확률을 크게 낮추었다.

 

 

카메라 Video Setting 에서 위와 같이 바꾸는 것도 에러 확률을 크게 낮추었다.

● Resolution: 1920X1080 => 1280X720

● Quality: Good => Worst 

Frame Rate, Max Bitrate는 별 영향이 없었다.

 

Packet Loss가 있어서 그런건지도 모르겠다.

stackoverflow

 

반응형
Posted by J-sean
:
반응형

C와 Python 프로그램간 이미지 데이터를 공유해 보자.

 

1) 파이썬 프로그램 데이터를 C 프로그램에 공유

mmap

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import time
import mmap
import cv2
 
frame = cv2.imread("image.jpg", cv2.IMREAD_COLOR)
h, w, c = frame.shape
buffer_size = h * w * c
 
mm = mmap.mmap(-1, buffer_size, "Local\\MySharedMemory")
 
try:
    mm.write(frame.tobytes())
 
    while True:
        time.sleep(1000)  # Sleep to prevent busy waiting.
finally:
    mm.close()
 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <opencv2/opencv.hpp>
#include <Windows.h>
 
int main() {
    int height = 495;
    int width = 396;
    int channels = 3;
    int buffersize = height * width * channels;
 
    byte* buffer = new byte[buffersize];
    memset(buffer, 0, buffersize);
 
    HANDLE hFMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, buffersize, L"MySharedMemory");
    if (hFMap == NULL)
        return -1;
 
    TCHAR* PtrInFile = (TCHAR*)MapViewOfFile(hFMap, FILE_MAP_ALL_ACCESS, 00, buffersize);
    if (PtrInFile == NULL)
        return -1;
 
    memcpy(buffer, PtrInFile, buffersize);
 
    cv::Mat image = cv::Mat(height, width, CV_8UC3, buffer);
    if (image.empty())
        return -1;
 
    // 사용할 Mat이 이미 존재한다면 아래처럼 memcpy()를 사용할 수도 있다.
    //cv::Mat image(height, width, CV_8UC3); // 이미 존재하는 Mat
    //memcpy(image.data, buffer, buffersize); // buffer 사용하지 않고 메모리에서 image.data로 직접 복사해도 된다.
 
    cv::imshow("image", image);
    cv::waitKey(0);
        
    UnmapViewOfFile(PtrInFile);
    CloseHandle(hFMap);
    delete[] buffer;
 
    return 0;
}
 

 

 

파이썬 프로그램을 먼저 실행하고 C 프로그램을 실행한다.

 

 

2) C 프로그램 데이터를 파이썬 프로그램에 공유

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <opencv2/opencv.hpp>
#include <Windows.h>
 
int main() {
    cv::Mat image = cv::imread("image.jpg");
    if (image.empty())
        return -1;
 
    int height = image.rows;
    int width = image.cols;
    int channels = image.channels();
    int buffersize = height * width * channels;
 
    HANDLE hFMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, buffersize, L"MySharedMemory");
    if (hFMap == NULL)
        return -1;
 
    TCHAR* PtrInFile = (TCHAR*)MapViewOfFile(hFMap, FILE_MAP_ALL_ACCESS, 00, buffersize);
    if (PtrInFile == NULL)
        return -1;
 
    memcpy(PtrInFile, image.data, buffersize);
    // 버퍼를 사용하지 않고 바로 메모리에 데이터 복사.
        
    int a;
    std::cin >> a;
    // 대기
 
    UnmapViewOfFile(PtrInFile);
    CloseHandle(hFMap);
    
    return 0;
}
 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import mmap
import numpy as np
import cv2
 
height = 495
width = 396
channels = 3
buffer_size = height * width * channels
 
mm = mmap.mmap(-1, buffer_size, "Local\\MySharedMemory")
 
try:    
    buffer = mm.read(buffer_size)
    image_arr = np.frombuffer(buffer, np.ubyte)
    image = image_arr.reshape(height, width, channels)
    
    cv2.imshow("image", image)
    cv2.waitKey(0)
finally:
    mm.close()
    cv2.destroyAllWindows()
 

 

결과는 같다.

 

※ 참고

2025.02.16 - [OpenCV] - C# and Python OpenCV Image Data Share (Memory Mapped File)

 

반응형
Posted by J-sean
:
반응형

C#과 Python 프로그램간 이미지 데이터를 공유해 보자.

 

1) Python 프로그램 데이터를 C# 프로그램에 공유.

mmap

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import time
import mmap
import cv2
 
frame = cv2.imread("image.jpg", cv2.IMREAD_COLOR)
h, w, c = frame.shape
buffer_size = h * w * c
 
# 3개의 4바이트(12바이트) 데이터를 위한 추가 공간 확보
#buffer_size = h * w * c + 12
 
# Open a memory-mapped file.
mm = mmap.mmap(-1, buffer_size, "Local\\MySharedMemory")
 
try:
    # 만약 3개의 4바이트 데이터를 저장하고 싶다면 아래와
    # 같이 한다.
    #mm.write(h.to_bytes(4))
    #mm.write(w.to_bytes(4))
    #mm.write(c.to_bytes(4))
 
    mm.write(frame.tobytes())
    
    # Keep the Python script running for demonstration.
    while True:
        time.sleep(1000)  # Sleep to prevent busy waiting.
finally:
    mm.close()
 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
using OpenCvSharp;
using System.IO.MemoryMappedFiles;
using System.Runtime.InteropServices;
 
public class Program
{
    public static void Main()
    {
        int width = 396;
        int height = 495;
        int channels = 3;
 
        using (MemoryMappedFile mmf = MemoryMappedFile.OpenExisting("MySharedMemory"))
        {
            using (MemoryMappedViewAccessor accessor = mmf.CreateViewAccessor())
            {
                byte[] buffer = new byte[width * height * channels];
                accessor.ReadArray(0, buffer, 0, buffer.Length);
 
                // 만약 buffer 앞 부분에 3개의 4바이트 int 데이터가 저장되어 있다면 아래와 같이 읽는다.
                // (물론 위에서 buffer[] 생성시 12바이트의 추가 공간 확보가 필요하다)
                //int h = ((buffer[0] << 24) +  (buffer[1] << 16) + (buffer[2] << 8) + (buffer[3]));
                //Console.WriteLine(h);
                //int w = ((buffer[4] << 24) + (buffer[5] << 16) + (buffer[6] << 8) + (buffer[7]));
                //Console.WriteLine(w);
                //int c = ((buffer[8] << 24) + (buffer[9] << 16) + (buffer[10] << 8) + (buffer[11]));
                //Console.WriteLine(c);
 
                // When buffer[] represents encoded image data (ex. JPEG, PNG, etc.), you can use
                // FromImageData or ImDecode.
                //buffer = System.IO.File.ReadAllBytes("image.jpg");
                //Mat mat = Mat.FromImageData(buffer, ImreadModes.Color);                
                //Mat mat = Cv2.ImDecode(buffer, ImreadModes.Color);
 
                // When buffer[] represens pixel data (BGRBGR...) , you need to hard-code copying operation.
                //Mat mat = new Mat(height, width, MatType.CV_8UC3);
                //Mat.Indexer<Vec3b> indexer = mat.GetGenericIndexer<Vec3b>();
                //for (int y = 0; y < height; y++)
                //{
                //    for (int x = 0; x < width; x++)
                //    {
                //        int pos = y * width * channels + x * channels;
                //        byte blue = buffer[pos + 0];
                //        byte green = buffer[pos + 1];
                //        byte red = buffer[pos + 2];
                //        Vec3b newValue = new Vec3b(blue, green, red);
                //        indexer[y, x] = newValue;
                //    }
                //}
 
                // If buffer[]'s step length is equal to the Mat's, there is a more effective way.
                Mat mat = new Mat(height, width, MatType.CV_8UC3);
                int length = height * width * channels;
                Marshal.Copy(buffer, 0, mat.Data, length);
 
                Cv2.ImShow("image", mat);
                Cv2.WaitKey(0);
                Cv2.DestroyAllWindows();
            }
        }
    }
}
 

 

 

Python 프로그램을 먼저 실행하고 C# 프로그램을 실행한다.

 

2) C# 프로그램 데이터를 Python 프로그램에 공유.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using System;
using System.IO.MemoryMappedFiles;
using System.Runtime.InteropServices;
using OpenCvSharp;
 
public class Program
{
    public static void Main()
    {   
        Mat image = Cv2.ImRead("image.jpg", ImreadModes.Color);
        int width = image.Width;
        int height = image.Height;
        int channels = image.Channels();
        int buffer_size = width * height * channels;
 
        using (MemoryMappedFile mmf = MemoryMappedFile.CreateNew("MySharedMemory", buffer_size))
        {
            using (MemoryMappedViewAccessor accessor = mmf.CreateViewAccessor())
            {
                byte[] buffer = new byte[buffer_size];
                Marshal.Copy(image.Data, buffer, 0, buffer_size);
                accessor.WriteArray<byte>(0, buffer, 0, buffer_size);
            }
            
            Console.WriteLine("waiting...");
            Console.ReadLine();
        }        
    }
}
 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import mmap
import numpy as np
import cv2
 
height = 495
width = 396
channels = 3
buffer_size = height * width * channels
 
mm = mmap.mmap(-1, buffer_size, "Local\\MySharedMemory")
 
try:    
    buffer = mm.read(buffer_size)
    image_arr = np.frombuffer(buffer, np.ubyte)
    image = image_arr.reshape(height, width, channels)
    
    cv2.imshow("image", image)
    cv2.waitKey(0)
finally:
    mm.close()
    cv2.destroyAllWindows()
 

 

결과는 같다.

 

※ 참고

2025.02.23 - [OpenCV] - C and Python OpenCV Image Data Share (Memory Mapped File)

 

반응형
Posted by J-sean
:
반응형

PyQt를 이용해 OpenCV 이미지를 출력해 보자.

 

디자이너에서 Dialog에 Label을 하나 배치하고 사이즈를 충분히 크게 한다. (test.ui)

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import sys
import cv2
from PyQt5 import QtWidgets
from PyQt5 import QtGui
from PyQt5 import uic
 
myui = uic.loadUiType("test.ui")
 
class MyApp(QtWidgets.QWidget, myui[0]):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
 
        # 1) QtWidgets.QGraphicsView()에 이미지 출력하기
        # pixmap = QtGui.QPixmap()
        # pixmap.load("palvin.jpg")
        # scene = QtWidgets.QGraphicsScene()
        # scene.addPixmap(pixmap)
        # self.graphicsView.setScene(scene)
 
        # 2) QtWidgets.QGraphicsView()에 OpenCV 이미지 출력하기
        # img = cv2.imread("palvin.jpg")
        # img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
        # h, w, c = img.shape
        # qimg = QtGui.QImage(img.data, w, h, w*c, QtGui.QImage.Format_RGB888)
        # pixmap = QtGui.QPixmap.fromImage(qimg)
        # scene = QtWidgets.QGraphicsScene()
        # scene.addPixmap(pixmap)
        # self.graphicsView.setScene(scene)
 
        # QtWidgets.Label에 OpenCV 이미지 출력하기
        img = cv2.imread("palvin.jpg")
        img = cv2.resize(img, (self.label.size().width(), self.label.size().height()))
        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
        h, w, c = img.shape
        qimg = QtGui.QImage(img.data, w, h, w*c, QtGui.QImage.Format_RGB888)
        pixmap = QtGui.QPixmap.fromImage(qimg)
        self.label.setPixmap(pixmap)
 
if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    myWindow = MyApp()
    myWindow.show()
   app.exec_()
 

 

소스를 입력하고 실행한다.

 

Label 사이즈에 맞게 이미지가 조절되었다.

 

이번엔 GraphicsView를 사용해 보자. 위 소스에서 1번이나 2번 주석을 해제한다.

1, 2번 주석부분의 소스는 이미지 크기를 조절하지 않는다.

 

Label은 삭제하고 Graphics View를 배치하고 크기를 조정한다.

 

이미지가 Graphics View보다 크지만 스크롤바가 표시된다.

 

Widget 없이 Dialog에 직접 이미지를 출력할 수 도 있다. Dialog만 남기고 모두 삭제한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import sys
import cv2
from PyQt5 import QtWidgets
from PyQt5 import QtGui
from PyQt5 import uic
 
myui = uic.loadUiType("test.ui")
 
class MyApp(QtWidgets.QWidget, myui[0]):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
 
        self.img = cv2.imread("palvin.jpg")        
        self.img = cv2.cvtColor(self.img, cv2.COLOR_BGR2RGB)
        h, w, c = self.img.shape
        h, w = int(h/6), int(w/6)        
        self.img = cv2.resize(self.img, (w, h))
        self.qimg = QtGui.QImage(self.img.data, w, h, w*c, QtGui.QImage.Format_RGB888)
    
    def paintEvent(self, e):
        qp = QtGui.QPainter(self)
        self.drawCVImage(qp)
 
    def drawCVImage(self, qp):
        qp.drawImage(1010self.qimg)
        
if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    myWindow = MyApp()
    myWindow.show()
   app.exec_()
 

 

소스를 입력하고 실행한다.

 

Dialog에 직접 이미지가 출력된다.

 

반응형
Posted by J-sean
:

[SDL] SDL OpenCV

C, C++ 2024. 1. 27. 01:00 |
반응형

SDL에서 OpenCV를 사용해 보자.

 

아래와 같이 비주얼 스튜디오에서 SDL과 OpenCV를 사용할 수 있도록 적절히 세팅한다.

 

 

 

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <iostream>
#include "SDL.h"
#include "opencv2/opencv.hpp"
 
int main(int argc, char* argv[]) {
    SDL_Init(SDL_INIT_VIDEO);
    SDL_Window* window = SDL_CreateWindow("SDL Test", SDL_WINDOWPOS_UNDEFINED,
        SDL_WINDOWPOS_UNDEFINED, 640480, SDL_WINDOW_RESIZABLE);
    SDL_Renderer* renderer = SDL_CreateRenderer(window, -10);
 
    //////////////////// cv::Mat to SDL_Texture ////////////////////
    cv::Mat image = cv::imread("image.png", cv::IMREAD_COLOR);
    SDL_Rect destRect = { 00, image.cols, image.rows };
    SDL_Texture* texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_BGR24,
        SDL_TEXTUREACCESS_STREAMING, image.cols, image.rows);
    SDL_UpdateTexture(texture, NULL, (void*)image.data, image.step1());
    // SDL_UpdateTexture() 대신 아래 주석 처리된 코드를 사용할 수도 있다.
    // #include <opencv2/core/types_c.h> 필요.
 
    /*
    IplImage iplImage = cvIplImage(image);
    unsigned char* texture_data = NULL;
    int texture_pitch = 0;
    SDL_LockTexture(texture, 0, (void**)&texture_data, &texture_pitch);
    memcpy(texture_data, (void*)iplImage.imageData,
        iplImage.width * iplImage.height * iplImage.nChannels);
    SDL_UnlockTexture(texture);
    */
    ////////////////////////////////////////////////////////////////
 
    SDL_Event event;
    bool quit = false;
 
    while (!quit) {
        while (SDL_PollEvent(&event)) {
            switch (event.type) {
            case SDL_QUIT:
                quit = true;
                break;
            case SDL_KEYDOWN:
                printf("Key pressed: %s\n", SDL_GetKeyName(event.key.keysym.sym));
                if (event.key.keysym.sym == SDLK_ESCAPE)
                    quit = true;
                break;
            default:
                break;
            }
        }
 
        SDL_SetRenderDrawColor(renderer, 255255255, SDL_ALPHA_OPAQUE);
        SDL_RenderClear(renderer);
        SDL_RenderCopy(renderer, texture, NULL&destRect);
        SDL_RenderPresent(renderer);
    }
 
    SDL_DestroyTexture(texture);
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();
 
    return 0;
}
 

 

코드를 작성하고 빌드한다.

 

실행하면 이미지가 출력된다.

 

반응형
Posted by J-sean
:
반응형

OpenPose 소스를 컴파일하고 실행해 보자.

 

CMake를 다운로드하고 설치한다.

 

CUDA Toolkit 11.1.1을 다운로드하고 설치한다.

 

권장옵션으로 설치한다.

 

cuDNN 8.1.0을 다운로드한다. (NVIDIA 로그인이 필요하다)

 

 

다운로드한 cuDNN 압축을 풀고 cuda 폴더를 위 경로에 복사한다.

 

적당한 디렉토리에 OpenPose를 클론한다.

 

git clone https://github.com/CMU-Perceptual-Computing-Lab/openpose

cd openpose

git submodule update --init --recursive --remote

 

build 폴더를 만든다.

 

CMake를 실행하고 경로를 설정한다. 그리고 Configure를 클릭한다.

 

 

위와 같이 설정하고 Finish를 클릭한다.

 

Configuring이 끝나면 Generate를 클릭한다.

Python이나 Unity에서 사용한다면 옵션을 선택한다.

(Configure에서 x64를 선택했다면 64비트 Python이 필요하다)

 

OpenPose.sln을 실행한다.

 

Solution Configurations를 Release로 바꾸고 빌드한다.

빌드가 완료되면 실행(Ctrl+F5)한다. OpenPoseDemo가 실행된다.

 

 

Visual Studio 외부에서 OpenPoseDemo.exe나 다른 예제를 실행하려면 아래 파일과 폴더를 같은 폴더에 복사해야 한다.

 

bin 폴더의 모든 파일을 실행파일이 있는 폴더에 복사한다.

 

models 폴더를 실행파일이 있는 폴더에 복사한다.

 

예제에 따라 examples 폴더가 필요할 수도 있다.

 

 

생각보다 메모리가 많이 필요하다.

 

※ 참고

OpenPose Installation

반응형
Posted by J-sean
:

OpenCvSharp Simple Camera Example

C# 2022. 1. 14. 18:07 |
반응형

C#과 OpenCvSharp를 이용한 간단한 카메라 응용 프로그램 예.

 

폼에 Button, RadioButton, PictureBox등을 적당히 배치한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
 
using System.Threading;
using OpenCvSharp;
using OpenCvSharp.Extensions;
 
namespace OpenCV
{
    delegate void dele(Mat m);
 
    public partial class Form1 : Form
    {
        bool isCameraOn;
 
        dele filter;    // 카메라에 적용할 필터(효과) 델리게이트
        Thread thread;
        Mat mat;
        VideoCapture videoCapture;
 
        public Form1()
        {
            InitializeComponent();
 
            this.FormBorderStyle = FormBorderStyle.FixedSingle;
            pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;
            button1.Text = "Camera Start";
            isCameraOn = false;
            filter = null;
            radioButton1.Checked = true;
        }
 
        private void CameraCallback()
        {
            mat = new Mat();
            videoCapture = new VideoCapture(0);
 
            if (!videoCapture.IsOpened())
            {
                Text = "Camera open failed!";
                MessageBox.Show("카메라를 열 수 없습니다. 연결 상태를 확인 해 주세요.");
 
                return;
            }
 
            while (true)
            {
                videoCapture.Read(mat);
 
                if (!mat.Empty() && filter != null)
                {
                    filter(mat);    // 선택된 라디오 버튼에 따른 필터 적용.                    
                }
 
                if (!mat.Empty())
                {
                    // 로고를 디스플레이하기 위해 그레이 이미지(1채널)는 컬러 포맷(3채널)으로 변환
                    if (mat.Channels() == 1)
                    {
                        Cv2.CvtColor(mat, mat, ColorConversionCodes.GRAY2BGR);
                    }
                    Cv2.PutText(mat, "SEAN"new OpenCvSharp.Point(550470), HersheyFonts.HersheyDuplex, 1new Scalar(00255), 2);
 
                    // 이 전 프레임에서 PictureBox에 로드된 비트맵 이미지를 Dispose하지 않으면 메모리 사용량이 크게 증가한다.
                    if (pictureBox1.Image != null)
                    {
                        pictureBox1.Image.Dispose();
                    }
                    pictureBox1.Image = BitmapConverter.ToBitmap(mat);
                }
            }
        }
 
        private void button1_Click(object sender, EventArgs e)
        {
            if (isCameraOn == false)
            {
                thread = new Thread(new ThreadStart(CameraCallback));
 
                thread.Start();
                isCameraOn = true;
                button1.Text = "Camera Stop";
            }
            else
            {
                if (videoCapture.IsOpened())
                {
                    thread.Abort();
                    if (pictureBox1.Image != null)
                    {
                        pictureBox1.Image.Dispose();
                    }
                    videoCapture.Release();
                    mat.Release();
                }
                isCameraOn = false;
                button1.Text = "Camera Start";
            }
        }
 
        private void button2_Click(object sender, EventArgs e)
        {
            System.Diagnostics.Process.Start("https://s-engineer.tistory.com/");
        }
 
        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (thread != null && thread.IsAlive && videoCapture.IsOpened())
            {
                thread.Abort();
                if (pictureBox1.Image != null)
                {
                    pictureBox1.Image.Dispose();
                }
                videoCapture.Release();
                mat.Release();
            }
        }
 
        // 필터 함수들
        private void ToGray(Mat mat)
        {
            Cv2.CvtColor(mat, mat, ColorConversionCodes.BGR2GRAY);
        }
 
        private void ToEmboss(Mat mat)
        {
            float[] data = { -1.0f, -1.0f, 0.0f, -1.0f, 0f, 1.0f, 0.0f, 1.0f, 1.0f };
            Mat emboss = new Mat(33, MatType.CV_32FC1, data);
 
            Cv2.CvtColor(mat, mat, ColorConversionCodes.BGR2GRAY);
            Cv2.Filter2D(mat, mat, -1, emboss, new OpenCvSharp.Point(-1-1), 128);
 
            emboss.Release();
        }
 
        private void ToBlur(Mat mat)
        {
            Cv2.GaussianBlur(mat, mat, new OpenCvSharp.Size(), (double)3);
        }
 
        private void ToSharpen(Mat mat)
        {
            Mat blurred = new Mat();
            Cv2.GaussianBlur(mat, blurred, new OpenCvSharp.Size(), (double)3);
 
            // 아래 연산이 반복되면 메모리 사용량이 크게 증가한다.
            float alpha = 2.0f;
            ((1 + alpha) * mat - alpha * blurred).ToMat().CopyTo(mat);
            //mat = (1 + alpha) * mat - alpha * blurred;
 
            blurred.Release();
        }
 
        private void ToEdge(Mat mat)
        {
            Cv2.CvtColor(mat, mat, ColorConversionCodes.BGR2GRAY);
            Cv2.Canny(mat, mat, 5070);
        }
 
        // 라디오 버튼 이벤트 핸들러들
        private void radioButton1_CheckedChanged(object sender, EventArgs e)
        {
            if (((RadioButton)sender).Checked)
            {
                filter = null;
            }
        }
 
        private void radioButton2_CheckedChanged(object sender, EventArgs e)
        {
            if (((RadioButton)sender).Checked)
            {
                filter = ToGray;
            }
        }
 
        private void radioButton3_CheckedChanged(object sender, EventArgs e)
        {
            if (((RadioButton)sender).Checked)
            {
                filter = ToEmboss;
            }
        }
 
        private void radioButton4_CheckedChanged(object sender, EventArgs e)
        {
            if (((RadioButton)sender).Checked)
            {
                filter = ToBlur;
            }
        }
 
        private void radioButton5_CheckedChanged(object sender, EventArgs e)
        {
            if (((RadioButton)sender).Checked)
            {
                filter = ToSharpen;
            }
        }
 
        private void radioButton6_CheckedChanged(object sender, EventArgs e)
        {
            if (((RadioButton)sender).Checked)
            {
                filter = ToEdge;
            }
        }
    }
}
 

 

소스를 입력하고 빌드한다.

 

프로그램을 실행하고 Camera Start 버튼을 클릭한다.

 

다른 필터를 선택하면 그에 맞는 화면이 출력된다.

 

※ 소스에서 ToSharpen() 의 주석 부분은 제대로 실행되지 않는다. 관련 내용은 아래 링크를 참고하자.

2022.01.14 - [C#] - OpenCvSharp Simple Example and MatExpr

 

※ ToSharpen() 의 반복 실행으로 인한 메모리 사용량 증가는 OpenCV의 메모리 할당을 파악하지 못하는 .NET Garbage Collector의 문제다. 아래와 같이 Garbage Collector 호출 코드 추가로 해결은 가능하다.

1
2
3
4
5
6
7
8
9
10
11
12
private void ToSharpen(Mat mat)
{
    Mat blurred = new Mat();
    Cv2.GaussianBlur(mat, blurred, new OpenCvSharp.Size(), (double)3);
 
    float alpha = 2.0f;
    ((1 + alpha) * mat - alpha * blurred).ToMat().CopyTo(mat);
 
    GC.Collect();
 
    blurred.Release();
}
 

 

https://github.com/shimat/opencvsharp/issues/391

 

반응형
Posted by J-sean
:
반응형

C#과 OpenCvSharp를 이용한 간단한 이미지 변환 예.

 

폼에 Button과 PictureBox를 적당히 배치한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
 
using OpenCvSharp;
 
namespace OpenCV
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
 
        private void button1_Click(object sender, EventArgs e)
        {
            try
            {
                OpenFileDialog dlg = new OpenFileDialog();
                if (dlg.ShowDialog() == DialogResult.OK)
                {
                    Mat mat = Cv2.ImRead(dlg.FileName);
 
                    if (pictureBox1.Image != null)
                    {
                        pictureBox1.Image.Dispose();
                    }
 
                    // Canny Edge Detection(컬러 이미지를 그레이 이미지로 변환 후 Canny Edge Detection)
                    //Cv2.CvtColor(mat, mat, ColorConversionCodes.BGR2GRAY);
                    //Cv2.Canny(mat, mat, 50, 100);
 
                    // 이미지 샤프닝(가우시안블러 후 샤프닝)
                    Mat blurred = new Mat();
                    Cv2.GaussianBlur(mat, blurred, new OpenCvSharp.Size(), (double)3);
 
                    float alpha = 2.0f;
                    mat = (1 + alpha) * mat - alpha * blurred;
                    //((1 + alpha) * mat - alpha * blurred).ToMat().CopyTo(mat);
 
                    // PictureBox에 이미지 디스플레이(Mat to Bitmap)
                    System.IO.MemoryStream memoryStream = new System.IO.MemoryStream(mat.ToBytes());
                    pictureBox1.Image = new Bitmap(memoryStream);
                }
            }
            catch (Exception exc)
            {
                MessageBox.Show(exc.Message);
            }
        }
    }
}
 

 

이미지 샤프닝 예제를 입력하고 빌드한다.

 

프로그램을 실행하고 이미지를 불러오면 샤프닝 처리가 되어 표시된다.

 

원본 이미지

 

1
2
3
4
5
6
7
// 이미지 샤프닝(가우시안블러 후 샤프닝)
Mat blurred = new Mat();
Cv2.GaussianBlur(mat, blurred, new OpenCvSharp.Size(), (double)3);
 
float alpha = 2.0f;
mat = (1 + alpha) * mat - alpha * blurred;
//((1 + alpha) * mat - alpha * blurred).ToMat().CopyTo(mat);
 

 

이미지 샤프닝 코드 부분을 보면 위와같이 Mat 클래스에 +, -, * 등의 연산을 직접한다. 이 때 Mat 클래스는 효율을 높이기 위해 MatExpr 클래스로 변환 되어 연산이 진행된다.

 

위 예제에서는 별 문제 없지만 Mat 클래스 인스턴스의 레퍼런스(포인터)가 함수의 파라미터로 넘어 오고 그 함수에서 계산해서 다시 리턴하는 등의 작업이 진행될 때는 이렇게 계산 결과를 대입하는 경우 계산된 데이터가 제대로 전달 되지 않는다. C++에서는 같은 방식으로 해도 문제가 없다. 내가 모르는 C#의 특성이 있는거 같다.

 

이럴때는 주석 부분과 같이 MatExpr 클래스로 변환 되는 부분을 괄호로 감싸고 MatExpr.ToMat()로 Mat 클래스로 변환해서 다시 Mat.CopyTo()로 복사한다. 아니면 함수의 파라미터 선언을 (ref Mat mat) 처럼 바꿔서 레퍼런스를 주고 받도록 바꾸면 된다. 아래 링크의 경우 델리게이트 선언도 레퍼런스를 주고 받도록 바꿔야 한다.

2022.01.14 - [C#] - OpenCvSharp Simple Camera Example

 

반응형
Posted by J-sean
: