CCTV 분석기 소개
지난번 포스팅에서 “동영상의 특정 영역안에서 움직이는 물체가 있는 구간찾기” 방법에 대해서 핵심루틴 함수를 소개했었습니다.
오늘은 그 기술을 이용해서 개발한 CCTV 분석기의 깃헙 URL을 공유합니다.
지난번 포스팅에서 “동영상의 특정 영역안에서 움직이는 물체가 있는 구간찾기” 방법에 대해서 핵심루틴 함수를 소개했었습니다.
오늘은 그 기술을 이용해서 개발한 CCTV 분석기의 깃헙 URL을 공유합니다.
최근에 출시되는 CCTV 관제 프로그램에는 움직임을 감지해서, 해당 구간만 조회할 수 있는 기능이 있습니다. 그런데 구형 CCTV 시스템이거나, 원본 동영상 파일만 가지고 있을 때 이런 기능을 구현하려면 별도의 프로그램이 있어야 합니다. 게다가 특정 영역안에서만 움직임을 필터링하려면 몇 가지 조건을 더 추가해 주어야 하지요. 오늘은 이러한 프로그램의 핵심기술이 될 수 있는 루틴을 공유해 보고자 합니다.
def make_frames_and_trace(self, filename):
# 비디오 캡쳐할 파일을 지정하고 각종 변수 가져오기
cpt = cv2.VideoCapture(filename)
frame_count = int(cpt.get(cv2.CAP_PROP_FRAME_COUNT))
fps = cpt.get(cv2.CAP_PROP_FPS)
f_width = cpt.get(cv2.CAP_PROP_FRAME_WIDTH)
f_height = cpt.get(cv2.CAP_PROP_FRAME_HEIGHT)
# 첫번째 프레임 이미지 얻기
cpt.set(cv2.CAP_PROP_POS_FRAMES, 0)
_, frame1 = cpt.read()
on_blank = True
prev_second = 0.0
# 1초 단위로 움직임을 체크하려고 합니다
fps_i = int(fps)
has_track = None
for i in range(1, frame_count - 1, fps_i):
if i + fps < frame_count:
# 비교할 프레임 이미지 얻기
cpt.set(cv2.CAP_PROP_POS_FRAMES, i+fps_i)
_, frame2 = cpt.read()
preview = frame1
dst1 = cv2.cvtColor(frame1, cv2.COLOR_BGR2GRAY)
dst2 = cv2.cvtColor(frame2, cv2.COLOR_BGR2GRAY)
diff = cv2.absdiff(dst1, dst2)
ret_thr, thr = cv2.threshold(
diff, 30, 255, cv2.THRESH_BINARY)
dilate = cv2.dilate(thr, self.kernel)
contours, hierarchy = cv2.findContours(
dilate, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
has_it = False
for ct in range(len(contours)):
# 내가 지정한 영역 rect1과 윤곽선추출된 최소영역인 rect2가 겹치는지를 점검합니다
area_size = cv2.contourArea(contours[ct])
min_rect = cv2.minAreaRect(contours[ct])
rect1 = [self.rect.left(), self.rect.top(),
self.rect.right(), self.rect.bottom()]
rect2 = [min_rect[0][0] / f_width, min_rect[0][1] / f_height,
(min_rect[0][0] + min_rect[1][0]) / f_width,
(min_rect[0][1] + min_rect[1][1]) / f_height]
if area_size > 100 and rect2[2] - rect2[0] > 0.01 and rect2[3] - rect2[1] > 0.01 and self.check_overlap(rect1, rect2):
x,y,w,h = cv2.boundingRect(contours[ct])
# 미리보기 화면 그리기
cv2.rectangle(preview, (x,y), (x+w,y+h), (255, 0, 0), 5)
has_it = True
current_second = int((i-1) / fps)
# 변화가 감지되었나?
if has_it:
# 기존에 공백구간이었나?
if on_blank:
on_blank = False
prev_second = current_second
has_track = TrackItem(filename, current_second, current_second)
else:
prev_second = current_second
if has_track is not None:
has_track.end = current_second
else:
# 10초 이상 감지되지 않았는가? 10초 이상 변화가 없으면 새로운 구간을 만들어도 됩니다
if current_second - prev_second > 10:
on_blank = True
if has_track is not None:
# 감지된 구간의 길이가 3초 이상일 때만 추가
if has_track.end - has_track.start >= 3:
self.trackAdded.emit(has_track)
has_track = None
# 감지를 원하는 영역을 미리보기에 그려주기
cv2.rectangle(preview,
(int(self.rect.left() * f_width), int(self.rect.top() * f_height)),
(int(self.rect.right() * f_width), int(self.rect.bottom() * f_height)),
(0,255,0), 5)
frame1 = frame2
if has_track is not None:
self.trackAdded.emit(has_track)
# arr1과 arr2 구간이 겹치는지 체크
def check_overlap(self, arr1, arr2):
if arr1[0] == arr1[2] or arr1[1] == arr1[3] or arr2[0] == arr2[2] or arr2[1] == arr2[3]:
return False
if arr1[0] > arr2[2] or arr2[0] > arr1[2]:
return False
if arr1[1] > arr2[3] or arr2[1] > arr1[3]:
return False
return True
그리하여 아래와 같은 응용프로그램을 만들어 볼 수 있었는데요, 테스트가 완료되어서 최소한의 안정화 버전이 갖춰지는대로
github에 공개하고 여기에도 공유해 보겠습니다.
써드파티 앱에서 샤오미 미밴드(MiBand4)에 집계된 현재걸음수를 알아내려면 어떻게 해야 할까요? 오늘은 그 방법에 대해 정리해보고자 합니다. 방법은 간단합니다. 일단 flutter_blue_plus 라이브러리를 dependencies에 정의해주세요.
그런 다음 flutter_blue_plus 인스턴스와 변수들을 초기화 합니다.
static const String MI_BAND_NAME = "Mi Smart Band 4";
static const String UUID_SERVICE_MIBAND =
"0000fee0-0000-1000-8000-00805f9b34fb";
static const String UUID_CHARACTERISTIC_REALTIME_STEPS =
"00000007-0000-3512-2118-0009af100700";
FlutterBluePlus flutterBlue = FlutterBluePlus.instance;
BluetoothDevice? miDevice;
BluetoothCharacteristic? stepChr;
그리고 나서 연결하려는 블루투스 디바이스를 찾습니다. 저 같은 경우엔 디바이스 네임을 특정해서 하드코딩된 방식으로 찾았지만, 보통의 경우 별도의 선택 UI를 만들어서 연결합니다.
// 이미 연결되어 있으면 먼저 연결을 해제합니다
List<BluetoothDevice> devices = await flutterBlue.connectedDevices;
if (devices.length > 0) {
for (BluetoothDevice d in devices) {
if (d.name == MI_BAND_NAME) {
d.disconnect();
}
}
}
// 이제 다시 연결합니다.
flutterBlue.startScan(timeout: Duration(seconds: 4));
var subs = flutterBlue.scanResults.listen((results) async {
for (ScanResult r in results) {
if (r.device.name == MI_BAND_NAME) {
miDevice = r.device;
flutterBlue.stopScan();
await miDevice!.connect(timeout: Duration(seconds: 4), autoConnect: false);
}
}
});
이제 현재걸음수를 얻기 위해 필요한 블루투스특성을 얻어옵니다.
List<BluetoothService> services = await miDevice!.discoverServices();
for (BluetoothService s in services) {
if (s.uuid.toString() == UUID_SERVICE_MIBAND) {
for (BluetoothCharacteristic c in basicService!.characteristics) {
if (c.uuid.toString() == UUID_CHARACTERISTIC_REALTIME_STEPS) {
stepChr = c;
}
}
}
}
마지막으로 해당특성을 이용해서 현재 걸음수를 얻어와 봅시다. 여기서 중요한 점은 돌아오는 바이트스트림의 1,2번째에 숫자가 패키징되어서 저장된다는 점 입니다.
await stepChr!.setNotifyValue(true);
stepChr!.value.listen((data) {
if (data.length > 2) {
int steps = ((((data[1] & 255) | ((data[2] & 255) << 8))));
print("Current Step: ${steps}");
}
});
심박수 정보도 얻어오고 싶은데 아직은 연구가 필요한 상황입니다. 추후에 정리되면 게시하겠습니다. 그 이후에 음악재생하는 방법도 게시해보고자 합니다.